From b369463c48d98bfeb1cb07b209d89e1b0e067178 Mon Sep 17 00:00:00 2001 From: javasabr Date: Mon, 1 Dec 2025 18:32:36 +0100 Subject: [PATCH 01/19] working on publish delivering --- .../mqtt/service/SubscriptionService.java | 15 +++---- .../impl/InMemorySubscriptionService.java | 23 ++++------- .../impl/AbstractMqttInMessageHandler.java | 2 +- .../impl/ConnectInMqttInMessageHandler.java | 4 +- .../PublishReleaseMqttInMessageHandler.java | 2 +- .../impl/SubscribeMqttInMessageHandler.java | 6 +-- .../impl/UnsubscribeMqttInMessageHandler.java | 4 +- .../AbstractMqttPublishInMessageHandler.java | 18 +++------ .../AbstractMqttPublishOutMessageHandler.java | 13 +++--- ...PersistedMqttPublishOutMessageHandler.java | 12 +++--- .../Qos0MqttPublishOutMessageHandler.java | 4 +- .../impl/Qos1MqttPublishInMessageHandler.java | 2 +- .../impl/Qos2MqttPublishInMessageHandler.java | 2 +- .../Qos2MqttPublishOutMessageHandler.java | 2 +- .../TestExternalNetworkMqttUser.groovy | 12 +++--- ...Qos0MqttPublishInMessageHandlerTest.groovy | 32 ++++++++------- ...os0MqttPublishOutMessageHandlerTest.groovy | 40 +++++++++++++++++++ ...QosMqttPublishOutMessageHandlerTest.groovy | 8 ++++ .../java/javasabr/mqtt/model/MqttUser.java | 6 ++- .../mqtt/model/publishing/Publish.java | 2 +- .../model/subscription/TestMqttUser.groovy | 10 ++++- .../network/impl/AbstractNetworkMqttUser.java | 14 +++---- .../mqtt/network/user/NetworkMqttUser.java | 7 +--- 23 files changed, 140 insertions(+), 100 deletions(-) create mode 100644 core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy create mode 100644 core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/QosMqttPublishOutMessageHandlerTest.groovy diff --git a/core-service/src/main/java/javasabr/mqtt/service/SubscriptionService.java b/core-service/src/main/java/javasabr/mqtt/service/SubscriptionService.java index aead2590..b8de7ded 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/SubscriptionService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/SubscriptionService.java @@ -1,14 +1,13 @@ package javasabr.mqtt.service; +import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; import javasabr.mqtt.model.session.MqttSession; import javasabr.mqtt.model.subscriber.SingleSubscriber; -import javasabr.mqtt.model.subscriber.Subscriber; import javasabr.mqtt.model.subscription.Subscription; import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.model.topic.TopicName; -import javasabr.mqtt.network.user.NetworkMqttUser; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.MutableArray; @@ -16,9 +15,7 @@ * Subscription service */ public interface SubscriptionService { - - NetworkMqttUser resolveClient(Subscriber subscriber); - + default Array findSubscribers(TopicName topicName) { return findSubscribersTo(MutableArray.ofType(SingleSubscriber.class), topicName); } @@ -32,7 +29,7 @@ default Array findSubscribers(TopicName topicName) { * @param subscriptions the list of request to subscribe topics * @return array of subscribe ack reason codes */ - Array subscribe(NetworkMqttUser user, MqttSession session, Array subscriptions); + Array subscribe(MqttUser user, MqttSession session, Array subscriptions); /** * Removes MQTT client from listening to the topics. @@ -41,9 +38,9 @@ default Array findSubscribers(TopicName topicName) { * @param topicFilters topic filters * @return array of unsubscribe ack reason codes */ - Array unsubscribe(NetworkMqttUser user, MqttSession session, Array topicFilters); + Array unsubscribe(MqttUser user, MqttSession session, Array topicFilters); - void cleanSubscriptions(NetworkMqttUser user, MqttSession session); + void cleanSubscriptions(MqttUser user, MqttSession session); - void restoreSubscriptions(NetworkMqttUser user, MqttSession session); + void restoreSubscriptions(MqttUser user, MqttSession session); } diff --git a/core-service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java b/core-service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java index fcc635ba..b9bd0e86 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java @@ -4,18 +4,17 @@ import static javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode.SUCCESS; import javasabr.mqtt.model.MqttClientConnectionConfig; +import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; import javasabr.mqtt.model.session.ActiveSubscriptions; import javasabr.mqtt.model.session.MqttSession; import javasabr.mqtt.model.subscriber.SingleSubscriber; -import javasabr.mqtt.model.subscriber.Subscriber; import javasabr.mqtt.model.subscriber.tree.ConcurrentSubscriberTree; import javasabr.mqtt.model.subscription.Subscription; import javasabr.mqtt.model.topic.SharedTopicFilter; import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.model.topic.TopicName; -import javasabr.mqtt.network.user.NetworkMqttUser; import javasabr.mqtt.service.SubscriptionService; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; @@ -37,14 +36,6 @@ public InMemorySubscriptionService() { this.subscriberTree = new ConcurrentSubscriberTree(); } - @Override - public NetworkMqttUser resolveClient(Subscriber subscriber) { - if (subscriber instanceof SingleSubscriber single) { - return (NetworkMqttUser) single.user(); - } - throw new IllegalArgumentException("Unexpected subscriber: " + subscriber); - } - @Override public Array findSubscribersTo(MutableArray container, TopicName topicName) { Array matched = subscriberTree.matches(topicName); @@ -54,7 +45,7 @@ public Array findSubscribersTo(MutableArray @Override public Array subscribe( - NetworkMqttUser user, + MqttUser user, MqttSession session, Array subscriptions) { @@ -69,7 +60,7 @@ public Array subscribe( return subscribeResults; } - private SubscribeAckReasonCode addSubscription(NetworkMqttUser user, MqttSession session, Subscription subscription) { + private SubscribeAckReasonCode addSubscription(MqttUser user, MqttSession session, Subscription subscription) { MqttClientConnectionConfig connectionConfig = user.connectionConfig(); TopicFilter topicFilter = subscription.topicFilter(); if (topicFilter.isInvalid()) { @@ -90,7 +81,7 @@ private SubscribeAckReasonCode addSubscription(NetworkMqttUser user, MqttSession @Override public Array unsubscribe( - NetworkMqttUser user, + MqttUser user, MqttSession session, Array topicFilters) { @@ -105,7 +96,7 @@ public Array unsubscribe( return unsubscribeResults; } - private UnsubscribeAckReasonCode removeSubscription(NetworkMqttUser user, MqttSession session, TopicFilter topicFilter) { + private UnsubscribeAckReasonCode removeSubscription(MqttUser user, MqttSession session, TopicFilter topicFilter) { if (topicFilter.isInvalid()) { return UnsubscribeAckReasonCode.TOPIC_FILTER_INVALID; } else if (subscriberTree.unsubscribe(user, topicFilter)) { @@ -119,7 +110,7 @@ private UnsubscribeAckReasonCode removeSubscription(NetworkMqttUser user, MqttSe } @Override - public void cleanSubscriptions(NetworkMqttUser user, MqttSession session) { + public void cleanSubscriptions(MqttUser user, MqttSession session) { Array subscriptions = session .activeSubscriptions() .subscriptions(); @@ -129,7 +120,7 @@ public void cleanSubscriptions(NetworkMqttUser user, MqttSession session) { } @Override - public void restoreSubscriptions(NetworkMqttUser user, MqttSession session) { + public void restoreSubscriptions(MqttUser user, MqttSession session) { Array subscriptions = session .activeSubscriptions() .subscriptions(); diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java index 7a019d75..04b22f8d 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java @@ -111,7 +111,7 @@ protected void malformedProtocolError(MqttConnection connection, U user, Excepti MqttOutMessage feedback = messageOutFactoryService .resolveFactory(user) .newDisconnect(user, DisconnectReasonCode.MALFORMED_PACKET, exception.getMessage()); - user.send(feedback) + user.sendAsync(feedback) .thenAccept(_ -> connection.close()); } diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index 9c6f1dc1..d9575aec 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -85,7 +85,7 @@ protected void processValidMessage( } private void reject(ExternalNetworkMqttUser user, ConnectAckReasonCode connectAckReasonCode) { - user.sendAsync(messageOutFactoryService + user.sendInBackground(messageOutFactoryService .resolveFactory(user) .newConnectAck(user, connectAckReasonCode)); } @@ -209,7 +209,7 @@ private Mono onConnected( subscriptionService.restoreSubscriptions(user, session); return Mono.fromFuture(user - .send(connectAck) + .sendAsync(connectAck) .thenApply(result -> onSentConnAck(user, session, result))); } diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java index 04cac7fe..47407020 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java @@ -52,7 +52,7 @@ protected void processValidMessage( } private void handleUnknownMessageId(ExternalNetworkMqttUser client, int messageId) { - client.sendAsync(messageOutFactoryService + client.sendInBackground(messageOutFactoryService .resolveFactory(client) .newPublishCompleted(messageId, PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND)); } diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java index e0c7474b..a80ab1cd 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java @@ -140,7 +140,7 @@ private void handleMessageIdIsInUse( Array subscribeResults = Array.repeated( SubscribeAckReasonCode.PACKET_IDENTIFIER_IN_USE, subscribeMessage.subscriptionsCount()); - user.sendAsync(messageOutFactoryService + user.sendInBackground(messageOutFactoryService .resolveFactory(user) .newSubscribeAck(subscribeMessage.messageId(), subscribeResults)); } @@ -156,7 +156,7 @@ private void handleSubscriptionIdNotSupported( MqttOutMessage response = messageOutFactoryService .resolveFactory(user) .newSubscribeAck(messageId, subscribeResults); - user.send(response) + user.sendAsync(response) .thenAccept(_ -> session .inMessageTracker() .remove(messageId)); @@ -171,7 +171,7 @@ private void sendSubscribeResults( MqttOutMessage response = messageOutFactoryService .resolveFactory(user) .newSubscribeAck(messageId, subscribeResults); - user.send(response) + user.sendAsync(response) .thenAccept(_ -> session .inMessageTracker() .remove(messageId)); diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java index 3fee4177..f9db0ec8 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java @@ -71,7 +71,7 @@ protected void processValidMessage( .newUnsubscribeAck(unsubscribeMessage.messageId(), unsubscribeResults); user - .send(response) + .sendAsync(response) .thenAccept(_ -> session .inMessageTracker() .remove(messageId)); @@ -83,7 +83,7 @@ private void handleMessageIdIsInUse( Array unsubscribeResults = Array.repeated( UnsubscribeAckReasonCode.PACKET_IDENTIFIER_IN_USE, unsubscribeMessage.topicFiltersCount()); - user.sendAsync(messageOutFactoryService + user.sendInBackground(messageOutFactoryService .resolveFactory(user) .newUnsubscribeAck(unsubscribeMessage.messageId(), unsubscribeResults)); } diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java index 19236360..ebd6ac9a 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java @@ -33,9 +33,7 @@ public abstract class AbstractMqttPublishInMessageHandler messageTacker.remove(messageId)); } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java index 613e1f96..76e7775a 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java @@ -1,6 +1,7 @@ package javasabr.mqtt.service.publish.handler.impl; import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.subscriber.SingleSubscriber; import javasabr.mqtt.network.message.out.MqttOutMessage; @@ -27,9 +28,9 @@ public abstract class AbstractMqttPublishOutMessageHandler send(SendableMqttMessage message) { - return send((MqttOutMessage) message) + CompletionStage sendAsync(SendableMqttMessage message) { + return sendAsync((MqttOutMessage) message) } @Override - CompletableFuture send(MqttOutMessage message) { + CompletableFuture sendAsync(MqttOutMessage message) { sentMessages.add(message) if (!returnCompletedFeatures) { return CompletableFuture.supplyAsync({ true }, DELAYED_EXECUTOR); diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishInMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishInMessageHandlerTest.groovy index 7abb9682..fd2d8600 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishInMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishInMessageHandlerTest.groovy @@ -19,28 +19,30 @@ class Qos0MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler def subscriber1 = mockedExternalConnection(MqttVersion.MQTT_5) def subscriber2 = mockedExternalConnection(MqttVersion.MQTT_5) def publisher = mockedExternalConnection(MqttVersion.MQTT_5) - def client1 = subscriber1.user() as TestExternalNetworkMqttUser - def client2 = subscriber2.user() as TestExternalNetworkMqttUser - def client3 = publisher.user() as TestExternalNetworkMqttUser - def topicFilter = defaultTopicService.createTopicFilter(client1, "Qos0MqttPublishInMessageHandlerTest/1") - def topicName = defaultTopicService.createTopicName(client1, "Qos0MqttPublishInMessageHandlerTest/1") + def user1 = subscriber1.user() as TestExternalNetworkMqttUser + def user2 = subscriber2.user() as TestExternalNetworkMqttUser + def user3 = publisher.user() as TestExternalNetworkMqttUser + def topicFilter = defaultTopicService.createTopicFilter(user1, "Qos0MqttPublishInMessageHandlerTest/1") + def expectedTopicName = defaultTopicService.createTopicName(user1, "Qos0MqttPublishInMessageHandlerTest/1") defaultSubscriptionService.subscribe( - client1, - client1.session(), + user1, + user1.session(), Array.of(Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE))) defaultSubscriptionService.subscribe( - client2, - client2.session(), + user2, + user2.session(), Array.of(Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE))) when: - publishInHandler.handle(client3, Publish.minimal(QoS.AT_MOST_ONCE, topicName, testPayload)) + publishInHandler.handle(user3, Publish.minimal(QoS.AT_MOST_ONCE, expectedTopicName, testPayload)) then: 'sender should not have any feedback' - client3.isEmpty() + user3.isEmpty() then: 'subscribers should receive the publish' - def message1 = client1.nextSentMessage(PublishMqtt5OutMessage) - message1.topicName() == topicName - def message2 = client2.nextSentMessage(PublishMqtt5OutMessage) - message2.topicName() == topicName + with(user1.nextSentMessage(PublishMqtt5OutMessage)) { + topicName() == expectedTopicName + } + with(user2.nextSentMessage(PublishMqtt5OutMessage)) { + topicName() == expectedTopicName + } } def "should not provide any feedback for accepted publish without any subscriber"() { diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy new file mode 100644 index 00000000..4118273e --- /dev/null +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy @@ -0,0 +1,40 @@ +package javasabr.mqtt.service.publish.handler.impl + +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.MqttVersion +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.publishing.Publish +import javasabr.mqtt.model.subscriber.SingleSubscriber +import javasabr.mqtt.model.subscription.Subscription +import javasabr.mqtt.network.message.out.PublishMqtt5OutMessage +import javasabr.mqtt.service.TestExternalNetworkMqttUser +import javasabr.mqtt.service.publish.handler.PublishHandlingResult + +class Qos0MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandlerTest { + + def "should deliver publish to subscriber"() { + given: + def publishOutHandler = new Qos0MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def testTopicName = defaultTopicService.createTopicName(user, "Qos0MqttPublishOutMessageHandlerTest/1") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos0MqttPublishOutMessageHandlerTest/1") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + when: + def result = publishOutHandler.handle(publish, subscriber) + then: + result == PublishHandlingResult.SUCCESS + with(user.nextSentMessage(PublishMqtt5OutMessage)) { + qos() == QoS.AT_MOST_ONCE + payload() == testPayload + topicName() == testTopicName + messageId() == MqttProperties.MESSAGE_ID_IS_NOT_SET + topicAlias() == MqttProperties.TOPIC_ALIAS_NOT_SET + } + } +} diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/QosMqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/QosMqttPublishOutMessageHandlerTest.groovy new file mode 100644 index 00000000..81e3199c --- /dev/null +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/QosMqttPublishOutMessageHandlerTest.groovy @@ -0,0 +1,8 @@ +package javasabr.mqtt.service.publish.handler.impl + +import javasabr.mqtt.service.IntegrationServiceSpecification + +abstract class QosMqttPublishOutMessageHandlerTest extends IntegrationServiceSpecification { + static { + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/MqttUser.java b/model/src/main/java/javasabr/mqtt/model/MqttUser.java index deba84f6..d910b1de 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttUser.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttUser.java @@ -17,10 +17,12 @@ public interface MqttUser { @Nullable MqttSession session(); - void sendAsync(SendableMqttMessage message); + MqttClientConnectionConfig connectionConfig(); + + void sendInBackground(SendableMqttMessage message); /** * @return the feature with result of delivering the message */ - CompletionStage send(SendableMqttMessage message); + CompletionStage sendAsync(SendableMqttMessage message); } diff --git a/model/src/main/java/javasabr/mqtt/model/publishing/Publish.java b/model/src/main/java/javasabr/mqtt/model/publishing/Publish.java index 56e30ebb..2e10c730 100644 --- a/model/src/main/java/javasabr/mqtt/model/publishing/Publish.java +++ b/model/src/main/java/javasabr/mqtt/model/publishing/Publish.java @@ -52,7 +52,7 @@ public static Publish minimal(int messageId, QoS qos, TopicName topicName, byte[ public static Publish minimal(QoS qos, TopicName topicName, byte[] payload) { return minimal(MqttProperties.MESSAGE_ID_IS_NOT_SET, qos, topicName, payload); } - + public Publish withDuplicated() { if (duplicated) { return this; diff --git a/model/src/testFixtures/groovy/javasabr/mqtt/model/subscription/TestMqttUser.groovy b/model/src/testFixtures/groovy/javasabr/mqtt/model/subscription/TestMqttUser.groovy index dc5a1892..be262616 100644 --- a/model/src/testFixtures/groovy/javasabr/mqtt/model/subscription/TestMqttUser.groovy +++ b/model/src/testFixtures/groovy/javasabr/mqtt/model/subscription/TestMqttUser.groovy @@ -1,6 +1,7 @@ package javasabr.mqtt.model.subscription import com.fasterxml.jackson.annotation.JsonValue +import javasabr.mqtt.model.MqttClientConnectionConfig import javasabr.mqtt.model.MqttUser import javasabr.mqtt.model.message.SendableMqttMessage import javasabr.mqtt.model.session.MqttSession @@ -37,11 +38,16 @@ record TestMqttUser(String id) implements MqttUser { } @Override - void sendAsync(SendableMqttMessage message) { + MqttClientConnectionConfig connectionConfig() { + return null + } + + @Override + void sendInBackground(SendableMqttMessage message) { } @Override - CompletionStage send(SendableMqttMessage message) { + CompletionStage sendAsync(SendableMqttMessage message) { return CompletableFuture.completedFuture(true) } } diff --git a/network/src/main/java/javasabr/mqtt/network/impl/AbstractNetworkMqttUser.java b/network/src/main/java/javasabr/mqtt/network/impl/AbstractNetworkMqttUser.java index edef817c..6311bffc 100644 --- a/network/src/main/java/javasabr/mqtt/network/impl/AbstractNetworkMqttUser.java +++ b/network/src/main/java/javasabr/mqtt/network/impl/AbstractNetworkMqttUser.java @@ -58,30 +58,30 @@ public String ipAddress() { } @Override - public void sendAsync(SendableMqttMessage message) { - sendAsync((MqttOutMessage) message); + public void sendInBackground(SendableMqttMessage message) { + sendInBackground((MqttOutMessage) message); } @Override - public void sendAsync(MqttOutMessage message) { + public void sendInBackground(MqttOutMessage message) { log.debug(clientId, message.name(), message, "[%s] Send to client packet:[%s] %s"::formatted); connection.send(message); } @Override - public CompletionStage send(SendableMqttMessage message) { - return send((MqttOutMessage) message); + public CompletionStage sendAsync(SendableMqttMessage message) { + return sendAsync((MqttOutMessage) message); } @Override - public CompletableFuture send(MqttOutMessage message) { + public CompletableFuture sendAsync(MqttOutMessage message) { log.debug(clientId, message.name(), message, "[%s] Send to client packet:[%s] %s"::formatted); return connection.sendWithFeedback(message); } @Override public CompletableFuture closeWithReason(MqttOutMessage message) { - return send(message) + return sendAsync(message) .thenApply(sent -> { connection.close(); return sent; diff --git a/network/src/main/java/javasabr/mqtt/network/user/NetworkMqttUser.java b/network/src/main/java/javasabr/mqtt/network/user/NetworkMqttUser.java index 9fcb13bc..54100ca3 100644 --- a/network/src/main/java/javasabr/mqtt/network/user/NetworkMqttUser.java +++ b/network/src/main/java/javasabr/mqtt/network/user/NetworkMqttUser.java @@ -1,7 +1,6 @@ package javasabr.mqtt.network.user; import java.util.concurrent.CompletableFuture; -import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.out.MqttOutMessage; @@ -11,19 +10,17 @@ public interface NetworkMqttUser extends MqttUser { MqttConnection connection(); - - MqttClientConnectionConfig connectionConfig(); @Nullable @Override NetworkMqttSession session(); - void sendAsync(MqttOutMessage message); + void sendInBackground(MqttOutMessage message); /** * @return the feature with result of delivering the message */ - CompletableFuture send(MqttOutMessage message); + CompletableFuture sendAsync(MqttOutMessage message); /** * @return the feature with result of delivering the reason before closing From 7301ada2fa9f216465bdf00674a688077f24b37a Mon Sep 17 00:00:00 2001 From: javasabr Date: Tue, 2 Dec 2025 05:55:10 +0100 Subject: [PATCH 02/19] working on publish delivering --- .../config/MqttBrokerSpringConfig.java | 6 +- .../handler/PublishHandlingResult.java | 4 + .../AbstractMqttPublishOutMessageHandler.java | 34 ++++---- ...PersistedMqttPublishOutMessageHandler.java | 79 ------------------- .../Qos0MqttPublishOutMessageHandler.java | 20 +++-- .../Qos1MqttPublishOutMessageHandler.java | 41 ++++++++-- .../Qos2MqttPublishOutMessageHandler.java | 2 +- ...TrackableMqttPublishOutMessageHandler.java | 78 ++++++++++++++++++ .../impl/InMemoryNetworkMqttSession.java | 15 ++-- ...os0MqttPublishOutMessageHandlerTest.groovy | 2 + .../mqtt/model/session/PublishRetryer.java | 2 +- .../session/TrackableMessageCallback.java | 2 +- 12 files changed, 166 insertions(+), 119 deletions(-) delete mode 100644 core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java create mode 100644 core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index eb4c4470..fe7550f0 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -303,7 +303,7 @@ MqttServerConnectionConfig externalConnectionConfig(Environment env) { env.getProperty( "mqtt.external.connection.topic.alias.maximum", int.class, - MqttProperties.TOPIC_ALIAS_MAX_DEFAULT), + 0), env.getProperty( "mqtt.external.connection.default.session.expiration.time", long.class, @@ -319,7 +319,7 @@ MqttServerConnectionConfig externalConnectionConfig(Environment env) { env.getProperty( "mqtt.external.connection.retain.available", boolean.class, - MqttProperties.RETAIN_AVAILABLE_DEFAULT), + false), // not implemented env.getProperty( "mqtt.external.connection.wildcard.subscription.available", boolean.class, @@ -327,7 +327,7 @@ MqttServerConnectionConfig externalConnectionConfig(Environment env) { env.getProperty( "mqtt.external.connection.subscription.id.available", boolean.class, - MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT), + false), // not implemented env.getProperty( "mqtt.external.connection.shared.subscription.available", boolean.class, diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/PublishHandlingResult.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/PublishHandlingResult.java index 864e8d81..f3e5aeaf 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/PublishHandlingResult.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/PublishHandlingResult.java @@ -36,6 +36,10 @@ public enum PublishHandlingResult { // CUSTOM NOT_EXPECTED_CLIENT( + true, + PublishAckReasonCode.UNSPECIFIED_ERROR, + PublishReceivedReasonCode.UNSPECIFIED_ERROR), + SESSION_IS_ALREADY_CLOSED( true, PublishAckReasonCode.UNSPECIFIED_ERROR, PublishReceivedReasonCode.UNSPECIFIED_ERROR); diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java index 76e7775a..a780b700 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java @@ -1,10 +1,11 @@ package javasabr.mqtt.service.publish.handler.impl; -import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.publishing.Publish; +import javasabr.mqtt.model.session.MqttSession; import javasabr.mqtt.model.subscriber.SingleSubscriber; import javasabr.mqtt.network.message.out.MqttOutMessage; +import javasabr.mqtt.network.session.NetworkMqttSession; import javasabr.mqtt.network.user.NetworkMqttUser; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; @@ -22,36 +23,39 @@ public abstract class AbstractMqttPublishOutMessageHandler implements MqttPublishOutMessageHandler { - Class expectedUser; + Class expectedUserType; SubscriptionService subscriptionService; MessageOutFactoryService messageOutFactoryService; @Override public PublishHandlingResult handle(Publish publish, SingleSubscriber subscriber) { MqttUser user = subscriber.resolveUser(); - if (!expectedUser.isInstance(user)) { - log.warning(user, "Accepted not expected user:[%s]"::formatted); + if (!expectedUserType.isInstance(user)) { + log.warning(user.clientId(), user.getClass(), "[%s] Not expected user of type:[%s]"::formatted); return PublishHandlingResult.NOT_EXPECTED_CLIENT; } - publish = reconstruct(user, publish); + U expectedUser = expectedUserType.cast(user); + MqttSession session = expectedUser.session(); + if (session == null) { + log.warning(user.clientId(), "[%s] Session is already closed"::formatted); + return PublishHandlingResult.SESSION_IS_ALREADY_CLOSED; + } + publish = reconstruct(expectedUser, session, publish); if (publish == null) { return PublishHandlingResult.SKIPPED; } - return handleImpl(publish, expectedUser.cast(user)); + return handleImpl(expectedUser, session, publish); } @Nullable - protected Publish reconstruct(MqttUser user, Publish original) { - return original.with( - MqttProperties.MESSAGE_ID_IS_NOT_SET, - qos(), - false, - MqttProperties.TOPIC_ALIAS_NOT_SET); - } + protected abstract Publish reconstruct(U user, MqttSession session, Publish original); - protected abstract PublishHandlingResult handleImpl(Publish publish, U user) ; + protected PublishHandlingResult handleImpl(U user, MqttSession session, Publish publish) { + startDelivering(user, publish); + return PublishHandlingResult.SUCCESS; + } - protected void startDelivering(NetworkMqttUser user, Publish publish) { + protected void startDelivering(U user, Publish publish) { MqttOutMessage outMessage = messageOutFactoryService .resolveFactory(user) .newPublish( diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java deleted file mode 100644 index 4ef7acb0..00000000 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java +++ /dev/null @@ -1,79 +0,0 @@ -package javasabr.mqtt.service.publish.handler.impl; - -import javasabr.mqtt.model.MqttProperties; -import javasabr.mqtt.model.MqttUser; -import javasabr.mqtt.model.message.TrackableMqttMessage; -import javasabr.mqtt.model.publishing.Publish; -import javasabr.mqtt.model.session.MqttSession; -import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; -import javasabr.mqtt.network.session.NetworkMqttSession; -import javasabr.mqtt.network.session.NetworkMqttSession.PendingMessageHandler; -import javasabr.mqtt.network.user.NetworkMqttUser; -import javasabr.mqtt.service.MessageOutFactoryService; -import javasabr.mqtt.service.SubscriptionService; -import javasabr.mqtt.service.publish.handler.PublishHandlingResult; -import lombok.AccessLevel; -import lombok.experimental.FieldDefaults; -import org.jspecify.annotations.Nullable; - -@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public abstract class PersistedMqttPublishOutMessageHandler extends - AbstractMqttPublishOutMessageHandler { - - PendingMessageHandler pendingMessageHandler; - - protected PersistedMqttPublishOutMessageHandler( - SubscriptionService subscriptionService, - MessageOutFactoryService messageOutFactoryService) { - super(ExternalNetworkMqttUser.class, subscriptionService, messageOutFactoryService); - this.pendingMessageHandler = new PendingMessageHandler() { - @Override - public boolean handleResponse(NetworkMqttUser user, TrackableMqttMessage response) { - return handleReceivedResponse(user, response); - } - @Override - public void resend(NetworkMqttUser user, Publish publish) { - tryToDeliverAgain(user, publish); - } - }; - } - - @Nullable - @Override - protected Publish reconstruct(MqttUser user, Publish original) { - MqttSession session = user.session(); - if (session == null) { - return null; - } - return original.with( - // generate new uniq packet id per client - session.generateMessageId(), - qos(), - false, - MqttProperties.TOPIC_ALIAS_NOT_SET); - } - - @Override - protected PublishHandlingResult handleImpl(Publish publish, ExternalNetworkMqttUser user) { - - NetworkMqttSession session = user.session(); - if (session == null) { - return PublishHandlingResult.SKIPPED; - } - - // register waiting async response - session.registerOutPublish(publish, pendingMessageHandler); - - // send publish - startDelivering(user, publish); - return PublishHandlingResult.SUCCESS; - } - - protected boolean handleReceivedResponse(NetworkMqttUser user, TrackableMqttMessage response) { - return false; - } - - protected void tryToDeliverAgain(NetworkMqttUser user, Publish publish) { - startDelivering(user, publish.withDuplicated()); - } -} diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java index 601bdf77..8803aa3a 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java @@ -1,13 +1,16 @@ package javasabr.mqtt.service.publish.handler.impl; +import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.QoS; import javasabr.mqtt.model.publishing.Publish; +import javasabr.mqtt.model.session.MqttSession; import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; -import javasabr.mqtt.service.publish.handler.PublishHandlingResult; +import org.jspecify.annotations.Nullable; -public class Qos0MqttPublishOutMessageHandler extends AbstractMqttPublishOutMessageHandler { +public class Qos0MqttPublishOutMessageHandler + extends AbstractMqttPublishOutMessageHandler { public Qos0MqttPublishOutMessageHandler( SubscriptionService subscriptionService, @@ -20,9 +23,16 @@ public QoS qos() { return QoS.AT_MOST_ONCE; } + @Nullable @Override - protected PublishHandlingResult handleImpl(Publish publish, ExternalNetworkMqttUser user) { - startDelivering(user, publish); - return PublishHandlingResult.SUCCESS; + protected Publish reconstruct( + ExternalNetworkMqttUser user, + MqttSession session, + Publish original) { + return original.with( + MqttProperties.MESSAGE_ID_IS_NOT_SET, + qos(), + false, + MqttProperties.TOPIC_ALIAS_NOT_SET); } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java index 4aec8dd7..7094e790 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java @@ -1,13 +1,21 @@ package javasabr.mqtt.service.publish.handler.impl; +import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.message.TrackableMqttMessage; +import javasabr.mqtt.model.publishing.Publish; +import javasabr.mqtt.model.session.MessageTacker; +import javasabr.mqtt.model.session.MqttSession; +import javasabr.mqtt.model.session.TrackedMessageMeta; +import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.network.message.in.PublishAckMqttInMessage; -import javasabr.mqtt.network.user.NetworkMqttUser; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; +import lombok.CustomLog; -public class Qos1MqttPublishOutMessageHandler extends PersistedMqttPublishOutMessageHandler { +@CustomLog +public class Qos1MqttPublishOutMessageHandler extends TrackableMqttPublishOutMessageHandler { public Qos1MqttPublishOutMessageHandler( SubscriptionService subscriptionService, @@ -21,11 +29,32 @@ public QoS qos() { } @Override - protected boolean handleReceivedResponse(NetworkMqttUser user, TrackableMqttMessage response) { - if (!(response instanceof PublishAckMqttInMessage)) { - throw new IllegalStateException("Unexpected response: " + response); + protected boolean handleReceivedTrackableMessageImpl( + ExternalNetworkMqttUser user, + MqttSession session, + TrackableMqttMessage message) { + int messageId = message.messageId(); + MessageTacker messageTacker = session.inMessageTracker(); + TrackedMessageMeta messageMeta = messageTacker.stored(messageId); + if (messageMeta == null) { + log.warning(user.clientId(), messageId, "[%s] No any stored information for messageId:[%d]"::formatted); + return true; } - // just return 'true' to remove pending packet from session + if (messageMeta.messageType() != MqttMessageType.PUBLISH) { + log.warning(user.clientId(), messageMeta, messageId, + "[%s] Not expected tracked message meta:[%s] for messageId:[%d]"::formatted); + return true; + } else if (!(message instanceof PublishAckMqttInMessage publishAck)) { + log.warning(user.clientId(), message, "[%s] Not expected message:%s]"::formatted); + return true; + } + messageTacker.remove(messageId); return true; } + + @Override + protected void retryDeliveringImpl(ExternalNetworkMqttUser user, MqttSession session, Publish publish) { + + + } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java index 06c6bf7b..61111855 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java @@ -10,7 +10,7 @@ import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; -public class Qos2MqttPublishOutMessageHandler extends PersistedMqttPublishOutMessageHandler { +public class Qos2MqttPublishOutMessageHandler extends TrackableMqttPublishOutMessageHandler { public Qos2MqttPublishOutMessageHandler( SubscriptionService subscriptionService, diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java new file mode 100644 index 00000000..e43d77e5 --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java @@ -0,0 +1,78 @@ +package javasabr.mqtt.service.publish.handler.impl; + +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.message.MqttMessageType; +import javasabr.mqtt.model.message.TrackableMqttMessage; +import javasabr.mqtt.model.publishing.Publish; +import javasabr.mqtt.model.session.MessageTacker; +import javasabr.mqtt.model.session.MqttSession; +import javasabr.mqtt.model.session.ProcessingPublishes; +import javasabr.mqtt.model.session.PublishRetryer; +import javasabr.mqtt.model.session.TrackableMessageCallback; +import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; +import javasabr.mqtt.service.MessageOutFactoryService; +import javasabr.mqtt.service.SubscriptionService; +import javasabr.mqtt.service.publish.handler.PublishHandlingResult; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; + +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +public abstract class TrackableMqttPublishOutMessageHandler extends + AbstractMqttPublishOutMessageHandler { + + TrackableMessageCallback trackableMessageCallback; + PublishRetryer publishRetryer; + + protected TrackableMqttPublishOutMessageHandler( + SubscriptionService subscriptionService, + MessageOutFactoryService messageOutFactoryService) { + super(ExternalNetworkMqttUser.class, subscriptionService, messageOutFactoryService); + this.trackableMessageCallback = this::handleReceivedTrackableMessage; + this.publishRetryer = this::retryDelivering; + } + + @Nullable + @Override + protected Publish reconstruct(ExternalNetworkMqttUser user, MqttSession session, Publish original) { + return original.with( + // generate new uniq message id for specific user + session.generateMessageId(), + qos(), + false, + MqttProperties.TOPIC_ALIAS_NOT_SET); + } + + @Override + protected PublishHandlingResult handleImpl(ExternalNetworkMqttUser user, MqttSession session, Publish publish) { + // register message id + MessageTacker messageTacker = session.outMessageTracker(); + messageTacker.add(publish.messageId(), MqttMessageType.PUBLISH); + // register callback and retrier + ProcessingPublishes processingPublishes = session.outProcessingPublishes(); + processingPublishes.register(publish, trackableMessageCallback, publishRetryer); + return super.handleImpl(user, session, publish); + } + + protected boolean handleReceivedTrackableMessage( + MqttUser user, + MqttSession session, + TrackableMqttMessage message) { + return handleReceivedTrackableMessageImpl(expectedUserType.cast(user), session, message); + } + + protected abstract boolean handleReceivedTrackableMessageImpl( + ExternalNetworkMqttUser user, + MqttSession session, + TrackableMqttMessage message); + + protected void retryDelivering(MqttUser user, MqttSession session, Publish publish) { + retryDeliveringImpl(expectedUserType.cast(user), session, publish); + } + + protected abstract void retryDeliveringImpl( + ExternalNetworkMqttUser user, + MqttSession session, + Publish publish); +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java index 47f748ce..d612c350 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java +++ b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java @@ -107,14 +107,13 @@ public InMemoryNetworkMqttSession(String clientId) { @Override public int generateMessageId() { - - int nextId = messageIdGenerator.incrementAndGet(); - - if (nextId >= MqttProperties.MAXIMUM_PACKET_ID) { - messageIdGenerator.compareAndSet(nextId, 0); - return generateMessageId(); - } - + int nextId; + do { + nextId = messageIdGenerator.incrementAndGet(); + if (nextId >= MqttProperties.MAXIMUM_PACKET_ID) { + messageIdGenerator.compareAndSet(nextId, 0); + } + } while (nextId >= MqttProperties.MAXIMUM_PACKET_ID); return nextId; } diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy index 4118273e..1d23a427 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy @@ -25,12 +25,14 @@ class Qos0MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def subscriber = new SingleSubscriber(user, subscription) def originalMessageId = 60 def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() when: def result = publishOutHandler.handle(publish, subscriber) then: result == PublishHandlingResult.SUCCESS with(user.nextSentMessage(PublishMqtt5OutMessage)) { qos() == QoS.AT_MOST_ONCE + !duplicate() payload() == testPayload topicName() == testTopicName messageId() == MqttProperties.MESSAGE_ID_IS_NOT_SET diff --git a/model/src/main/java/javasabr/mqtt/model/session/PublishRetryer.java b/model/src/main/java/javasabr/mqtt/model/session/PublishRetryer.java index 56e70529..d5ec8f56 100644 --- a/model/src/main/java/javasabr/mqtt/model/session/PublishRetryer.java +++ b/model/src/main/java/javasabr/mqtt/model/session/PublishRetryer.java @@ -7,5 +7,5 @@ public interface PublishRetryer { PublishRetryer NO_OPS = (owner, session, publish) -> {}; - void retry(MqttUser owner, Object session, Publish publish); + void retry(MqttUser user, MqttSession session, Publish publish); } diff --git a/model/src/main/java/javasabr/mqtt/model/session/TrackableMessageCallback.java b/model/src/main/java/javasabr/mqtt/model/session/TrackableMessageCallback.java index 02f0d7b2..9c1bc6b4 100644 --- a/model/src/main/java/javasabr/mqtt/model/session/TrackableMessageCallback.java +++ b/model/src/main/java/javasabr/mqtt/model/session/TrackableMessageCallback.java @@ -8,5 +8,5 @@ public interface TrackableMessageCallback { /** * @return true if this handler should be de-register */ - boolean accept(MqttUser owner, MqttSession session, TrackableMqttMessage message); + boolean accept(MqttUser user, MqttSession session, TrackableMqttMessage message); } From d69e8b972bd46dd18df5ad0960fd17e1e6a2ebf8 Mon Sep 17 00:00:00 2001 From: javasabr Date: Tue, 2 Dec 2025 06:25:58 +0100 Subject: [PATCH 03/19] working on publish delivering --- .../AbstractMqttPublishOutMessageHandler.java | 5 +- .../Qos1MqttPublishOutMessageHandler.java | 50 ++++++++++++++----- .../impl/Qos2MqttPublishInMessageHandler.java | 2 +- .../Qos2MqttPublishOutMessageHandler.java | 48 +++++++++++++++++- ...TrackableMqttPublishOutMessageHandler.java | 17 ++++++- 5 files changed, 101 insertions(+), 21 deletions(-) diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java index a780b700..f0c0538f 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java @@ -5,7 +5,6 @@ import javasabr.mqtt.model.session.MqttSession; import javasabr.mqtt.model.subscriber.SingleSubscriber; import javasabr.mqtt.network.message.out.MqttOutMessage; -import javasabr.mqtt.network.session.NetworkMqttSession; import javasabr.mqtt.network.user.NetworkMqttUser; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; @@ -51,11 +50,11 @@ public PublishHandlingResult handle(Publish publish, SingleSubscriber subscriber protected abstract Publish reconstruct(U user, MqttSession session, Publish original); protected PublishHandlingResult handleImpl(U user, MqttSession session, Publish publish) { - startDelivering(user, publish); + send(user, publish); return PublishHandlingResult.SUCCESS; } - protected void startDelivering(U user, Publish publish) { + protected void send(U user, Publish publish) { MqttOutMessage outMessage = messageOutFactoryService .resolveFactory(user) .newPublish( diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java index 7094e790..ae005f10 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java @@ -1,10 +1,10 @@ package javasabr.mqtt.service.publish.handler.impl; -import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.QoS; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.message.TrackableMqttMessage; import javasabr.mqtt.model.publishing.Publish; +import javasabr.mqtt.model.reason.code.PublishAckReasonCode; import javasabr.mqtt.model.session.MessageTacker; import javasabr.mqtt.model.session.MqttSession; import javasabr.mqtt.model.session.TrackedMessageMeta; @@ -13,6 +13,7 @@ import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; import lombok.CustomLog; +import org.jspecify.annotations.Nullable; @CustomLog public class Qos1MqttPublishOutMessageHandler extends TrackableMqttPublishOutMessageHandler { @@ -32,29 +33,52 @@ public QoS qos() { protected boolean handleReceivedTrackableMessageImpl( ExternalNetworkMqttUser user, MqttSession session, - TrackableMqttMessage message) { + TrackableMqttMessage message, + @Nullable TrackedMessageMeta trackedMessageMeta) { + int messageId = message.messageId(); - MessageTacker messageTacker = session.inMessageTracker(); - TrackedMessageMeta messageMeta = messageTacker.stored(messageId); - if (messageMeta == null) { - log.warning(user.clientId(), messageId, "[%s] No any stored information for messageId:[%d]"::formatted); + String clientId = user.clientId(); + if (trackedMessageMeta == null) { + log.warning(clientId, messageId, "[%s] No any stored information for messageId:[%d]"::formatted); return true; - } - if (messageMeta.messageType() != MqttMessageType.PUBLISH) { - log.warning(user.clientId(), messageMeta, messageId, - "[%s] Not expected tracked message meta:[%s] for messageId:[%d]"::formatted); + } else if (trackedMessageMeta.messageType() != MqttMessageType.PUBLISH) { + log.warning(clientId, trackedMessageMeta, messageId, + "[%s] No expected message meta:[%s] for messageId:[%d]"::formatted); return true; - } else if (!(message instanceof PublishAckMqttInMessage publishAck)) { - log.warning(user.clientId(), message, "[%s] Not expected message:%s]"::formatted); + } + if (!(message instanceof PublishAckMqttInMessage publishAck)) { + log.warning(clientId, message.messageType(), "[%s] Not expected message type:[%s]"::formatted); return true; } + + PublishAckReasonCode reasonCode = publishAck.reasonCode(); + if (reasonCode != PublishAckReasonCode.SUCCESS) { + log.warning(clientId, reasonCode, messageId, "[%s] Received error response:[%s] for publish:[%s]"::formatted); + } + + MessageTacker messageTacker = session.outMessageTracker(); messageTacker.remove(messageId); + + log.debug(clientId, messageId, "[%s] Completed publish:[%s]"::formatted); return true; } @Override protected void retryDeliveringImpl(ExternalNetworkMqttUser user, MqttSession session, Publish publish) { - + int messageId = publish.messageId(); + MessageTacker messageTacker = session.outMessageTracker(); + TrackedMessageMeta messageMeta = messageTacker.stored(messageId); + if (messageMeta == null) { + log.warning(user.clientId(), messageId, "[%s] No any stored information for messageId:[%d]"::formatted); + return; + } else if(messageMeta.messageType() != MqttMessageType.PUBLISH) { + log.warning(user.clientId(), messageMeta, messageId, + "[%s] Not expected tracked message meta:[%s] for messageId:[%d]"::formatted); + return; + } + + log.debug(user.clientId(), messageId, "[%s] Retry to deliver publish:[%s]"::formatted); + send(user, publish.withDuplicated()); } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java index bd7a8e2e..532b0b8f 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java @@ -158,7 +158,7 @@ private boolean handleReceivedTrackableMessage(MqttUser user, MqttSession sessio "[%s] Not expected tracked message meta:[%s] for messageId:[%d]"::formatted); return true; } else if (!(message instanceof PublishReleaseMqttInMessage release)) { - log.warning(networkUser.clientId(), message, "[%s] Not expected message:%s]"::formatted); + log.warning(networkUser.clientId(), message, "[%s] Not expected message:[%s]"::formatted); return true; } diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java index 61111855..436985dd 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java @@ -1,15 +1,24 @@ package javasabr.mqtt.service.publish.handler.impl; -import static javasabr.mqtt.model.reason.code.PublishReleaseReasonCode.SUCCESS; - import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.message.TrackableMqttMessage; +import javasabr.mqtt.model.publishing.Publish; +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; +import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode; +import javasabr.mqtt.model.session.MessageTacker; +import javasabr.mqtt.model.session.MqttSession; +import javasabr.mqtt.model.session.TrackedMessageMeta; +import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.network.message.in.PublishCompleteMqttInMessage; import javasabr.mqtt.network.message.in.PublishReceivedMqttInMessage; import javasabr.mqtt.network.user.NetworkMqttUser; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; +import lombok.CustomLog; +import org.jspecify.annotations.Nullable; +@CustomLog public class Qos2MqttPublishOutMessageHandler extends TrackableMqttPublishOutMessageHandler { public Qos2MqttPublishOutMessageHandler( @@ -23,6 +32,41 @@ public QoS qos() { return QoS.EXACTLY_ONCE; } + @Override + protected boolean handleReceivedTrackableMessageImpl( + ExternalNetworkMqttUser user, + MqttSession session, + TrackableMqttMessage message, + @Nullable TrackedMessageMeta trackedMessageMeta) { + + int messageId = message.messageId(); + MessageTacker messageTacker = session.outMessageTracker(); + + if (message instanceof PublishReceivedMqttInMessage publishReceived) { + + PublishReceivedReasonCode reasonCode = publishReceived.reasonCode(); + if (reasonCode != PublishReceivedReasonCode.SUCCESS) { + log.warning(user.clientId(), reasonCode, messageId, + "[%s] Received error response:[%s] for publish:[%s]"::formatted); + // we can cancel the flow + messageTacker.remove(messageId); + return true; + } + messageTacker.update(messageId, MqttMessageType.PUBLISH_RECEIVED, reasonCode); + user.sendInBackground(messageOutFactoryService + .resolveFactory(user) + .newPublishRelease(messageId, PublishReleaseReasonCode.SUCCESS)); + return false; + } + + return false; + } + + @Override + protected void retryDeliveringImpl(ExternalNetworkMqttUser user, MqttSession session, Publish publish) { + + } + @Override protected boolean handleReceivedResponse(NetworkMqttUser user, TrackableMqttMessage response) { if (response instanceof PublishReceivedMqttInMessage) { diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java index e43d77e5..e0614722 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java @@ -10,14 +10,17 @@ import javasabr.mqtt.model.session.ProcessingPublishes; import javasabr.mqtt.model.session.PublishRetryer; import javasabr.mqtt.model.session.TrackableMessageCallback; +import javasabr.mqtt.model.session.TrackedMessageMeta; import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.publish.handler.PublishHandlingResult; import lombok.AccessLevel; +import lombok.CustomLog; import lombok.experimental.FieldDefaults; import org.jspecify.annotations.Nullable; +@CustomLog @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) public abstract class TrackableMqttPublishOutMessageHandler extends AbstractMqttPublishOutMessageHandler { @@ -59,13 +62,23 @@ protected boolean handleReceivedTrackableMessage( MqttUser user, MqttSession session, TrackableMqttMessage message) { - return handleReceivedTrackableMessageImpl(expectedUserType.cast(user), session, message); + + int messageId = message.messageId(); + MessageTacker messageTacker = session.outMessageTracker(); + TrackedMessageMeta trackedMessageMeta = messageTacker.stored(messageId); + + return handleReceivedTrackableMessageImpl( + expectedUserType.cast(user), + session, + message, + trackedMessageMeta); } protected abstract boolean handleReceivedTrackableMessageImpl( ExternalNetworkMqttUser user, MqttSession session, - TrackableMqttMessage message); + TrackableMqttMessage message, + @Nullable TrackedMessageMeta trackedMessageMeta); protected void retryDelivering(MqttUser user, MqttSession session, Publish publish) { retryDeliveringImpl(expectedUserType.cast(user), session, publish); From abdc101e52fbc2dc7a104ecc13b9871e6f2c5cbe Mon Sep 17 00:00:00 2001 From: javasabr Date: Tue, 2 Dec 2025 07:14:08 +0100 Subject: [PATCH 04/19] working on publish delivering --- .../Qos1MqttPublishOutMessageHandler.java | 22 +--- .../Qos2MqttPublishOutMessageHandler.java | 106 ++++++++++++------ ...TrackableMqttPublishOutMessageHandler.java | 19 +++- 3 files changed, 90 insertions(+), 57 deletions(-) diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java index ae005f10..784d96cc 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java @@ -47,7 +47,8 @@ protected boolean handleReceivedTrackableMessageImpl( return true; } if (!(message instanceof PublishAckMqttInMessage publishAck)) { - log.warning(clientId, message.messageType(), "[%s] Not expected message type:[%s]"::formatted); + log.warning(clientId, message.messageType(), messageId, + "[%s] Not expected message type:[%s] for messageId:[%d]"::formatted); return true; } @@ -62,23 +63,4 @@ protected boolean handleReceivedTrackableMessageImpl( log.debug(clientId, messageId, "[%s] Completed publish:[%s]"::formatted); return true; } - - @Override - protected void retryDeliveringImpl(ExternalNetworkMqttUser user, MqttSession session, Publish publish) { - - int messageId = publish.messageId(); - MessageTacker messageTacker = session.outMessageTracker(); - TrackedMessageMeta messageMeta = messageTacker.stored(messageId); - if (messageMeta == null) { - log.warning(user.clientId(), messageId, "[%s] No any stored information for messageId:[%d]"::formatted); - return; - } else if(messageMeta.messageType() != MqttMessageType.PUBLISH) { - log.warning(user.clientId(), messageMeta, messageId, - "[%s] Not expected tracked message meta:[%s] for messageId:[%d]"::formatted); - return; - } - - log.debug(user.clientId(), messageId, "[%s] Retry to deliver publish:[%s]"::formatted); - send(user, publish.withDuplicated()); - } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java index 436985dd..fe42e67d 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java @@ -3,7 +3,7 @@ import javasabr.mqtt.model.QoS; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.message.TrackableMqttMessage; -import javasabr.mqtt.model.publishing.Publish; +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode; import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode; import javasabr.mqtt.model.session.MessageTacker; @@ -12,7 +12,6 @@ import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.network.message.in.PublishCompleteMqttInMessage; import javasabr.mqtt.network.message.in.PublishReceivedMqttInMessage; -import javasabr.mqtt.network.user.NetworkMqttUser; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; import lombok.CustomLog; @@ -38,46 +37,87 @@ protected boolean handleReceivedTrackableMessageImpl( MqttSession session, TrackableMqttMessage message, @Nullable TrackedMessageMeta trackedMessageMeta) { + if (message instanceof PublishReceivedMqttInMessage publishReceived) { + return handlePublishRelease(user, session, message, trackedMessageMeta, publishReceived); + } else if (message instanceof PublishCompleteMqttInMessage publishComplete) { + handlePublishComplete(user, session, message, trackedMessageMeta, publishComplete); + return true; + } + log.warning(user.clientId(), message.messageType(), message.messageId(), + "[%s] Not expected message type:[%s] for messageId:[%d]"::formatted); + return true; + } - int messageId = message.messageId(); - MessageTacker messageTacker = session.outMessageTracker(); + private boolean handlePublishRelease( + ExternalNetworkMqttUser user, + MqttSession session, + TrackableMqttMessage message, + @Nullable TrackedMessageMeta trackedMessageMeta, + PublishReceivedMqttInMessage publishReceived) { - if (message instanceof PublishReceivedMqttInMessage publishReceived) { + int messageId = message.messageId(); + String clientId = user.clientId(); + if (trackedMessageMeta != null && trackedMessageMeta.messageType() != MqttMessageType.PUBLISH) { + log.warning(clientId, trackedMessageMeta, messageId, + "[%s] No expected message meta:[%s] for messageId:[%d]"::formatted); + return true; + } - PublishReceivedReasonCode reasonCode = publishReceived.reasonCode(); - if (reasonCode != PublishReceivedReasonCode.SUCCESS) { - log.warning(user.clientId(), reasonCode, messageId, - "[%s] Received error response:[%s] for publish:[%s]"::formatted); - // we can cancel the flow + MessageTacker messageTacker = session.outMessageTracker(); + PublishReceivedReasonCode reasonCode = publishReceived.reasonCode(); + if (reasonCode != PublishReceivedReasonCode.SUCCESS) { + log.warning(clientId, reasonCode, messageId, + "[%s] Received error response:[%s] for publish:[%s]"::formatted); + // we can cancel the flow + if (trackedMessageMeta != null) { messageTacker.remove(messageId); - return true; } - messageTacker.update(messageId, MqttMessageType.PUBLISH_RECEIVED, reasonCode); - user.sendInBackground(messageOutFactoryService - .resolveFactory(user) - .newPublishRelease(messageId, PublishReleaseReasonCode.SUCCESS)); - return false; + return true; } - - return false; - } - @Override - protected void retryDeliveringImpl(ExternalNetworkMqttUser user, MqttSession session, Publish publish) { - + PublishReleaseReasonCode releaseResult; + // we unknown this flow + if (trackedMessageMeta == null) { + releaseResult = PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND; + } else { + releaseResult = PublishReleaseReasonCode.SUCCESS; + messageTacker.update(messageId, MqttMessageType.PUBLISH_RELEASE, reasonCode); + } + + user.sendInBackground(messageOutFactoryService + .resolveFactory(user) + .newPublishRelease(messageId, releaseResult)); + + // cancel this flow if it's not success + return releaseResult != PublishReleaseReasonCode.SUCCESS; } - @Override - protected boolean handleReceivedResponse(NetworkMqttUser user, TrackableMqttMessage response) { - if (response instanceof PublishReceivedMqttInMessage) { - user.sendInBackground(messageOutFactoryService - .resolveFactory(user) - .newPublishRelease(response.messageId(), SUCCESS)); - return false; - } else if (response instanceof PublishCompleteMqttInMessage) { - return true; - } else { - throw new IllegalStateException("Unexpected response: " + response); + private void handlePublishComplete( + ExternalNetworkMqttUser user, + MqttSession session, + TrackableMqttMessage message, + @Nullable TrackedMessageMeta trackedMessageMeta, + PublishCompleteMqttInMessage publishComplete) { + + int messageId = message.messageId(); + String clientId = user.clientId(); + if (trackedMessageMeta == null) { + log.warning(clientId, messageId, "[%s] No any stored information for messageId:[%d]"::formatted); + return; + } else if (trackedMessageMeta.messageType() != MqttMessageType.PUBLISH_RELEASE) { + log.warning(clientId, trackedMessageMeta, messageId, + "[%s] No expected message meta:[%s] for messageId:[%d]"::formatted); + return; } + + PublishCompletedReasonCode reasonCode = publishComplete.reasonCode(); + if (reasonCode != PublishCompletedReasonCode.SUCCESS) { + log.warning(clientId, reasonCode, messageId, + "[%s] Received error response:[%s] for publish:[%s]"::formatted); + } + + // finish the flow + MessageTacker messageTacker = session.outMessageTracker(); + messageTacker.remove(messageId); } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java index e0614722..c038db54 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java @@ -84,8 +84,19 @@ protected void retryDelivering(MqttUser user, MqttSession session, Publish publi retryDeliveringImpl(expectedUserType.cast(user), session, publish); } - protected abstract void retryDeliveringImpl( - ExternalNetworkMqttUser user, - MqttSession session, - Publish publish); + protected void retryDeliveringImpl(ExternalNetworkMqttUser user, MqttSession session, Publish publish) { + int messageId = publish.messageId(); + MessageTacker messageTacker = session.outMessageTracker(); + TrackedMessageMeta messageMeta = messageTacker.stored(messageId); + if (messageMeta == null) { + log.warning(user.clientId(), messageId, "[%s] No any stored information for messageId:[%d]"::formatted); + return; + } else if(messageMeta.messageType() != MqttMessageType.PUBLISH) { + log.warning(user.clientId(), messageMeta, messageId, + "[%s] Not expected tracked message meta:[%s] for messageId:[%d]"::formatted); + return; + } + log.debug(user.clientId(), messageId, "[%s] Retry to deliver publish:[%s]"::formatted); + send(user, publish.withDuplicated()); + } } From 19ba64286ad7b68a28bee53b82633eca3a3d2622 Mon Sep 17 00:00:00 2001 From: javasabr Date: Tue, 2 Dec 2025 18:49:52 +0100 Subject: [PATCH 05/19] working on publish delivering --- .../handler/impl/AbstractMqttPublishInMessageHandler.java | 3 +-- .../java/javasabr/mqtt/model/MqttServerConnectionConfig.java | 2 +- .../javasabr/mqtt/network/impl/AbstractNetworkMqttUser.java | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java index ebd6ac9a..ab91e42d 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java @@ -73,8 +73,7 @@ protected void handleImpl(U user, NetworkMqttSession session, Publish publish) { } } - log.debug(user.clientId(), count, - "[%s] Started delivering publish to [%s] subscribers"::formatted); + log.debug(count, "Started delivering publish to [%s] subscribers"::formatted); handleSuccess(user, session, publish, count); for (SingleSubscriber subscriber : subscribers) { diff --git a/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java b/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java index e64e9fc1..46ae8745 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java @@ -61,7 +61,7 @@ public MqttServerConnectionConfig( this.receiveMaxPublishes = receiveMaxPublishes; this.topicAliasMaxValue = NumberUtils.validate( topicAliasMaxValue, - MqttProperties.TOPIC_ALIAS_MIN, + MqttProperties.TOPIC_ALIAS_MAXIMUM_DISABLED, MqttProperties.TOPIC_ALIAS_MAX); this.defaultSessionExpiryInterval = defaultSessionExpiryInterval; this.keepAliveEnabled = keepAliveEnabled; diff --git a/network/src/main/java/javasabr/mqtt/network/impl/AbstractNetworkMqttUser.java b/network/src/main/java/javasabr/mqtt/network/impl/AbstractNetworkMqttUser.java index 6311bffc..63ac5cd0 100644 --- a/network/src/main/java/javasabr/mqtt/network/impl/AbstractNetworkMqttUser.java +++ b/network/src/main/java/javasabr/mqtt/network/impl/AbstractNetworkMqttUser.java @@ -64,7 +64,7 @@ public void sendInBackground(SendableMqttMessage message) { @Override public void sendInBackground(MqttOutMessage message) { - log.debug(clientId, message.name(), message, "[%s] Send to client packet:[%s] %s"::formatted); + log.debug(clientId, message.name(), message, "[%s] Send message to user:[%s] %s"::formatted); connection.send(message); } @@ -75,7 +75,7 @@ public CompletionStage sendAsync(SendableMqttMessage message) { @Override public CompletableFuture sendAsync(MqttOutMessage message) { - log.debug(clientId, message.name(), message, "[%s] Send to client packet:[%s] %s"::formatted); + log.debug(clientId, message.name(), message, "[%s] Send message to user:[%s] %s"::formatted); return connection.sendWithFeedback(message); } From 0ed5c0983beaf7b1da6a89ce7de8bd1fc794b154 Mon Sep 17 00:00:00 2001 From: javasabr Date: Wed, 3 Dec 2025 20:11:25 +0100 Subject: [PATCH 06/19] finish first part of upgrading delivering publishes to subscribers --- .../mqtt/service/acl/AclRulesLoader.groovy | 2 +- .../model/acl/rule/AllowSubscribeRule.java | 2 - .../mqtt/model/acl/rule/DenyPublishRule.java | 2 - .../model/acl/rule/DenySubscribeRule.java | 2 - .../impl/ConnectInMqttInMessageHandler.java | 2 +- ...singOutPublishesMqttInMessageHandler.java} | 14 ++- .../impl/PublishAckMqttInMessageHandler.java | 3 +- .../PublishCompleteMqttInMessageHandler.java | 2 +- .../PublishReceiveMqttInMessageHandler.java | 2 +- .../Qos1MqttPublishOutMessageHandler.java | 1 - .../impl/InMemoryActiveSubscriptions.java | 9 ++ .../session/impl/InMemoryMessageTacker.java | 9 ++ .../impl/InMemoryMqttSessionService.java | 30 +++-- .../impl/InMemoryNetworkMqttSession.java | 111 +++--------------- .../impl/InMemoryProcessingPublishes.java | 30 ++++- .../impl/InMemoryTopicNameMapping.java | 9 ++ .../mqtt/model/topic/AbstractTopic.java | 1 - .../model/subscription/TestMqttUser.groovy | 1 + .../mqtt/network/NetworkMqttSession.java | 25 ---- .../ConfigurableNetworkMqttSession.java | 6 - .../network/session/NetworkMqttSession.java | 12 ++ 21 files changed, 113 insertions(+), 162 deletions(-) rename core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/{PendingOutResponseMqttInMessageHandler.java => ProcessingOutPublishesMqttInMessageHandler.java} (64%) delete mode 100644 network/src/main/java/javasabr/mqtt/network/NetworkMqttSession.java rename network/src/main/java/javasabr/mqtt/network/{ => session}/ConfigurableNetworkMqttSession.java (72%) create mode 100644 network/src/main/java/javasabr/mqtt/network/session/NetworkMqttSession.java diff --git a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy index 639ea034..5d4bfa9c 100644 --- a/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy +++ b/acl-groovy-dsl/src/main/groovy/javasabr/mqtt/service/acl/AclRulesLoader.groovy @@ -1,6 +1,6 @@ package javasabr.mqtt.service.acl -import groovy.transform.Field + import javasabr.mqtt.model.acl.Operation import javasabr.mqtt.model.acl.rule.Rule import javasabr.mqtt.model.exception.AclConfigurationException diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AllowSubscribeRule.java b/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AllowSubscribeRule.java index df09f235..19a15312 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AllowSubscribeRule.java +++ b/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/AllowSubscribeRule.java @@ -3,12 +3,10 @@ import static javasabr.mqtt.model.acl.Action.ALLOW; import static javasabr.mqtt.model.acl.Operation.SUBSCRIBE; -import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.acl.Action; import javasabr.mqtt.model.acl.Operation; import javasabr.mqtt.model.acl.condition.MqttUserCondition; import javasabr.mqtt.model.acl.condition.TopicCondition; -import javasabr.mqtt.model.topic.AbstractTopic; import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenyPublishRule.java b/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenyPublishRule.java index 56746d3f..f8dbc0ab 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenyPublishRule.java +++ b/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenyPublishRule.java @@ -3,12 +3,10 @@ import static javasabr.mqtt.model.acl.Action.DENY; import static javasabr.mqtt.model.acl.Operation.PUBLISH; -import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.acl.Action; import javasabr.mqtt.model.acl.Operation; import javasabr.mqtt.model.acl.condition.MqttUserCondition; import javasabr.mqtt.model.acl.condition.TopicCondition; -import javasabr.mqtt.model.topic.AbstractTopic; import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) diff --git a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenySubscribeRule.java b/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenySubscribeRule.java index 7e4ac339..e5b16b0d 100644 --- a/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenySubscribeRule.java +++ b/acl-groovy-dsl/src/main/java/javasabr/mqtt/model/acl/rule/DenySubscribeRule.java @@ -3,12 +3,10 @@ import static javasabr.mqtt.model.acl.Action.DENY; import static javasabr.mqtt.model.acl.Operation.SUBSCRIBE; -import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.acl.Action; import javasabr.mqtt.model.acl.Operation; import javasabr.mqtt.model.acl.condition.MqttUserCondition; import javasabr.mqtt.model.acl.condition.TopicCondition; -import javasabr.mqtt.model.topic.AbstractTopic; import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index d9575aec..9e5dedf3 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -220,7 +220,7 @@ private boolean onSentConnAck(ConfigurableNetworkMqttUser user, NetworkMqttSessi return false; } - session.resendPendingPackets(user); + session.resendNotConfirmedPublishesTo(user); return true; } diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ProcessingOutPublishesMqttInMessageHandler.java similarity index 64% rename from core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java rename to core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ProcessingOutPublishesMqttInMessageHandler.java index c0f1d69b..c9d215b8 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/ProcessingOutPublishesMqttInMessageHandler.java @@ -1,17 +1,18 @@ package javasabr.mqtt.service.message.handler.impl; import javasabr.mqtt.model.message.TrackableMqttMessage; +import javasabr.mqtt.model.session.ProcessingPublishes; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.network.message.in.MqttInMessage; import javasabr.mqtt.network.session.NetworkMqttSession; import javasabr.mqtt.service.MessageOutFactoryService; -public abstract class PendingOutResponseMqttInMessageHandler

- extends AbstractMqttInMessageHandler { +public abstract class ProcessingOutPublishesMqttInMessageHandler + extends AbstractMqttInMessageHandler { - protected PendingOutResponseMqttInMessageHandler( - Class

expectedNetworkPacket, + protected ProcessingOutPublishesMqttInMessageHandler( + Class expectedNetworkPacket, MessageOutFactoryService messageOutFactoryService) { super(ExternalNetworkMqttUser.class, expectedNetworkPacket, messageOutFactoryService); } @@ -21,7 +22,8 @@ protected void processValidMessage( MqttConnection connection, ExternalNetworkMqttUser user, NetworkMqttSession session, - P message) { - session.updateOutPendingPacket(user, message); + M message) { + ProcessingPublishes processingPublishes = session.outProcessingPublishes(); + processingPublishes.apply(user, message); } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishAckMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishAckMqttInMessageHandler.java index 81a887b8..0353073b 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishAckMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishAckMqttInMessageHandler.java @@ -4,7 +4,8 @@ import javasabr.mqtt.network.message.in.PublishAckMqttInMessage; import javasabr.mqtt.service.MessageOutFactoryService; -public class PublishAckMqttInMessageHandler extends PendingOutResponseMqttInMessageHandler { +public class PublishAckMqttInMessageHandler extends + ProcessingOutPublishesMqttInMessageHandler { public PublishAckMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { super(PublishAckMqttInMessage.class, messageOutFactoryService); diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishCompleteMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishCompleteMqttInMessageHandler.java index 6cc4cdb8..cb122ce0 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishCompleteMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishCompleteMqttInMessageHandler.java @@ -5,7 +5,7 @@ import javasabr.mqtt.service.MessageOutFactoryService; public class PublishCompleteMqttInMessageHandler extends - PendingOutResponseMqttInMessageHandler { + ProcessingOutPublishesMqttInMessageHandler { public PublishCompleteMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { super(PublishCompleteMqttInMessage.class, messageOutFactoryService); diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReceiveMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReceiveMqttInMessageHandler.java index 39bacad5..2a38f0da 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReceiveMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReceiveMqttInMessageHandler.java @@ -5,7 +5,7 @@ import javasabr.mqtt.service.MessageOutFactoryService; public class PublishReceiveMqttInMessageHandler extends - PendingOutResponseMqttInMessageHandler { + ProcessingOutPublishesMqttInMessageHandler { public PublishReceiveMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { super(PublishReceivedMqttInMessage.class, messageOutFactoryService); diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java index 784d96cc..66812d07 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java @@ -3,7 +3,6 @@ import javasabr.mqtt.model.QoS; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.message.TrackableMqttMessage; -import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.reason.code.PublishAckReasonCode; import javasabr.mqtt.model.session.MessageTacker; import javasabr.mqtt.model.session.MqttSession; diff --git a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryActiveSubscriptions.java b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryActiveSubscriptions.java index 55f6834e..eff006bc 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryActiveSubscriptions.java +++ b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryActiveSubscriptions.java @@ -87,4 +87,13 @@ public Array findBySubscriptionId(int subscriptionId) { } return result; } + + public void clear() { + long stamp = lock.writeLock(); + try { + subscriptions.clear(); + } finally { + lock.unlockWrite(stamp); + } + } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMessageTacker.java b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMessageTacker.java index d931f5c6..038822b4 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMessageTacker.java +++ b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMessageTacker.java @@ -75,4 +75,13 @@ public TrackedMessageMeta remove(int messageId) { lock.unlockWrite(stamp); } } + + public void clear() { + long stamp = lock.writeLock(); + try { + messageIdToMeta.clear(); + } finally { + lock.unlockWrite(stamp); + } + } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java index 54c9f0ad..117b25ab 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java @@ -20,7 +20,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE) public class InMemoryMqttSessionService implements MqttSessionService, Closeable { - final LockableRefToRefDictionary storedSession; + final LockableRefToRefDictionary storedSession; final Thread cleanThread; final int cleanIntervalInMs; @@ -38,15 +38,14 @@ public InMemoryMqttSessionService(int cleanIntervalInMs) { @Override public Mono restore(String clientId) { - ConfigurableNetworkMqttSession session = storedSession + InMemoryNetworkMqttSession session = storedSession .operations() .getInWriteLock(clientId, MutableRefToRefDictionary::remove); if (session != null) { - session.onRestored(); - log.debug(clientId, "Restored session for client:[%s]"::formatted); + log.debug(clientId, "[%s] Restored session"::formatted); } else { - log.debug(clientId, "No stored session for client:[%s]"::formatted); + log.debug(clientId, "[%s] No any stored session"::formatted); } return Mono.justOrEmpty(session); @@ -55,7 +54,7 @@ public Mono restore(String clientId) { @Override public Mono create(String clientId) { - ConfigurableNetworkMqttSession session = storedSession + InMemoryNetworkMqttSession session = storedSession .operations() .getInWriteLock(clientId, MutableRefToRefDictionary::remove); @@ -71,9 +70,8 @@ public Mono create(String clientId) { @Override public Mono store(String clientId, NetworkMqttSession session, long expiryInterval) { - var configurable = (ConfigurableNetworkMqttSession) session; + var configurable = (InMemoryNetworkMqttSession) session; configurable.expirationTime(System.currentTimeMillis() + (expiryInterval * 1000)); - configurable.onPersisted(); storedSession .operations() @@ -86,8 +84,8 @@ public Mono store(String clientId, NetworkMqttSession session, long exp private void cleanup() { - var toCheck = ArrayFactory.mutableArray(ConfigurableNetworkMqttSession.class); - var toRemove = ArrayFactory.mutableArray(ConfigurableNetworkMqttSession.class); + var toCheck = ArrayFactory.mutableArray(InMemoryNetworkMqttSession.class); + var toRemove = ArrayFactory.mutableArray(InMemoryNetworkMqttSession.class); while (!closed) { ThreadUtils.sleep(cleanIntervalInMs); @@ -110,15 +108,15 @@ private void cleanup() { } private static void removeExpiredSessions( - LockableRefToRefDictionary sessions, - MutableArray expired) { + LockableRefToRefDictionary sessions, + MutableArray expired) { long time = System.currentTimeMillis(); for (ConfigurableNetworkMqttSession session : expired) { if (session.expirationTime() <= time) { continue; } - ConfigurableNetworkMqttSession removed = sessions.remove(session.clientId()); + InMemoryNetworkMqttSession removed = sessions.remove(session.clientId()); log.debug(session.clientId(), "Removed expired session for client:[%]"::formatted); // if we already have new session under the same client id @@ -131,12 +129,12 @@ private static void removeExpiredSessions( } private boolean findToRemove( - MutableArray toCheck, - MutableArray toRemove) { + MutableArray toCheck, + MutableArray toRemove) { var currentTime = System.currentTimeMillis(); - for (ConfigurableNetworkMqttSession session : toCheck) { + for (InMemoryNetworkMqttSession session : toCheck) { if (session.expirationTime() > currentTime) { toRemove.add(session); } diff --git a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java index d612c350..9b331f69 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java +++ b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java @@ -1,18 +1,9 @@ package javasabr.mqtt.service.session.impl; -import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; import javasabr.mqtt.model.MqttProperties; -import javasabr.mqtt.model.message.TrackableMqttMessage; -import javasabr.mqtt.model.publishing.Publish; -import javasabr.mqtt.model.session.ActiveSubscriptions; -import javasabr.mqtt.model.session.MessageTacker; -import javasabr.mqtt.model.session.ProcessingPublishes; -import javasabr.mqtt.model.session.TopicNameMapping; import javasabr.mqtt.network.session.ConfigurableNetworkMqttSession; import javasabr.mqtt.network.user.NetworkMqttUser; -import javasabr.rlib.collections.array.ArrayFactory; -import javasabr.rlib.collections.array.LockableArray; import lombok.AccessLevel; import lombok.CustomLog; import lombok.EqualsAndHashCode; @@ -28,66 +19,22 @@ @Accessors(fluent = true, chain = false) @FieldDefaults(level = AccessLevel.PRIVATE) public class InMemoryNetworkMqttSession implements ConfigurableNetworkMqttSession { - - private record PendingPublish(Publish publish, PendingMessageHandler handler) {} - - private static void registerPublish( - Publish publish, - PendingMessageHandler handler, - LockableArray pendingPublishes) { - PendingPublish pendingPublish = new PendingPublish(publish, handler); - pendingPublishes - .operations() - .inWriteLock(pendingPublish, Collection::add); - } - - private static void updatePendingPacket( - NetworkMqttUser user, - TrackableMqttMessage response, - LockableArray pendingPublishes, - String clientId) { - - int messageId = response.messageId(); - PendingPublish pendingPublish; - - long stamp = pendingPublishes.readLock(); - try { - pendingPublish = pendingPublishes - .iterations() - .findAny(messageId, (pending, targetId) -> pending.publish.messageId() == targetId); - } finally { - pendingPublishes.readUnlock(stamp); - } - - if (pendingPublish == null) { - log.warning(clientId , response, "Not found pending publish for client:[%s] by received packet:[%s]"::formatted); - return; - } - - boolean shouldBeRemoved = pendingPublish.handler.handleResponse(user, response); - if (shouldBeRemoved) { - pendingPublishes - .operations() - .inWriteLock(pendingPublish, Collection::remove); - } - } - + final String clientId; final AtomicInteger messageIdGenerator; - final LockableArray pendingOutPublishes; @Getter - final MessageTacker inMessageTracker; + final InMemoryMessageTacker inMessageTracker; @Getter - final MessageTacker outMessageTracker; + final InMemoryMessageTacker outMessageTracker; @Getter - final ProcessingPublishes inProcessingPublishes; + final InMemoryProcessingPublishes inProcessingPublishes; @Getter - final ProcessingPublishes outProcessingPublishes; + final InMemoryProcessingPublishes outProcessingPublishes; @Getter - final ActiveSubscriptions activeSubscriptions; + final InMemoryActiveSubscriptions activeSubscriptions; @Getter - final TopicNameMapping topicNameMapping; + final InMemoryTopicNameMapping topicNameMapping; @Getter @Setter @@ -95,7 +42,6 @@ private static void updatePendingPacket( public InMemoryNetworkMqttSession(String clientId) { this.clientId = clientId; - this.pendingOutPublishes = ArrayFactory.stampedLockBasedArray(PendingPublish.class); this.messageIdGenerator = new AtomicInteger(0); this.inMessageTracker = new InMemoryMessageTacker(); this.outMessageTracker = new InMemoryMessageTacker(); @@ -123,41 +69,16 @@ public String clientId() { } @Override - public void registerOutPublish(Publish publish, PendingMessageHandler handler) { - registerPublish(publish, handler, pendingOutPublishes); + public int resendNotConfirmedPublishesTo(NetworkMqttUser user) { + return outProcessingPublishes.resendTo(user); } - - @Override - public void resendPendingPackets(NetworkMqttUser user) { - long stamp = pendingOutPublishes.readLock(); - try { - for (PendingPublish pending : pendingOutPublishes) { - PendingMessageHandler handler = pending.handler; - Publish publish = pending.publish; - handler.resend(user, publish); - } - } finally { - pendingOutPublishes.readUnlock(stamp); - } - } - - @Override - public void updateOutPendingPacket(NetworkMqttUser user, TrackableMqttMessage response) { - updatePendingPacket(user, response, pendingOutPublishes, clientId); - } - - @Override + public void clear() { - pendingOutPublishes - .operations() - .inWriteLock(Collection::clear); - } - - @Override - public void onPersisted() { - } - - @Override - public void onRestored() { + inMessageTracker.clear(); + outMessageTracker.clear(); + inProcessingPublishes.clear(); + outProcessingPublishes.clear(); + activeSubscriptions.clear(); + topicNameMapping.clear(); } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryProcessingPublishes.java b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryProcessingPublishes.java index 70dcaaa0..47bded34 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryProcessingPublishes.java +++ b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryProcessingPublishes.java @@ -51,7 +51,8 @@ public boolean apply(MqttUser user, TrackableMqttMessage message) { if (inProcessPublish == null) { return false; } - boolean shouldBeDeregister = inProcessPublish.callback.accept(user, session, message); + TrackableMessageCallback callback = inProcessPublish.callback(); + boolean shouldBeDeregister = callback.accept(user, session, message); if (shouldBeDeregister) { processing.remove(message.messageId()); } @@ -61,6 +62,24 @@ public boolean apply(MqttUser user, TrackableMqttMessage message) { } } + /** + * @return the count of resent publishes + */ + public int resendTo(MqttUser user) { + int counter = 0; + long stamp = lock.writeLock(); + try { + for (InProcessPublish inProcessPublish : processing) { + PublishRetryer retryer = inProcessPublish.retryer(); + retryer.retry(user, session, inProcessPublish.publish); + counter++; + } + } finally { + lock.unlockWrite(stamp); + } + return counter; + } + @Override public boolean remove(TrackableMqttMessage message) { long stamp = lock.writeLock(); @@ -71,4 +90,13 @@ public boolean remove(TrackableMqttMessage message) { lock.unlockWrite(stamp); } } + + public void clear() { + long stamp = lock.writeLock(); + try { + processing.clear(); + } finally { + lock.unlockWrite(stamp); + } + } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryTopicNameMapping.java b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryTopicNameMapping.java index 7af6e29d..eed881de 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryTopicNameMapping.java +++ b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryTopicNameMapping.java @@ -49,4 +49,13 @@ public TopicName resolve(int topicAlias) { lock.unlockRead(stamp); } } + + public void clear() { + long stamp = lock.writeLock(); + try { + topicNameAliases.clear(); + } finally { + lock.unlockWrite(stamp); + } + } } diff --git a/model/src/main/java/javasabr/mqtt/model/topic/AbstractTopic.java b/model/src/main/java/javasabr/mqtt/model/topic/AbstractTopic.java index cea0f54d..2b1f313e 100644 --- a/model/src/main/java/javasabr/mqtt/model/topic/AbstractTopic.java +++ b/model/src/main/java/javasabr/mqtt/model/topic/AbstractTopic.java @@ -1,6 +1,5 @@ package javasabr.mqtt.model.topic; -import java.util.Objects; import javasabr.mqtt.base.util.DebugUtils; import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; diff --git a/model/src/testFixtures/groovy/javasabr/mqtt/model/subscription/TestMqttUser.groovy b/model/src/testFixtures/groovy/javasabr/mqtt/model/subscription/TestMqttUser.groovy index 064b532d..0fa24336 100644 --- a/model/src/testFixtures/groovy/javasabr/mqtt/model/subscription/TestMqttUser.groovy +++ b/model/src/testFixtures/groovy/javasabr/mqtt/model/subscription/TestMqttUser.groovy @@ -5,6 +5,7 @@ import javasabr.mqtt.model.MqttClientConnectionConfig import javasabr.mqtt.model.MqttUser import javasabr.mqtt.model.message.SendableMqttMessage import javasabr.mqtt.model.session.MqttSession + import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletionStage diff --git a/network/src/main/java/javasabr/mqtt/network/NetworkMqttSession.java b/network/src/main/java/javasabr/mqtt/network/NetworkMqttSession.java deleted file mode 100644 index 3bcf597b..00000000 --- a/network/src/main/java/javasabr/mqtt/network/NetworkMqttSession.java +++ /dev/null @@ -1,25 +0,0 @@ -package javasabr.mqtt.network.session; - -import javasabr.mqtt.model.message.TrackableMqttMessage; -import javasabr.mqtt.model.publishing.Publish; -import javasabr.mqtt.model.session.MqttSession; -import javasabr.mqtt.network.user.NetworkMqttUser; - -public interface NetworkMqttSession extends MqttSession { - - interface PendingMessageHandler { - - /** - * @return true if pending packet can be removed. - */ - boolean handleResponse(NetworkMqttUser user, TrackableMqttMessage response); - - default void resend(NetworkMqttUser user, Publish publish) {} - } - - void resendPendingPackets(NetworkMqttUser user); - - void registerOutPublish(Publish publish, PendingMessageHandler handler); - - void updateOutPendingPacket(NetworkMqttUser user, TrackableMqttMessage response); -} diff --git a/network/src/main/java/javasabr/mqtt/network/ConfigurableNetworkMqttSession.java b/network/src/main/java/javasabr/mqtt/network/session/ConfigurableNetworkMqttSession.java similarity index 72% rename from network/src/main/java/javasabr/mqtt/network/ConfigurableNetworkMqttSession.java rename to network/src/main/java/javasabr/mqtt/network/session/ConfigurableNetworkMqttSession.java index 519fc221..dab637a8 100644 --- a/network/src/main/java/javasabr/mqtt/network/ConfigurableNetworkMqttSession.java +++ b/network/src/main/java/javasabr/mqtt/network/session/ConfigurableNetworkMqttSession.java @@ -3,10 +3,4 @@ public interface ConfigurableNetworkMqttSession extends NetworkMqttSession { void expirationTime(long expirationTime); - - void clear(); - - void onPersisted(); - - void onRestored(); } diff --git a/network/src/main/java/javasabr/mqtt/network/session/NetworkMqttSession.java b/network/src/main/java/javasabr/mqtt/network/session/NetworkMqttSession.java new file mode 100644 index 00000000..562086fd --- /dev/null +++ b/network/src/main/java/javasabr/mqtt/network/session/NetworkMqttSession.java @@ -0,0 +1,12 @@ +package javasabr.mqtt.network.session; + +import javasabr.mqtt.model.session.MqttSession; +import javasabr.mqtt.network.user.NetworkMqttUser; + +public interface NetworkMqttSession extends MqttSession { + + /** + * @return the count of resent publishes + */ + int resendNotConfirmedPublishesTo(NetworkMqttUser user); +} From 9263c8e3c97d0c618e1c7989df49357f8dc0e790 Mon Sep 17 00:00:00 2001 From: javasabr Date: Thu, 4 Dec 2025 19:09:45 +0100 Subject: [PATCH 07/19] working on improving testing --- .../Qos1MqttPublishOutMessageHandler.java | 13 +- ...TrackableMqttPublishOutMessageHandler.java | 26 ++ .../impl/InMemoryProcessingPublishes.java | 12 +- .../impl/InMemoryTrackedMessageMeta.java | 10 + ...os1MqttPublishOutMessageHandlerTest.groovy | 231 ++++++++++++++++++ .../mqtt/model/MqttProtocolErrors.java | 4 + .../model/session/ProcessingPublishes.java | 2 + .../message/in/PublishAckMqttInMessage.java | 7 + .../in/PublishReceivedMqttInMessage.java | 7 + 9 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java index 66812d07..072f7dca 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java @@ -40,22 +40,29 @@ protected boolean handleReceivedTrackableMessageImpl( if (trackedMessageMeta == null) { log.warning(clientId, messageId, "[%s] No any stored information for messageId:[%d]"::formatted); return true; - } else if (trackedMessageMeta.messageType() != MqttMessageType.PUBLISH) { + } + + MqttMessageType trackedMessageType = trackedMessageMeta.messageType(); + if (trackedMessageType != MqttMessageType.PUBLISH) { log.warning(clientId, trackedMessageMeta, messageId, "[%s] No expected message meta:[%s] for messageId:[%d]"::formatted); + handleNotExpectedFlowState(user, trackedMessageType, MqttMessageType.PUBLISH); return true; } + if (!(message instanceof PublishAckMqttInMessage publishAck)) { log.warning(clientId, message.messageType(), messageId, - "[%s] Not expected message type:[%s] for messageId:[%d]"::formatted); + "[%s] Not expected message type:%s for messageId:[%d]"::formatted); + handleNotExpectedResponseMessage(user, message, MqttMessageType.PUBLISH_ACK); return true; } PublishAckReasonCode reasonCode = publishAck.reasonCode(); if (reasonCode != PublishAckReasonCode.SUCCESS) { + // just to note in logs, we can't do anything with this log.warning(clientId, reasonCode, messageId, "[%s] Received error response:[%s] for publish:[%s]"::formatted); } - + MessageTacker messageTacker = session.outMessageTracker(); messageTacker.remove(messageId); diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java index c038db54..4013fe07 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java @@ -1,10 +1,12 @@ package javasabr.mqtt.service.publish.handler.impl; import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttProtocolErrors; import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.message.TrackableMqttMessage; import javasabr.mqtt.model.publishing.Publish; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; import javasabr.mqtt.model.session.MessageTacker; import javasabr.mqtt.model.session.MqttSession; import javasabr.mqtt.model.session.ProcessingPublishes; @@ -12,8 +14,10 @@ import javasabr.mqtt.model.session.TrackableMessageCallback; import javasabr.mqtt.model.session.TrackedMessageMeta; import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; +import javasabr.mqtt.network.message.out.MqttOutMessage; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; +import javasabr.mqtt.service.message.out.factory.MqttMessageOutFactory; import javasabr.mqtt.service.publish.handler.PublishHandlingResult; import lombok.AccessLevel; import lombok.CustomLog; @@ -99,4 +103,26 @@ protected void retryDeliveringImpl(ExternalNetworkMqttUser user, MqttSession ses log.debug(user.clientId(), messageId, "[%s] Retry to deliver publish:[%s]"::formatted); send(user, publish.withDuplicated()); } + + protected void handleNotExpectedFlowState( + ExternalNetworkMqttUser user, + MqttMessageType trackedMessageType, + MqttMessageType expectedTrackedMessageType) { + MqttMessageOutFactory messageOutFactory = messageOutFactoryService.resolveFactory(user); + String reason = MqttProtocolErrors.UNEXPECTED_FLOW_STATE.formatted( + trackedMessageType, + expectedTrackedMessageType); + user.closeWithReason(messageOutFactory.newDisconnect(user, DisconnectReasonCode.PROTOCOL_ERROR, reason)); + } + + protected void handleNotExpectedResponseMessage( + ExternalNetworkMqttUser user, + TrackableMqttMessage receivedMessage, + MqttMessageType expectedMessageType) { + MqttMessageOutFactory messageOutFactory = messageOutFactoryService.resolveFactory(user); + String reason = MqttProtocolErrors.UNEXPECTED_RESPONSE_MESSAGE.formatted( + receivedMessage.messageType(), + expectedMessageType); + user.closeWithReason(messageOutFactory.newDisconnect(user, DisconnectReasonCode.PROTOCOL_ERROR, reason)); + } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryProcessingPublishes.java b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryProcessingPublishes.java index 47bded34..70034999 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryProcessingPublishes.java +++ b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryProcessingPublishes.java @@ -90,7 +90,17 @@ public boolean remove(TrackableMqttMessage message) { lock.unlockWrite(stamp); } } - + + @Override + public int size() { + long stamp = lock.readLock(); + try { + return processing.size(); + } finally { + lock.unlockRead(stamp); + } + } + public void clear() { long stamp = lock.writeLock(); try { diff --git a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryTrackedMessageMeta.java b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryTrackedMessageMeta.java index 1602394f..f5b8e554 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryTrackedMessageMeta.java +++ b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryTrackedMessageMeta.java @@ -1,5 +1,6 @@ package javasabr.mqtt.service.session.impl; +import javasabr.mqtt.base.util.DebugUtils; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.reason.code.ReasonCode; import javasabr.mqtt.model.session.TrackedMessageMeta; @@ -18,7 +19,16 @@ @FieldDefaults(level = AccessLevel.PRIVATE) public class InMemoryTrackedMessageMeta implements TrackedMessageMeta { + static { + DebugUtils.registerIncludedFields("messageType", "reasonCode"); + } + MqttMessageType messageType; @Nullable ReasonCode reasonCode; + + @Override + public String toString() { + return DebugUtils.toJsonString(this); + } } diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy new file mode 100644 index 00000000..1862df25 --- /dev/null +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy @@ -0,0 +1,231 @@ +package javasabr.mqtt.service.publish.handler.impl + +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.MqttVersion +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.message.MqttMessageType +import javasabr.mqtt.model.publishing.Publish +import javasabr.mqtt.model.reason.code.PublishAckReasonCode +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode +import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode +import javasabr.mqtt.model.subscriber.SingleSubscriber +import javasabr.mqtt.model.subscription.Subscription +import javasabr.mqtt.network.message.in.PublishAckMqttInMessage +import javasabr.mqtt.network.message.in.PublishReceivedMqttInMessage +import javasabr.mqtt.network.message.out.PublishMqtt5OutMessage +import javasabr.mqtt.network.session.NetworkMqttSession +import javasabr.mqtt.service.TestExternalNetworkMqttUser +import javasabr.mqtt.service.publish.handler.PublishHandlingResult + +class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandlerTest { + + def "should deliver publish to subscriber"() { + given: + def publishOutHandler = new Qos1MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def testTopicName = defaultTopicService.createTopicName(user, "Qos1MqttPublishOutMessageHandlerTest/1") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos1MqttPublishOutMessageHandlerTest/1") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(publish, subscriber) + then: + result == PublishHandlingResult.SUCCESS + with(user.nextSentMessage(PublishMqtt5OutMessage)) { + qos() == QoS.AT_LEAST_ONCE + !duplicate() + payload() == testPayload + topicName() == testTopicName + messageId() != MqttProperties.MESSAGE_ID_IS_NOT_SET + topicAlias() == MqttProperties.TOPIC_ALIAS_NOT_SET + } + } + + def "should wait for ack response for publish"() { + given: + def publishOutHandler = new Qos1MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def session = user.session() + def testTopicName = defaultTopicService.createTopicName(user, "Qos1MqttPublishOutMessageHandlerTest/2") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos1MqttPublishOutMessageHandlerTest/2") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(publish, subscriber) + def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + then: + result == PublishHandlingResult.SUCCESS + with(session.outMessageTracker()) { + with(stored(receivedPublish.messageId())) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == null + } + } + with(session.outProcessingPublishes()) { + size() == 1 + } + when: 'send publish ack' + def publishAck = PublishAckMqttInMessage + .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishAck) + then: + with(session.outMessageTracker()) { + stored(receivedPublish.messageId()) == null + } + with(session.outProcessingPublishes()) { + size() == 0 + } + } + + def "should correctly handle publish ack when no stored trackable meta about the publish"() { + given: + def publishOutHandler = new Qos1MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def session = user.session() + def testTopicName = defaultTopicService.createTopicName(user, "Qos1MqttPublishOutMessageHandlerTest/3") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos1MqttPublishOutMessageHandlerTest/3") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(publish, subscriber) + def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + then: + result == PublishHandlingResult.SUCCESS + with(session.outMessageTracker()) { + with(stored(receivedPublish.messageId())) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == null + } + } + with(session.outProcessingPublishes()) { + size() == 1 + } + when: 'remove trackable info and send publish ack' + session + .outMessageTracker() + .remove(receivedPublish.messageId()) + def publishAck = PublishAckMqttInMessage + .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishAck) + then: + with(session.outMessageTracker()) { + stored(receivedPublish.messageId()) == null + } + with(session.outProcessingPublishes()) { + size() == 0 + } + } + + def "should correctly handle unexpected message as response for publish"() { + given: + def publishOutHandler = new Qos1MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def session = user.session() + def testTopicName = defaultTopicService.createTopicName(user, "Qos1MqttPublishOutMessageHandlerTest/4") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos1MqttPublishOutMessageHandlerTest/4") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(publish, subscriber) + def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + then: + result == PublishHandlingResult.SUCCESS + with(session.outMessageTracker()) { + with(stored(receivedPublish.messageId())) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == null + } + } + with(session.outProcessingPublishes()) { + size() == 1 + } + when: 'send unexpected publish receive' + def publishReceive = PublishReceivedMqttInMessage + .of(receivedPublish.messageId(), PublishReceivedReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishReceive) + then: + with(session.outMessageTracker()) { + stored(receivedPublish.messageId()) == null + } + with(session.outProcessingPublishes()) { + size() == 0 + } + } + + def "should correctly handle publish ack when unexpected stored trackable meta about the publish"() { + given: + def publishOutHandler = new Qos1MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def session = user.session() + def testTopicName = defaultTopicService.createTopicName(user, "Qos1MqttPublishOutMessageHandlerTest/5") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos1MqttPublishOutMessageHandlerTest/5") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(publish, subscriber) + def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + then: + result == PublishHandlingResult.SUCCESS + with(session.outMessageTracker()) { + with(stored(receivedPublish.messageId())) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == null + } + } + with(session.outProcessingPublishes()) { + size() == 1 + } + when: 'chane trackable info to publish release and send publish ack' + session + .outMessageTracker() + .update(receivedPublish.messageId(), MqttMessageType.PUBLISH_RELEASE, PublishReleaseReasonCode.SUCCESS) + def publishAck = PublishAckMqttInMessage + .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishAck) + then: + with(session.outMessageTracker()) { + stored(receivedPublish.messageId()) == null + } + with(session.outProcessingPublishes()) { + size() == 0 + } + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java b/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java index 6b51de39..f59e317f 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java @@ -23,7 +23,11 @@ public interface MqttProtocolErrors { String MISSED_REQUIRED_MESSAGE_ID = "'Packet Identifier' must be presented'"; String NOT_EXPECTED_MESSAGE_ID = "'Packet Identifier' must be zero'"; String INVALID_SUBSCRIPTION_ID = "Provided invalid 'Subscription Identifier'"; + String PROTOCOL_LEVEL_UNSUPPORTED_NO_LOCAL_OPTION = "'No Local' option is not available on this protocol level"; String PROTOCOL_LEVEL_UNSUPPORTED_RETAIN_AS_PUBLISH_OPTION = "'Retain As Published' option is not available on this protocol level"; String PROTOCOL_LEVEL_UNSUPPORTED_RETAIN_HANDLING_OPTION = "'Retain Handling' option is not available on this protocol level"; + + String UNEXPECTED_FLOW_STATE = "Unexpected flow state:'%s', expected:'%s'"; + String UNEXPECTED_RESPONSE_MESSAGE = "Unexpected response packet:'%s', expected:'%s'"; } diff --git a/model/src/main/java/javasabr/mqtt/model/session/ProcessingPublishes.java b/model/src/main/java/javasabr/mqtt/model/session/ProcessingPublishes.java index 65fb722f..cd0523c5 100644 --- a/model/src/main/java/javasabr/mqtt/model/session/ProcessingPublishes.java +++ b/model/src/main/java/javasabr/mqtt/model/session/ProcessingPublishes.java @@ -17,4 +17,6 @@ public interface ProcessingPublishes { * @return true if was found some callback for this message */ boolean remove(TrackableMqttMessage message); + + int size(); } 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 902ac5c7..9962f73a 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 @@ -64,4 +64,11 @@ protected PublishAckReasonCode readReasonCode(int unsignedByte) { protected Set availableProperties() { return AVAILABLE_PROPERTIES; } + + public static PublishAckMqttInMessage of(int messageId, PublishAckReasonCode reasonCode) { + var message = new PublishAckMqttInMessage(MESSAGE_FLAGS); + message.messageId = messageId; + message.reasonCode = reasonCode; + return message; + } } 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 d25cfd98..b222fb22 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 @@ -64,4 +64,11 @@ protected PublishReceivedReasonCode readReasonCode(int unsignedByte) { protected Set availableProperties() { return AVAILABLE_PROPERTIES; } + + public static PublishReceivedMqttInMessage of(int messageId, PublishReceivedReasonCode reasonCode) { + var message = new PublishReceivedMqttInMessage(MESSAGE_FLAGS); + message.messageId = messageId; + message.reasonCode = reasonCode; + return message; + } } From bfc3ea8f5894b2c5d94772edef1690df675e6ce0 Mon Sep 17 00:00:00 2001 From: javasabr Date: Thu, 4 Dec 2025 19:15:59 +0100 Subject: [PATCH 08/19] resolve comments --- .../config/MqttBrokerSpringConfig.java | 4 ++-- .../Qos2MqttPublishOutMessageHandler.java | 7 ++++--- ...TrackableMqttPublishOutMessageHandler.java | 19 ++++++++++--------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index fe7550f0..731fb107 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -319,7 +319,7 @@ MqttServerConnectionConfig externalConnectionConfig(Environment env) { env.getProperty( "mqtt.external.connection.retain.available", boolean.class, - false), // not implemented + false), // set false because currently it's not implemented and we should not allow for clients to use it env.getProperty( "mqtt.external.connection.wildcard.subscription.available", boolean.class, @@ -327,7 +327,7 @@ MqttServerConnectionConfig externalConnectionConfig(Environment env) { env.getProperty( "mqtt.external.connection.subscription.id.available", boolean.class, - false), // not implemented + false), // set false because currently it's not implemented and we should not allow for clients to use it env.getProperty( "mqtt.external.connection.shared.subscription.available", boolean.class, diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java index fe42e67d..049ed3bc 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java @@ -42,10 +42,11 @@ protected boolean handleReceivedTrackableMessageImpl( } else if (message instanceof PublishCompleteMqttInMessage publishComplete) { handlePublishComplete(user, session, message, trackedMessageMeta, publishComplete); return true; + } else { + log.warning(user.clientId(), message.messageType(), message.messageId(), + "[%s] Not expected message type:[%s] for messageId:[%d]"::formatted); + return true; } - log.warning(user.clientId(), message.messageType(), message.messageId(), - "[%s] Not expected message type:[%s] for messageId:[%d]"::formatted); - return true; } private boolean handlePublishRelease( diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java index c038db54..2de19ee6 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java @@ -85,18 +85,19 @@ protected void retryDelivering(MqttUser user, MqttSession session, Publish publi } protected void retryDeliveringImpl(ExternalNetworkMqttUser user, MqttSession session, Publish publish) { + String clientId = user.clientId(); int messageId = publish.messageId(); - MessageTacker messageTacker = session.outMessageTracker(); - TrackedMessageMeta messageMeta = messageTacker.stored(messageId); + TrackedMessageMeta messageMeta = session + .outMessageTracker() + .stored(messageId); if (messageMeta == null) { - log.warning(user.clientId(), messageId, "[%s] No any stored information for messageId:[%d]"::formatted); - return; - } else if(messageMeta.messageType() != MqttMessageType.PUBLISH) { - log.warning(user.clientId(), messageMeta, messageId, + log.warning(clientId, messageId, "[%s] No any stored information for messageId:[%d]"::formatted); + } else if (messageMeta.messageType() != MqttMessageType.PUBLISH) { + log.warning(clientId, messageMeta, messageId, "[%s] Not expected tracked message meta:[%s] for messageId:[%d]"::formatted); - return; + } else { + log.debug(clientId, messageId, "[%s] Retry to deliver publish:[%s]"::formatted); + send(user, publish.withDuplicated()); } - log.debug(user.clientId(), messageId, "[%s] Retry to deliver publish:[%s]"::formatted); - send(user, publish.withDuplicated()); } } From f8752d0f849a5c06bb40cc5f9c98df5c4a8e2c39 Mon Sep 17 00:00:00 2001 From: javasabr Date: Fri, 5 Dec 2025 18:34:02 +0100 Subject: [PATCH 09/19] continuer add tests --- .../Qos2MqttPublishOutMessageHandler.java | 58 ++-- ...os1MqttPublishOutMessageHandlerTest.groovy | 33 ++- ...os2MqttPublishOutMessageHandlerTest.groovy | 262 ++++++++++++++++++ .../in/PublishCompleteMqttInMessage.java | 7 + .../out/PublishReleaseMqtt311OutMessage.java | 12 +- .../out/PublishReleaseMqtt5OutMessage.java | 4 + 6 files changed, 335 insertions(+), 41 deletions(-) create mode 100644 core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java index 049ed3bc..73613622 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java @@ -38,18 +38,22 @@ protected boolean handleReceivedTrackableMessageImpl( TrackableMqttMessage message, @Nullable TrackedMessageMeta trackedMessageMeta) { if (message instanceof PublishReceivedMqttInMessage publishReceived) { - return handlePublishRelease(user, session, message, trackedMessageMeta, publishReceived); + return handlePublishReceive(user, session, message, trackedMessageMeta, publishReceived); } else if (message instanceof PublishCompleteMqttInMessage publishComplete) { handlePublishComplete(user, session, message, trackedMessageMeta, publishComplete); return true; } else { log.warning(user.clientId(), message.messageType(), message.messageId(), "[%s] Not expected message type:[%s] for messageId:[%d]"::formatted); + handleNotExpectedResponseMessage(user, message, MqttMessageType.PUBLISH_RECEIVED); return true; } } - private boolean handlePublishRelease( + /** + * @return true if need to cancel the flow + */ + private boolean handlePublishReceive( ExternalNetworkMqttUser user, MqttSession session, TrackableMqttMessage message, @@ -58,39 +62,45 @@ private boolean handlePublishRelease( int messageId = message.messageId(); String clientId = user.clientId(); - if (trackedMessageMeta != null && trackedMessageMeta.messageType() != MqttMessageType.PUBLISH) { + PublishReceivedReasonCode reasonCode = publishReceived.reasonCode(); + + // if we unknown this flow + if (trackedMessageMeta == null) { + log.warning(clientId, messageId, "[%s] No any stored information for messageId:[%d]"::formatted); + // for success reason code we should answer that we don't know what this flow + if (reasonCode == PublishReceivedReasonCode.SUCCESS) { + user.sendInBackground(messageOutFactoryService + .resolveFactory(user) + .newPublishRelease(messageId, PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND)); + } + return true; + } + + MqttMessageType trackedMessageType = trackedMessageMeta.messageType(); + if (trackedMessageType != MqttMessageType.PUBLISH) { log.warning(clientId, trackedMessageMeta, messageId, "[%s] No expected message meta:[%s] for messageId:[%d]"::formatted); + handleNotExpectedFlowState(user, trackedMessageType, MqttMessageType.PUBLISH); return true; } MessageTacker messageTacker = session.outMessageTracker(); - PublishReceivedReasonCode reasonCode = publishReceived.reasonCode(); if (reasonCode != PublishReceivedReasonCode.SUCCESS) { log.warning(clientId, reasonCode, messageId, "[%s] Received error response:[%s] for publish:[%s]"::formatted); // we can cancel the flow - if (trackedMessageMeta != null) { - messageTacker.remove(messageId); - } + messageTacker.remove(messageId); return true; } - PublishReleaseReasonCode releaseResult; - // we unknown this flow - if (trackedMessageMeta == null) { - releaseResult = PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND; - } else { - releaseResult = PublishReleaseReasonCode.SUCCESS; - messageTacker.update(messageId, MqttMessageType.PUBLISH_RELEASE, reasonCode); - } - + // switch flow from publish to release phase + messageTacker.update(messageId, MqttMessageType.PUBLISH_RELEASE, reasonCode); + + // completed this phase user.sendInBackground(messageOutFactoryService .resolveFactory(user) - .newPublishRelease(messageId, releaseResult)); - - // cancel this flow if it's not success - return releaseResult != PublishReleaseReasonCode.SUCCESS; + .newPublishRelease(messageId, PublishReleaseReasonCode.SUCCESS)); + return false; } private void handlePublishComplete( @@ -102,12 +112,18 @@ private void handlePublishComplete( int messageId = message.messageId(); String clientId = user.clientId(); + + // if we unknown this flow if (trackedMessageMeta == null) { log.warning(clientId, messageId, "[%s] No any stored information for messageId:[%d]"::formatted); return; - } else if (trackedMessageMeta.messageType() != MqttMessageType.PUBLISH_RELEASE) { + } + + MqttMessageType trackedMessageType = trackedMessageMeta.messageType(); + if (trackedMessageType != MqttMessageType.PUBLISH_RELEASE) { log.warning(clientId, trackedMessageMeta, messageId, "[%s] No expected message meta:[%s] for messageId:[%d]"::formatted); + handleNotExpectedFlowState(user, trackedMessageType, MqttMessageType.PUBLISH_RELEASE); return; } diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy index 1862df25..ac561140 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy @@ -5,6 +5,7 @@ import javasabr.mqtt.model.MqttVersion import javasabr.mqtt.model.QoS import javasabr.mqtt.model.message.MqttMessageType import javasabr.mqtt.model.publishing.Publish +import javasabr.mqtt.model.reason.code.DisconnectReasonCode import javasabr.mqtt.model.reason.code.PublishAckReasonCode import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode @@ -12,8 +13,8 @@ import javasabr.mqtt.model.subscriber.SingleSubscriber import javasabr.mqtt.model.subscription.Subscription import javasabr.mqtt.network.message.in.PublishAckMqttInMessage import javasabr.mqtt.network.message.in.PublishReceivedMqttInMessage +import javasabr.mqtt.network.message.out.DisconnectMqtt5OutMessage import javasabr.mqtt.network.message.out.PublishMqtt5OutMessage -import javasabr.mqtt.network.session.NetworkMqttSession import javasabr.mqtt.service.TestExternalNetworkMqttUser import javasabr.mqtt.service.publish.handler.PublishHandlingResult @@ -89,6 +90,10 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl with(session.outProcessingPublishes()) { size() == 0 } + then: 'user should not receive any new message' + with(user) { + isEmpty() + } } def "should correctly handle publish ack when no stored trackable meta about the publish"() { @@ -136,9 +141,13 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl with(session.outProcessingPublishes()) { size() == 0 } + then: 'user should not receive any new message' + with(user) { + isEmpty() + } } - def "should correctly handle unexpected message as response for publish"() { + def "should handle as protocol error receiving unexpected response message"() { given: def publishOutHandler = new Qos1MqttPublishOutMessageHandler( defaultSubscriptionService, @@ -167,22 +176,20 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl with(session.outProcessingPublishes()) { size() == 1 } - when: 'send unexpected publish receive' + when: 'send unexpected publish receive to get protocol error' def publishReceive = PublishReceivedMqttInMessage .of(receivedPublish.messageId(), PublishReceivedReasonCode.SUCCESS) session .outProcessingPublishes() .apply(user, publishReceive) then: - with(session.outMessageTracker()) { - stored(receivedPublish.messageId()) == null - } - with(session.outProcessingPublishes()) { - size() == 0 + with(user.nextSentMessage(DisconnectMqtt5OutMessage)) { + reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR + reason() == "Unexpected response packet:'$MqttMessageType.PUBLISH_RECEIVED', expected:'$MqttMessageType.PUBLISH_ACK'" } } - def "should correctly handle publish ack when unexpected stored trackable meta about the publish"() { + def "should handle as protocol error for unexpected flow state"() { given: def publishOutHandler = new Qos1MqttPublishOutMessageHandler( defaultSubscriptionService, @@ -221,11 +228,9 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl .outProcessingPublishes() .apply(user, publishAck) then: - with(session.outMessageTracker()) { - stored(receivedPublish.messageId()) == null - } - with(session.outProcessingPublishes()) { - size() == 0 + with(user.nextSentMessage(DisconnectMqtt5OutMessage)) { + reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR + reason() == "Unexpected flow state:'$MqttMessageType.PUBLISH_RELEASE', expected:'$MqttMessageType.PUBLISH'" } } } diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy new file mode 100644 index 00000000..2ebc2385 --- /dev/null +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy @@ -0,0 +1,262 @@ +package javasabr.mqtt.service.publish.handler.impl + +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.MqttVersion +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.message.MqttMessageType +import javasabr.mqtt.model.publishing.Publish +import javasabr.mqtt.model.reason.code.DisconnectReasonCode +import javasabr.mqtt.model.reason.code.PublishAckReasonCode +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode +import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode +import javasabr.mqtt.model.subscriber.SingleSubscriber +import javasabr.mqtt.model.subscription.Subscription +import javasabr.mqtt.network.message.in.PublishAckMqttInMessage +import javasabr.mqtt.network.message.in.PublishCompleteMqttInMessage +import javasabr.mqtt.network.message.in.PublishReceivedMqttInMessage +import javasabr.mqtt.network.message.out.DisconnectMqtt5OutMessage +import javasabr.mqtt.network.message.out.PublishMqtt5OutMessage +import javasabr.mqtt.network.message.out.PublishReleaseMqtt5OutMessage +import javasabr.mqtt.service.TestExternalNetworkMqttUser +import javasabr.mqtt.service.publish.handler.PublishHandlingResult + +class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandlerTest { + + def "should deliver publish to subscriber"() { + given: + def publishOutHandler = new Qos2MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def testTopicName = defaultTopicService.createTopicName(user, "Qos2MqttPublishOutMessageHandlerTest/1") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos2MqttPublishOutMessageHandlerTest/1") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(testPublish, subscriber) + then: + result == PublishHandlingResult.SUCCESS + with(user.nextSentMessage(PublishMqtt5OutMessage)) { + qos() == QoS.EXACTLY_ONCE + !duplicate() + payload() == testPayload + topicName() == testTopicName + messageId() != MqttProperties.MESSAGE_ID_IS_NOT_SET + topicAlias() == MqttProperties.TOPIC_ALIAS_NOT_SET + } + } + + def "should wait for receive-complete responses for publish"() { + given: + def publishOutHandler = new Qos2MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def session = user.session() + def testTopicName = defaultTopicService.createTopicName(user, "Qos2MqttPublishOutMessageHandlerTest/2") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos2MqttPublishOutMessageHandlerTest/2") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(testPublish, subscriber) + def publish = user.nextSentMessage(PublishMqtt5OutMessage) + then: + result == PublishHandlingResult.SUCCESS + with(session.outMessageTracker()) { + with(stored(publish.messageId())) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == null + } + } + with(session.outProcessingPublishes()) { + size() == 1 + } + when: 'send publish receive' + def publishReceived = PublishReceivedMqttInMessage + .of(publish.messageId(), PublishReceivedReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishReceived) + then: + with(session.outMessageTracker()) { + with(stored(publish.messageId())) { + messageType() == MqttMessageType.PUBLISH_RELEASE + reasonCode() == PublishReceivedReasonCode.SUCCESS + } + } + with(session.outProcessingPublishes()) { + size() == 1 + } + when: 'user should receive publish release' + def publishRelease = user.nextSentMessage(PublishReleaseMqtt5OutMessage) + then: + with(publishRelease) { + reasonCode() == PublishReleaseReasonCode.SUCCESS + messageId() == publish.messageId() + } + when: 'send publish complete' + def publishComplete = PublishCompleteMqttInMessage + .of(publish.messageId(), PublishCompletedReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishComplete) + then: + with(session.outMessageTracker()) { + stored(publish.messageId()) == null + } + with(session.outProcessingPublishes()) { + size() == 0 + } + then: + with(user) { + isEmpty() + } + } + + def "should correctly handle publish ack when no stored trackable meta about the publish"() { + given: + def publishOutHandler = new Qos2MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def session = user.session() + def testTopicName = defaultTopicService.createTopicName(user, "Qos2MqttPublishOutMessageHandlerTest/3") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos2MqttPublishOutMessageHandlerTest/3") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(publish, subscriber) + def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + then: + result == PublishHandlingResult.SUCCESS + with(session.outMessageTracker()) { + with(stored(receivedPublish.messageId())) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == null + } + } + with(session.outProcessingPublishes()) { + size() == 1 + } + when: 'remove trackable info and send publish ack' + session + .outMessageTracker() + .remove(receivedPublish.messageId()) + def publishAck = PublishAckMqttInMessage + .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishAck) + then: + with(session.outMessageTracker()) { + stored(receivedPublish.messageId()) == null + } + with(session.outProcessingPublishes()) { + size() == 0 + } + then: 'user should not receive any new message' + with(user) { + isEmpty() + } + } + + def "should handle as protocol error receiving unexpected response message"() { + given: + def publishOutHandler = new Qos2MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def session = user.session() + def testTopicName = defaultTopicService.createTopicName(user, "Qos2MqttPublishOutMessageHandlerTest/4") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos2MqttPublishOutMessageHandlerTest/4") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(publish, subscriber) + def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + then: + result == PublishHandlingResult.SUCCESS + with(session.outMessageTracker()) { + with(stored(receivedPublish.messageId())) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == null + } + } + with(session.outProcessingPublishes()) { + size() == 1 + } + when: 'send unexpected publish receive to get protocol error' + def publishAck = PublishAckMqttInMessage + .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishAck) + then: + with(user.nextSentMessage(DisconnectMqtt5OutMessage)) { + reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR + reason() == "Unexpected response packet:'$MqttMessageType.PUBLISH_ACK', expected:'$MqttMessageType.PUBLISH_RECEIVED'" + } + } + + def "should handle as protocol error for unexpected flow state"() { + given: + def publishOutHandler = new Qos2MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def session = user.session() + def testTopicName = defaultTopicService.createTopicName(user, "Qos2MqttPublishOutMessageHandlerTest/5") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos2MqttPublishOutMessageHandlerTest/5") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(publish, subscriber) + def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + then: + result == PublishHandlingResult.SUCCESS + with(session.outMessageTracker()) { + with(stored(receivedPublish.messageId())) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == null + } + } + with(session.outProcessingPublishes()) { + size() == 1 + } + when: 'chane trackable info to publish release and send publish ack' + session + .outMessageTracker() + .update(receivedPublish.messageId(), MqttMessageType.PUBLISH_RELEASE, PublishReleaseReasonCode.SUCCESS) + def publishAck = PublishAckMqttInMessage + .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishAck) + then: + with(user.nextSentMessage(DisconnectMqtt5OutMessage)) { + reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR + reason() == "Unexpected flow state:'$MqttMessageType.PUBLISH_RELEASE', expected:'$MqttMessageType.PUBLISH'" + } + } +} 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 3827f2ff..9489e07f 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 @@ -64,4 +64,11 @@ protected PublishCompletedReasonCode readReasonCode(int unsignedByte) { protected Set availableProperties() { return AVAILABLE_PROPERTIES; } + + public static PublishCompleteMqttInMessage of(int messageId, PublishCompletedReasonCode reasonCode) { + var message = new PublishCompleteMqttInMessage(MESSAGE_FLAGS); + message.messageId = messageId; + message.reasonCode = reasonCode; + return message; + } } 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 0b607726..a91d29f8 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 @@ -4,19 +4,20 @@ import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.network.MqttConnection; import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; /** * Publish release (QoS 2 delivery part 2). */ -@RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public class PublishReleaseMqtt311OutMessage extends MqttOutMessage { +public class PublishReleaseMqtt311OutMessage extends TrackableMqttOutMessage { + public static final int MESSAGE_FLAGS = 0b0000_0010; private static final byte MESSAGE_TYPE = (byte) MqttMessageType.PUBLISH_RELEASE.ordinal(); - int messageId; + public PublishReleaseMqtt311OutMessage(int messageId) { + super(messageId); + } @Override protected byte messageTypeId() { @@ -30,7 +31,7 @@ public MqttMessageType messageType() { @Override protected byte messageFlags() { - return 0b0000_0010; + return MESSAGE_FLAGS; } @Override @@ -43,5 +44,4 @@ protected void writeVariableHeader(MqttConnection connection, ByteBuffer buffer) // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718055 writeShort(buffer, messageId); } - } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt5OutMessage.java index ae70d9f5..9ac76f77 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishReleaseMqtt5OutMessage.java @@ -9,11 +9,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; /** * Publish release (QoS 2 delivery part 2). */ +@Getter +@Accessors @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class PublishReleaseMqtt5OutMessage extends PublishReleaseMqtt311OutMessage { From 1164f82be7f5ffaa1a8fdd2431a67655368b9344 Mon Sep 17 00:00:00 2001 From: javasabr Date: Fri, 5 Dec 2025 19:13:43 +0100 Subject: [PATCH 10/19] finish adding tests --- .../Qos2MqttPublishOutMessageHandler.java | 6 +- ...TrackableMqttPublishOutMessageHandler.java | 1 - ...os0MqttPublishOutMessageHandlerTest.groovy | 4 +- ...Qos1MqttPublishInMessageHandlerTest.groovy | 60 +++--- ...os1MqttPublishOutMessageHandlerTest.groovy | 2 +- ...Qos2MqttPublishInMessageHandlerTest.groovy | 191 +++++++++--------- ...os2MqttPublishOutMessageHandlerTest.groovy | 156 +++++++++++--- .../in/PublishReleaseMqttInMessage.java | 7 + 8 files changed, 273 insertions(+), 154 deletions(-) diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java index 73613622..be50d50d 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java @@ -43,9 +43,13 @@ protected boolean handleReceivedTrackableMessageImpl( handlePublishComplete(user, session, message, trackedMessageMeta, publishComplete); return true; } else { + MqttMessageType expectedMessageType = MqttMessageType.PUBLISH_RECEIVED; + if (trackedMessageMeta != null && trackedMessageMeta.messageType() == MqttMessageType.PUBLISH_RELEASE) { + expectedMessageType = MqttMessageType.PUBLISH_COMPLETE; + } log.warning(user.clientId(), message.messageType(), message.messageId(), "[%s] Not expected message type:[%s] for messageId:[%d]"::formatted); - handleNotExpectedResponseMessage(user, message, MqttMessageType.PUBLISH_RECEIVED); + handleNotExpectedResponseMessage(user, message, expectedMessageType); return true; } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java index a667e778..7977f9a7 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java @@ -14,7 +14,6 @@ import javasabr.mqtt.model.session.TrackableMessageCallback; import javasabr.mqtt.model.session.TrackedMessageMeta; import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; -import javasabr.mqtt.network.message.out.MqttOutMessage; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.message.out.factory.MqttMessageOutFactory; diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy index 1d23a427..2781da71 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy @@ -24,10 +24,10 @@ class Qos0MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) def subscriber = new SingleSubscriber(user, subscription) def originalMessageId = 60 - def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(publish, subscriber) + def result = publishOutHandler.handle(testPublish, subscriber) then: result == PublishHandlingResult.SUCCESS with(user.nextSentMessage(PublishMqtt5OutMessage)) { diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishInMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishInMessageHandlerTest.groovy index 1f69fba0..d86426e6 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishInMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishInMessageHandlerTest.groovy @@ -31,7 +31,7 @@ class Qos1MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler def client2 = subscriber2.user() as TestExternalNetworkMqttUser def client3 = publisher.user() as TestExternalNetworkMqttUser def topicFilter = defaultTopicService.createTopicFilter(client1, "Qos1MqttPublishInMessageHandlerTest/1") - def topicName = defaultTopicService.createTopicName(client1, "Qos1MqttPublishInMessageHandlerTest/1") + def expectedTopicName = defaultTopicService.createTopicName(client1, "Qos1MqttPublishInMessageHandlerTest/1") def expectedMessageId = 35 defaultSubscriptionService.subscribe( client1, @@ -45,19 +45,22 @@ class Qos1MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler .session() .inMessageTracker() when: - publishInHandler.handle(client3, Publish.minimal(expectedMessageId, QoS.AT_MOST_ONCE, topicName, testPayload)) + publishInHandler.handle(client3, Publish.minimal(expectedMessageId, QoS.AT_MOST_ONCE, expectedTopicName, testPayload)) then: 'sender should have feedback' - def publishAck = client3.nextSentMessage(PublishAckMqtt5OutMessage) - publishAck.reasonCode() == PublishAckReasonCode.SUCCESS - publishAck.messageId() == expectedMessageId - publishAck.reason() == null - publishAck.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + with(client3.nextSentMessage(PublishAckMqtt5OutMessage)) { + reasonCode() == PublishAckReasonCode.SUCCESS + messageId() == expectedMessageId + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } inMessageTracker.stored(expectedMessageId) == null then: 'subscribers should receive the publish' - def message1 = client1.nextSentMessage(PublishMqtt5OutMessage) - message1.topicName() == topicName - def message2 = client2.nextSentMessage(PublishMqtt5OutMessage) - message2.topicName() == topicName + with(client1.nextSentMessage(PublishMqtt5OutMessage)) { + topicName() == expectedTopicName + } + with(client2.nextSentMessage(PublishMqtt5OutMessage)) { + topicName() == expectedTopicName + } } def "should provide feedback for accepted publish without any subscriber"() { @@ -75,11 +78,12 @@ class Qos1MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler when: publishInHandler.handle(user, Publish.minimal(expectedMessageId, QoS.AT_MOST_ONCE, topicName, testPayload)) then: 'sender should have feedback that no matched subscribers' - def publishAck = user.nextSentMessage(PublishAckMqtt5OutMessage) - publishAck.reasonCode() == PublishAckReasonCode.NO_MATCHING_SUBSCRIBERS - publishAck.messageId() == expectedMessageId - publishAck.reason() == null - publishAck.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + with(user.nextSentMessage(PublishAckMqtt5OutMessage)) { + reasonCode() == PublishAckReasonCode.NO_MATCHING_SUBSCRIBERS + messageId() == expectedMessageId + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } inMessageTracker.stored(expectedMessageId) == null } @@ -99,10 +103,11 @@ class Qos1MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler topicName, testPayload)) then: - def disconnect = user.nextSentMessage(DisconnectMqtt5OutMessage) - disconnect.reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR - disconnect.reason() == MqttProtocolErrors.MISSED_REQUIRED_MESSAGE_ID - disconnect.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + with(user.nextSentMessage(DisconnectMqtt5OutMessage)) { + reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR + reason() == MqttProtocolErrors.MISSED_REQUIRED_MESSAGE_ID + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } } def "should provide feedback that message id is already used"() { @@ -121,13 +126,14 @@ class Qos1MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler when: publishInHandler.handle(user, Publish.minimal(expectedMessageId, QoS.AT_MOST_ONCE, topicName, testPayload)) then: - def publishAck = user.nextSentMessage(PublishAckMqtt5OutMessage) - publishAck.reasonCode() == PublishAckReasonCode.PACKET_IDENTIFIER_IN_USE - publishAck.reason() == null - publishAck.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - def trackedMessageMeta = inMessageTracker.stored(expectedMessageId) - trackedMessageMeta != null - trackedMessageMeta.messageType() == MqttMessageType.SUBSCRIBE + with(user.nextSentMessage(PublishAckMqtt5OutMessage)) { + reasonCode() == PublishAckReasonCode.PACKET_IDENTIFIER_IN_USE + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } + with(inMessageTracker.stored(expectedMessageId)) { + messageType() == MqttMessageType.SUBSCRIBE + } } def "should skip handling duplicated publish"() { diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy index ac561140..d1adf56a 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy @@ -218,7 +218,7 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl with(session.outProcessingPublishes()) { size() == 1 } - when: 'chane trackable info to publish release and send publish ack' + when: 'change trackable info to publish release and send publish ack' session .outMessageTracker() .update(receivedPublish.messageId(), MqttMessageType.PUBLISH_RELEASE, PublishReleaseReasonCode.SUCCESS) diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandlerTest.groovy index c185f68d..fdae20e6 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandlerTest.groovy @@ -35,7 +35,7 @@ class Qos2MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler def client2 = subscriber2.user() as TestExternalNetworkMqttUser def client3 = publisher.user() as TestExternalNetworkMqttUser def topicFilter = defaultTopicService.createTopicFilter(client1, "Qos2MqttPublishInMessageHandlerTest/1") - def topicName = defaultTopicService.createTopicName(client1, "Qos2MqttPublishInMessageHandlerTest/1") + def expectedTopicName = defaultTopicService.createTopicName(client1, "Qos2MqttPublishInMessageHandlerTest/1") def expectedMessageId = 35 defaultSubscriptionService.subscribe( client1, @@ -49,34 +49,36 @@ class Qos2MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler .session() .inMessageTracker() when: - publishInHandler.handle(client3, Publish.minimal(expectedMessageId, QoS.EXACTLY_ONCE, topicName, testPayload)) + publishInHandler.handle(client3, Publish.minimal(expectedMessageId, QoS.EXACTLY_ONCE, expectedTopicName, testPayload)) then: 'sender should have feedback of first phase' - def publishReceive = client3.nextSentMessage(PublishReceivedMqtt5OutMessage) - publishReceive.reasonCode() == PublishReceivedReasonCode.SUCCESS - publishReceive.messageId() == expectedMessageId - publishReceive.reason() == null - publishReceive.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - def trackedMessageMeta = inMessageTracker.stored(expectedMessageId) - trackedMessageMeta != null - trackedMessageMeta.messageType() == MqttMessageType.PUBLISH - trackedMessageMeta.reasonCode() == PublishReceivedReasonCode.SUCCESS + with(client3.nextSentMessage(PublishReceivedMqtt5OutMessage)) { + reasonCode() == PublishReceivedReasonCode.SUCCESS + messageId() == expectedMessageId + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } + with(inMessageTracker.stored(expectedMessageId)) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == PublishReceivedReasonCode.SUCCESS + } then: 'subscribers should receive the publish' - def message1 = client1.nextSentMessage(PublishMqtt5OutMessage) - message1.topicName() == topicName - def message2 = client2.nextSentMessage(PublishMqtt5OutMessage) - message2.topicName() == topicName + with(client1.nextSentMessage(PublishMqtt5OutMessage)) { + topicName() == expectedTopicName + } + with(client2.nextSentMessage(PublishMqtt5OutMessage)) { + topicName() == expectedTopicName + } when: - def publishRelease = new PublishReleaseMqttInMessage(0b0000_0010 as byte) {{ - messageId = expectedMessageId - reasonCode = PublishReleaseReasonCode.SUCCESS - }} + def publishRelease = PublishReleaseMqttInMessage + .of(expectedMessageId, PublishReleaseReasonCode.SUCCESS) defaultPublishReleaseMqttInMessageHandler.processValidMessage(publisher, publishRelease) then: - def publishComplete = client3.nextSentMessage(PublishCompleteMqtt5OutMessage) - publishComplete.reasonCode() == PublishCompletedReasonCode.SUCCESS - publishComplete.messageId() == expectedMessageId - publishComplete.reason() == null - publishComplete.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + with(client3.nextSentMessage(PublishCompleteMqtt5OutMessage)) { + reasonCode() == PublishCompletedReasonCode.SUCCESS + messageId() == expectedMessageId + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } inMessageTracker.stored(expectedMessageId) == null } @@ -95,27 +97,27 @@ class Qos2MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler when: publishInHandler.handle(user, Publish.minimal(expectedMessageId, QoS.EXACTLY_ONCE, topicName, testPayload)) then: 'sender should have feedback that no matched subscribers' - def publishReceive = user.nextSentMessage(PublishReceivedMqtt5OutMessage) - publishReceive.reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS - publishReceive.messageId() == expectedMessageId - publishReceive.reason() == null - publishReceive.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - def trackedMessageMeta = inMessageTracker.stored(expectedMessageId) - trackedMessageMeta != null - trackedMessageMeta.messageType() == MqttMessageType.PUBLISH - trackedMessageMeta.reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS + with(user.nextSentMessage(PublishReceivedMqtt5OutMessage)) { + reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS + messageId() == expectedMessageId + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } + with(inMessageTracker.stored(expectedMessageId)) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS + } when: - def publishRelease = new PublishReleaseMqttInMessage(0b0000_0010 as byte) {{ - messageId = expectedMessageId - reasonCode = PublishReleaseReasonCode.SUCCESS - }} + def publishRelease = PublishReleaseMqttInMessage + .of(expectedMessageId, PublishReleaseReasonCode.SUCCESS) defaultPublishReleaseMqttInMessageHandler.processValidMessage(publisher, publishRelease) then: - def publishComplete = user.nextSentMessage(PublishCompleteMqtt5OutMessage) - publishComplete.reasonCode() == PublishCompletedReasonCode.SUCCESS - publishComplete.messageId() == expectedMessageId - publishComplete.reason() == null - publishComplete.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + with(user.nextSentMessage(PublishCompleteMqtt5OutMessage)) { + reasonCode() == PublishCompletedReasonCode.SUCCESS + messageId() == expectedMessageId + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } inMessageTracker.stored(expectedMessageId) == null } @@ -135,10 +137,11 @@ class Qos2MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler topicName, testPayload)) then: - def disconnect = user.nextSentMessage(DisconnectMqtt5OutMessage) - disconnect.reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR - disconnect.reason() == MqttProtocolErrors.MISSED_REQUIRED_MESSAGE_ID - disconnect.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + with(user.nextSentMessage(DisconnectMqtt5OutMessage)) { + reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR + reason() == MqttProtocolErrors.MISSED_REQUIRED_MESSAGE_ID + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } } def "should provide feedback that message id is already used"() { @@ -157,13 +160,14 @@ class Qos2MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler when: publishInHandler.handle(user, Publish.minimal(expectedMessageId, QoS.EXACTLY_ONCE, topicName, testPayload)) then: - def publishReceive = user.nextSentMessage(PublishReceivedMqtt5OutMessage) - publishReceive.reasonCode() == PublishReceivedReasonCode.PACKET_IDENTIFIER_IN_USE - publishReceive.reason() == null - publishReceive.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - def trackedMessageMeta = inMessageTracker.stored(expectedMessageId) - trackedMessageMeta != null - trackedMessageMeta.messageType() == MqttMessageType.SUBSCRIBE + with(user.nextSentMessage(PublishReceivedMqtt5OutMessage)) { + reasonCode() == PublishReceivedReasonCode.PACKET_IDENTIFIER_IN_USE + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } + with(inMessageTracker.stored(expectedMessageId)) { + messageType() == MqttMessageType.SUBSCRIBE + } } def "should provide feedback for duplicated publish as well"() { @@ -184,13 +188,14 @@ class Qos2MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler .minimal(expectedMessageId, QoS.EXACTLY_ONCE, topicName, testPayload) .withDuplicated()) then: - def publishReceive = user.nextSentMessage(PublishReceivedMqtt5OutMessage) - publishReceive.reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS - publishReceive.reason() == null - publishReceive.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - def trackedMessageMeta = inMessageTracker.stored(expectedMessageId) - trackedMessageMeta != null - trackedMessageMeta.messageType() == MqttMessageType.PUBLISH + with(user.nextSentMessage(PublishReceivedMqtt5OutMessage)) { + reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } + with(inMessageTracker.stored(expectedMessageId)) { + messageType() == MqttMessageType.PUBLISH + } } def "should not provide feedback for duplicated publish after accepting publish release"() { @@ -208,51 +213,53 @@ class Qos2MqttPublishInMessageHandlerTest extends QosMqttPublishInMessageHandler when: 'init floy by original publish' publishInHandler.handle(user, Publish.minimal(expectedMessageId, QoS.EXACTLY_ONCE, topicName, testPayload)) then: - def publishReceive = user.nextSentMessage(PublishReceivedMqtt5OutMessage) - publishReceive.reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS - publishReceive.reason() == null - publishReceive.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - def trackedMessageMeta = inMessageTracker.stored(expectedMessageId) - trackedMessageMeta != null - trackedMessageMeta.messageType() == MqttMessageType.PUBLISH - trackedMessageMeta.reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS + with(user.nextSentMessage(PublishReceivedMqtt5OutMessage)) { + reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } + with(inMessageTracker.stored(expectedMessageId)) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS + } when: 'send duplicated before publish release' publishInHandler.handle(user, Publish .minimal(expectedMessageId, QoS.EXACTLY_ONCE, topicName, testPayload) .withDuplicated()) then: 'server should return the same feedback for duplicated as for original' - def publishReceive2 = user.nextSentMessage(PublishReceivedMqtt5OutMessage) - publishReceive2.reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS - publishReceive2.reason() == null - publishReceive2.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - def trackedMessageMeta2 = inMessageTracker.stored(expectedMessageId) - trackedMessageMeta2 != null - trackedMessageMeta2.messageType() == MqttMessageType.PUBLISH - trackedMessageMeta2.reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS + with(user.nextSentMessage(PublishReceivedMqtt5OutMessage)) { + reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } + with(inMessageTracker.stored(expectedMessageId)) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS + } when: 'send publish release to change flow stage' - def publishRelease = new PublishReleaseMqttInMessage(0b0000_0010 as byte) {{ - messageId = expectedMessageId - reasonCode = PublishReleaseReasonCode.SUCCESS - }} + def publishRelease = PublishReleaseMqttInMessage + .of(expectedMessageId, PublishReleaseReasonCode.SUCCESS) user.returnCompletedFeatures(false) defaultPublishReleaseMqttInMessageHandler.processValidMessage(publisher, publishRelease) then: - def publishComplete = user.nextSentMessage(PublishCompleteMqtt5OutMessage) - publishComplete.reasonCode() == PublishCompletedReasonCode.SUCCESS - publishComplete.messageId() == expectedMessageId - publishComplete.reason() == null - publishComplete.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - def trackedMessageMeta3 = inMessageTracker.stored(expectedMessageId) - trackedMessageMeta3 != null - trackedMessageMeta3.messageType() == MqttMessageType.PUBLISH_COMPLETE + with(user.nextSentMessage(PublishCompleteMqtt5OutMessage)) { + reasonCode() == PublishCompletedReasonCode.SUCCESS + messageId() == expectedMessageId + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } + with(inMessageTracker.stored(expectedMessageId)) { + messageType() == MqttMessageType.PUBLISH_COMPLETE + } when: 'send duplicated after publish release' publishInHandler.handle(user, Publish .minimal(expectedMessageId, QoS.EXACTLY_ONCE, topicName, testPayload) .withDuplicated()) then: 'server should return that this message id is already used because publish complete is in progress of sending' - def publishReceive3 = user.nextSentMessage(PublishReceivedMqtt5OutMessage) - publishReceive3.reasonCode() == PublishReceivedReasonCode.PACKET_IDENTIFIER_IN_USE - publishReceive3.reason() == null - publishReceive3.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + with(user.nextSentMessage(PublishReceivedMqtt5OutMessage)) { + reasonCode() == PublishReceivedReasonCode.PACKET_IDENTIFIER_IN_USE + reason() == null + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } } } diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy index 2ebc2385..d6d0ef56 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy @@ -80,7 +80,7 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl with(session.outProcessingPublishes()) { size() == 1 } - when: 'send publish receive' + when: 'send publish received' def publishReceived = PublishReceivedMqttInMessage .of(publish.messageId(), PublishReceivedReasonCode.SUCCESS) session @@ -122,7 +122,7 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl } } - def "should correctly handle publish ack when no stored trackable meta about the publish"() { + def "should correctly handle publish receive when no stored trackable meta about the publish"() { given: def publishOutHandler = new Qos2MqttPublishOutMessageHandler( defaultSubscriptionService, @@ -135,15 +135,15 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) def subscriber = new SingleSubscriber(user, subscription) def originalMessageId = 60 - def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(publish, subscriber) - def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + def result = publishOutHandler.handle(testPublish, subscriber) + def publish = user.nextSentMessage(PublishMqtt5OutMessage) then: result == PublishHandlingResult.SUCCESS with(session.outMessageTracker()) { - with(stored(receivedPublish.messageId())) { + with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH reasonCode() == null } @@ -154,26 +154,27 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl when: 'remove trackable info and send publish ack' session .outMessageTracker() - .remove(receivedPublish.messageId()) - def publishAck = PublishAckMqttInMessage - .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + .remove(publish.messageId()) + def publishReceive = PublishReceivedMqttInMessage + .of(publish.messageId(), PublishReceivedReasonCode.SUCCESS) session .outProcessingPublishes() - .apply(user, publishAck) + .apply(user, publishReceive) then: with(session.outMessageTracker()) { - stored(receivedPublish.messageId()) == null + stored(publish.messageId()) == null } with(session.outProcessingPublishes()) { size() == 0 } then: 'user should not receive any new message' - with(user) { - isEmpty() + with(user.nextSentMessage(PublishReleaseMqtt5OutMessage)) { + reasonCode() == PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND + messageId() == publish.messageId() } } - def "should handle as protocol error receiving unexpected response message"() { + def "should handle as protocol error receiving unexpected response message for first stage"() { given: def publishOutHandler = new Qos2MqttPublishOutMessageHandler( defaultSubscriptionService, @@ -186,15 +187,15 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) def subscriber = new SingleSubscriber(user, subscription) def originalMessageId = 60 - def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(publish, subscriber) - def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + def result = publishOutHandler.handle(testPublish, subscriber) + def publish = user.nextSentMessage(PublishMqtt5OutMessage) then: result == PublishHandlingResult.SUCCESS with(session.outMessageTracker()) { - with(stored(receivedPublish.messageId())) { + with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH reasonCode() == null } @@ -202,9 +203,9 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl with(session.outProcessingPublishes()) { size() == 1 } - when: 'send unexpected publish receive to get protocol error' + when: 'send unexpected publish ack to get protocol error' def publishAck = PublishAckMqttInMessage - .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + .of(publish.messageId(), PublishAckReasonCode.SUCCESS) session .outProcessingPublishes() .apply(user, publishAck) @@ -215,7 +216,60 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl } } - def "should handle as protocol error for unexpected flow state"() { + def "should handle as protocol error receiving unexpected response message for second stage"() { + given: + def publishOutHandler = new Qos2MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def session = user.session() + def testTopicName = defaultTopicService.createTopicName(user, "Qos2MqttPublishOutMessageHandlerTest/4") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos2MqttPublishOutMessageHandlerTest/4") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(testPublish, subscriber) + def publish = user.nextSentMessage(PublishMqtt5OutMessage) + then: + result == PublishHandlingResult.SUCCESS + with(session.outMessageTracker()) { + with(stored(publish.messageId())) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == null + } + } + with(session.outProcessingPublishes()) { + size() == 1 + } + when: 'send publish received' + def publishReceived = PublishReceivedMqttInMessage + .of(publish.messageId(), PublishReceivedReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishReceived) + then: + with(user.nextSentMessage(PublishReleaseMqtt5OutMessage)) { + reasonCode() == PublishReleaseReasonCode.SUCCESS + messageId() == publish.messageId() + } + when: 'send unexpected publish ack to get protocol error' + def publishAck = PublishAckMqttInMessage + .of(publish.messageId(), PublishAckReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishAck) + then: + with(user.nextSentMessage(DisconnectMqtt5OutMessage)) { + reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR + reason() == "Unexpected response packet:'$MqttMessageType.PUBLISH_ACK', expected:'$MqttMessageType.PUBLISH_COMPLETE'" + } + } + + def "should handle as protocol error for unexpected flow state for publish received"() { given: def publishOutHandler = new Qos2MqttPublishOutMessageHandler( defaultSubscriptionService, @@ -228,15 +282,15 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) def subscriber = new SingleSubscriber(user, subscription) def originalMessageId = 60 - def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(publish, subscriber) - def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + def result = publishOutHandler.handle(testPublish, subscriber) + def publish = user.nextSentMessage(PublishMqtt5OutMessage) then: result == PublishHandlingResult.SUCCESS with(session.outMessageTracker()) { - with(stored(receivedPublish.messageId())) { + with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH reasonCode() == null } @@ -244,19 +298,61 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl with(session.outProcessingPublishes()) { size() == 1 } - when: 'chane trackable info to publish release and send publish ack' + when: 'change trackable info to publish release and send publish received' session .outMessageTracker() - .update(receivedPublish.messageId(), MqttMessageType.PUBLISH_RELEASE, PublishReleaseReasonCode.SUCCESS) - def publishAck = PublishAckMqttInMessage - .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + .update(publish.messageId(), MqttMessageType.PUBLISH_RELEASE, PublishReleaseReasonCode.SUCCESS) + def publishReceived = PublishReceivedMqttInMessage + .of(publish.messageId(), PublishReceivedReasonCode.SUCCESS) session .outProcessingPublishes() - .apply(user, publishAck) + .apply(user, publishReceived) then: with(user.nextSentMessage(DisconnectMqtt5OutMessage)) { reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR reason() == "Unexpected flow state:'$MqttMessageType.PUBLISH_RELEASE', expected:'$MqttMessageType.PUBLISH'" } } + + def "should handle as protocol error for unexpected flow state for publish complete"() { + given: + def publishOutHandler = new Qos2MqttPublishOutMessageHandler( + defaultSubscriptionService, + defaultMessageOutFactoryService) + def connection = mockedExternalConnection(MqttVersion.MQTT_5) + def user = connection.user() as TestExternalNetworkMqttUser + def session = user.session() + def testTopicName = defaultTopicService.createTopicName(user, "Qos2MqttPublishOutMessageHandlerTest/6") + def topicFilter = defaultTopicService.createTopicFilter(user, "Qos2MqttPublishOutMessageHandlerTest/6") + def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) + def subscriber = new SingleSubscriber(user, subscription) + def originalMessageId = 60 + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + .withDuplicated() + when: + def result = publishOutHandler.handle(testPublish, subscriber) + def publish = user.nextSentMessage(PublishMqtt5OutMessage) + then: + result == PublishHandlingResult.SUCCESS + with(session.outMessageTracker()) { + with(stored(publish.messageId())) { + messageType() == MqttMessageType.PUBLISH + reasonCode() == null + } + } + with(session.outProcessingPublishes()) { + size() == 1 + } + when: 'send publish complete' + def publishComplete = PublishCompleteMqttInMessage + .of(publish.messageId(), PublishCompletedReasonCode.SUCCESS) + session + .outProcessingPublishes() + .apply(user, publishComplete) + then: + with(user.nextSentMessage(DisconnectMqtt5OutMessage)) { + reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR + reason() == "Unexpected flow state:'$MqttMessageType.PUBLISH', expected:'$MqttMessageType.PUBLISH_RELEASE'" + } + } } 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 39ce7d0c..2f8d4922 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 @@ -70,4 +70,11 @@ protected PublishReleaseReasonCode readReasonCode(int unsignedByte) { protected Set availableProperties() { return AVAILABLE_PROPERTIES; } + + public static PublishReleaseMqttInMessage of(int messageId, PublishReleaseReasonCode reasonCode) { + var message = new PublishReleaseMqttInMessage(MESSAGE_FLAGS); + message.messageId = messageId; + message.reasonCode = reasonCode; + return message; + } } From 6c47ae6afc0098368e9a1d7f8c013d34dc395053 Mon Sep 17 00:00:00 2001 From: javasabr Date: Fri, 5 Dec 2025 20:50:14 +0100 Subject: [PATCH 11/19] update tests for messages --- .../network/message/in/MqttInMessage.java | 1 - .../message/in/PublishMqttInMessage.java | 2 +- .../out/PublishCompleteMqtt311OutMessage.java | 3 - .../out/PublishReceivedMqtt311OutMessage.java | 3 - .../out/PublishReleaseMqtt311OutMessage.java | 3 - .../in/AuthenticationMqttInMessageTest.groovy | 8 +- .../in/ConnectAckMqttInMessageTest.groovy | 2 +- .../in/ConnectMqttInMessageTest.groovy | 4 +- .../in/DisconnectMqttInMessageTest.groovy | 4 +- .../in/PublishAckMqttInMessageTest.groovy | 60 ++++---- .../PublishCompleteMqttInMessageTest.groovy | 54 ++++--- .../in/PublishMqttInMessageTest.groovy | 136 ++++++++++-------- .../PublishReceivedMqttInMessageTest.groovy | 54 ++++--- .../in/PublishReleaseMqttInMessageTest.groovy | 54 ++++--- .../in/SubscribeAckMqttInMessageTest.groovy | 20 +-- .../in/SubscribeMqttInMessageTest.groovy | 40 +++--- .../in/UnsubscribeAckMqttInMessageTest.groovy | 20 +-- .../in/UnsubscribeMqttInMessageTest.groovy | 26 ++-- .../AuthenticationMqtt5OutMessageTest.groovy | 4 +- .../out/ConnectAckMqtt5OutMessageTest.groovy | 4 +- .../out/ConnectMqtt5OutMessageTest.groovy | 4 +- .../DisconnectAckMqtt5OutMessageTest.groovy | 4 +- .../PublishAckMqtt311OutMessageTest.groovy | 14 +- .../out/PublishAckMqtt5OutMessageTest.groovy | 16 ++- ...ublishCompleteMqtt311OutMessageTest.groovy | 12 +- .../PublishCompleteMqtt5OutMessageTest.groovy | 14 +- .../out/PublishMqtt311OutMessageTest.groovy | 40 +++--- .../out/PublishMqtt5OutMessageTest.groovy | 72 +++++----- ...ublishReceivedMqtt311OutMessageTest.groovy | 12 +- .../PublishReceivedMqtt5OutMessageTest.groovy | 14 +- ...PublishReleaseMqtt311OutMessageTest.groovy | 12 +- .../PublishReleaseMqtt5OutMessageTest.groovy | 14 +- .../SubscribeAckMqtt311OutMessageTest.groovy | 4 +- .../SubscribeAckMqtt5OutMessageTest.groovy | 8 +- .../out/SubscribeMqtt5OutMessageTest.groovy | 8 +- ...UnsubscribeAckMqtt311OutMessageTest.groovy | 4 +- .../UnsubscribeAckMqtt5OutMessageTest.groovy | 8 +- .../network/NetworkUnitSpecification.groovy | 16 +-- 38 files changed, 421 insertions(+), 357 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 5d9b1363..ecb16f8d 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 @@ -39,7 +39,6 @@ public abstract class MqttInMessage extends AbstractReadableNetworkPacket 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) {} 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 4b5c5005..f2895a14 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 @@ -24,7 +24,7 @@ * Publish message. */ @Getter -@Accessors(fluent = true) +@Accessors @FieldDefaults(level = AccessLevel.PROTECTED) public class PublishMqttInMessage extends TrackableMqttInMessage { diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishCompleteMqtt311OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishCompleteMqtt311OutMessage.java index b4ce3fc2..59182780 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishCompleteMqtt311OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishCompleteMqtt311OutMessage.java @@ -2,13 +2,10 @@ import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.network.MqttConnection; -import lombok.AccessLevel; -import lombok.experimental.FieldDefaults; /** * Publish complete (QoS 2 delivery part 3). */ -@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) public class PublishCompleteMqtt311OutMessage extends TrackableMqttOutMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.PUBLISH_COMPLETE.ordinal(); diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishReceivedMqtt311OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishReceivedMqtt311OutMessage.java index 951677a3..22afce41 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishReceivedMqtt311OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishReceivedMqtt311OutMessage.java @@ -2,13 +2,10 @@ import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.network.MqttConnection; -import lombok.AccessLevel; -import lombok.experimental.FieldDefaults; /** * Publish received (QoS 2 delivery part 1). */ -@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) public class PublishReceivedMqtt311OutMessage extends TrackableMqttOutMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.PUBLISH_RECEIVED.ordinal(); 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 a91d29f8..ffb4d1ab 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 @@ -3,13 +3,10 @@ import java.nio.ByteBuffer; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.network.MqttConnection; -import lombok.AccessLevel; -import lombok.experimental.FieldDefaults; /** * Publish release (QoS 2 delivery part 2). */ -@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) public class PublishReleaseMqtt311OutMessage extends TrackableMqttOutMessage { public static final int MESSAGE_FLAGS = 0b0000_0010; 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 eebf2f0d..302558ab 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 @@ -14,7 +14,7 @@ class AuthenticationMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.AUTHENTICATION_METHOD, authMethod) it.putProperty(MqttMessageProperty.AUTHENTICATION_DATA, authData) it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { it.put(AuthenticateReasonCode.SUCCESS) @@ -30,12 +30,12 @@ class AuthenticationMqttInMessageTest extends BaseMqttInMessageTest { inMessage.authenticationMethod() == authMethod inMessage.authenticationData() == authData inMessage.reason() == reasonString - inMessage.userProperties() == userProperties + inMessage.userProperties() == testUserProperties when: def propertiesBuffer2 = BufferUtils.prepareBuffer(512) { it.putProperty(MqttMessageProperty.AUTHENTICATION_METHOD, authMethod) it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) it.putProperty(MqttMessageProperty.AUTHENTICATION_DATA, authData) } def dataBuffer2 = BufferUtils.prepareBuffer(512) { @@ -51,7 +51,7 @@ class AuthenticationMqttInMessageTest extends BaseMqttInMessageTest { inMessage2.authenticationMethod() == authMethod inMessage2.authenticationData() == authData inMessage2.reason() == reasonString - inMessage2.userProperties() == userProperties + inMessage2.userProperties() == testUserProperties when: def propertiesBuffer3 = BufferUtils.prepareBuffer(512) { it.putProperty(MqttMessageProperty.AUTHENTICATION_METHOD, authMethod) diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/ConnectAckMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/ConnectAckMqttInMessageTest.groovy index fff76d99..854c312d 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/ConnectAckMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/ConnectAckMqttInMessageTest.groovy @@ -191,7 +191,7 @@ class ConnectAckMqttInMessageTest extends BaseMqttInMessageTest { def "should not allow invalid message flags"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishAckReasonCode.SUCCESS) it.putMbi(0) } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/ConnectMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/ConnectMqttInMessageTest.groovy index 41597193..a73d4d66 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/ConnectMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/ConnectMqttInMessageTest.groovy @@ -44,7 +44,7 @@ class ConnectMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.REQUEST_PROBLEM_INFORMATION, requestProblemInformation ? 1 : 0) it.putProperty(MqttMessageProperty.AUTHENTICATION_METHOD, authMethod) it.putProperty(MqttMessageProperty.AUTHENTICATION_DATA, authData) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { it.putString("MQTT") @@ -76,7 +76,7 @@ class ConnectMqttInMessageTest extends BaseMqttInMessageTest { packet.willTopic() == "" packet.willQos() == 0 packet.willPayload() == ArrayUtils.EMPTY_BYTE_ARRAY - packet.userProperties() == userProperties + packet.userProperties() == testUserProperties } def "should not read packet correctly with invalid UTF8 strings"(byte[] stringBytes) { 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 376fed6c..362fb29a 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 @@ -12,7 +12,7 @@ class DisconnectMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) it.putProperty(MqttMessageProperty.SERVER_REFERENCE, serverReference) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { it.putByte(DisconnectReasonCode.QUOTA_EXCEEDED.code()) @@ -28,7 +28,7 @@ class DisconnectMqttInMessageTest extends BaseMqttInMessageTest { inMessage.serverReference() == serverReference inMessage.reasonCode() == DisconnectReasonCode.QUOTA_EXCEEDED inMessage.sessionExpiryInterval() == sessionExpiryInterval - inMessage.userProperties() == userProperties + inMessage.userProperties() == testUserProperties when: propertiesBuffer = BufferUtils.prepareBuffer(512) { it.putProperty(MqttMessageProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) 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 e7e1f929..6fe95557 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 @@ -11,28 +11,30 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { def "should read message correctly as MQTT 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) } when: def inMessage = new PublishAckMqttInMessage(PublishAckMqttInMessage.MESSAGE_FLAGS) def result = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.exception() == null - inMessage.reason() == null - inMessage.messageId() == messageId - inMessage.reasonCode() == PublishAckReasonCode.SUCCESS - inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + with(inMessage) { + exception() == null + reason() == null + messageId() == testMessageId + reasonCode() == PublishAckReasonCode.SUCCESS + userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + } } def "should read message correctly as MQTT 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishAckReasonCode.PAYLOAD_FORMAT_INVALID) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -42,14 +44,16 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.exception() == null - inMessage.reason() == reasonString - inMessage.messageId() == messageId - inMessage.reasonCode() == PublishAckReasonCode.PAYLOAD_FORMAT_INVALID - inMessage.userProperties() == userProperties + with(inMessage) { + exception() == null + reason() == reasonString + messageId() == testMessageId + reasonCode() == PublishAckReasonCode.PAYLOAD_FORMAT_INVALID + userProperties() == userProperties + } when: def dataBuffer2 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishAckReasonCode.UNSPECIFIED_ERROR) it.putMbi(0) } @@ -57,11 +61,13 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { def result2 = inMessage2.read(defaultMqtt5Connection, dataBuffer2, dataBuffer2.limit()) then: result2 - inMessage2.exception() == null - inMessage2.reason() == null - inMessage2.messageId() == messageId - inMessage2.reasonCode() == PublishAckReasonCode.UNSPECIFIED_ERROR - inMessage2.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + with(inMessage2) { + exception() == null + reason() == null + messageId() == testMessageId + reasonCode() == PublishAckReasonCode.UNSPECIFIED_ERROR + userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + } } def "should not allow to put reason 2 times"() { @@ -71,7 +77,7 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.REASON_STRING, "reason1") } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishAckReasonCode.SUCCESS) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -81,14 +87,16 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: !result - inMessage.exception() instanceof MalformedProtocolMqttException - inMessage.exception().message == "Property:[$MqttMessageProperty.REASON_STRING] is already presented in message:[$MqttMessageType.PUBLISH_ACK]" + with(inMessage) { + exception() instanceof MalformedProtocolMqttException + exception().message == "Property:[$MqttMessageProperty.REASON_STRING] is already presented in message:[$MqttMessageType.PUBLISH_ACK]" + } } def "should not allow invalid message flags"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishAckReasonCode.SUCCESS) it.putMbi(0) } @@ -97,7 +105,9 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: !result - inMessage.exception() instanceof MalformedProtocolMqttException - inMessage.exception().message == "Unexpected message flags:[0b0101_0101] in message:[$MqttMessageType.PUBLISH_ACK]" + with(inMessage) { + exception() instanceof MalformedProtocolMqttException + exception().message == "Unexpected message flags:[0b0101_0101] in message:[$MqttMessageType.PUBLISH_ACK]" + } } } 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 8335bb4a..68a17259 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 @@ -11,27 +11,29 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { def "should read message correctly as MQTT 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) } when: def inMessage = new PublishCompleteMqttInMessage(PublishCompleteMqttInMessage.MESSAGE_FLAGS) def result = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.reason() == null - inMessage.messageId() == messageId - inMessage.reasonCode() == PublishCompletedReasonCode.SUCCESS - inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + with(inMessage) { + reason() == null + messageId() == testMessageId + reasonCode() == PublishCompletedReasonCode.SUCCESS + userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + } } def "should read message correctly as MQTT 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -41,13 +43,15 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.reason() == reasonString - inMessage.messageId() == messageId - inMessage.reasonCode() == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND - inMessage.userProperties() == userProperties + with(inMessage) { + reason() == reasonString + messageId() == testMessageId + reasonCode() == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND + userProperties() == userProperties + } when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND) it.putMbi(0) } @@ -55,10 +59,12 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.reason() == null - inMessage.messageId() == messageId - inMessage.reasonCode() == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND - inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + with(inMessage) { + reason() == null + messageId() == testMessageId + reasonCode() == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND + userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + } } def "should not allow to put reason 2 times"() { @@ -68,7 +74,7 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.REASON_STRING, "reason1") } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishCompletedReasonCode.SUCCESS) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -78,14 +84,16 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: !result - inMessage.exception() instanceof MalformedProtocolMqttException - inMessage.exception().message == "Property:[$MqttMessageProperty.REASON_STRING] is already presented in message:[$MqttMessageType.PUBLISH_COMPLETE]" + with(inMessage) { + exception() instanceof MalformedProtocolMqttException + exception().message == "Property:[$MqttMessageProperty.REASON_STRING] is already presented in message:[$MqttMessageType.PUBLISH_COMPLETE]" + } } def "should not allow invalid message flags"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishCompletedReasonCode.SUCCESS) it.putMbi(0) } @@ -94,7 +102,9 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: !result - inMessage.exception() instanceof MalformedProtocolMqttException - inMessage.exception().message == "Unexpected message flags:[0b0101_0101] in message:[$MqttMessageType.PUBLISH_COMPLETE]" + with(inMessage) { + exception() instanceof MalformedProtocolMqttException + exception().message == "Unexpected message flags:[0b0101_0101] in message:[$MqttMessageType.PUBLISH_COMPLETE]" + } } } 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 abc1f1c7..e4579a83 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 @@ -15,7 +15,7 @@ class PublishMqttInMessageTest extends BaseMqttInMessageTest { given: def dataBuffer = BufferUtils.prepareBuffer(512) { it.putString(publishTopic.toString()) - it.putShort(messageId) + it.putShort(testMessageId) it.put(publishPayload) } when: @@ -23,82 +23,88 @@ class PublishMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.qos() == QoS.AT_LEAST_ONCE - !inMessage.duplicate() - inMessage.retain() - inMessage.rawResponseTopicName() == null - inMessage.subscriptionIds() == IntArray.empty() - inMessage.contentType() == null - inMessage.correlationData() == null - inMessage.payload() == publishPayload - inMessage.messageId() == messageId - inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES - inMessage.messageExpiryInterval() == MqttProperties.MESSAGE_EXPIRY_INTERVAL_IS_NOT_SET - inMessage.topicAlias() == MqttProperties.TOPIC_ALIAS_NOT_SET - inMessage.payloadFormat() == PayloadFormat.UNDEFINED + with(inMessage) { + qos() == QoS.AT_LEAST_ONCE + !duplicate() + retain() + rawResponseTopicName() == null + subscriptionIds() == IntArray.empty() + contentType() == null + correlationData() == null + payload() == publishPayload + messageId() == testMessageId + userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + messageExpiryInterval() == MqttProperties.MESSAGE_EXPIRY_INTERVAL_IS_NOT_SET + topicAlias() == MqttProperties.TOPIC_ALIAS_NOT_SET + payloadFormat() == PayloadFormat.UNDEFINED + } } def "should read message correctly as MQTT 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { it.putProperty(MqttMessageProperty.PAYLOAD_FORMAT_INDICATOR, 1) - it.putProperty(MqttMessageProperty.MESSAGE_EXPIRY_INTERVAL, messageExpiryInterval) - it.putProperty(MqttMessageProperty.TOPIC_ALIAS, topicAlias) - it.putProperty(MqttMessageProperty.RESPONSE_TOPIC, responseTopic.rawTopic()) - it.putProperty(MqttMessageProperty.CORRELATION_DATA, correlationData) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) - it.putProperty(MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, subscriptionIds) - it.putProperty(MqttMessageProperty.CONTENT_TYPE, contentType) + it.putProperty(MqttMessageProperty.MESSAGE_EXPIRY_INTERVAL, testMessageExpiryInterval) + it.putProperty(MqttMessageProperty.TOPIC_ALIAS, testTopicAlias) + it.putProperty(MqttMessageProperty.RESPONSE_TOPIC, testResponseTopic.rawTopic()) + it.putProperty(MqttMessageProperty.CORRELATION_DATA, testCorrelationData) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) + it.putProperty(MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, testSubscriptionIds) + it.putProperty(MqttMessageProperty.CONTENT_TYPE, testContentType) } def dataBuffer = BufferUtils.prepareBuffer(512) { it.putString(publishTopic.toString()) - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.put(publishPayload) } when: - def message = new PublishMqttInMessage(0b0110_0011 as byte) - def result = message.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + def inMessage = new PublishMqttInMessage(0b0110_0011 as byte) + def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - message.qos() == QoS.AT_LEAST_ONCE - !message.duplicate() - message.retain() - message.rawResponseTopicName() == responseTopic.rawTopic() - message.subscriptionIds() == subscriptionIds - message.contentType() == contentType - message.correlationData() == correlationData - message.payload() == publishPayload - message.messageId() == messageId - message.userProperties() == userProperties - message.messageExpiryInterval() == messageExpiryInterval - message.topicAlias() == topicAlias - message.payloadFormat() == PayloadFormat.UTF8_STRING + with(inMessage) { + qos() == QoS.AT_LEAST_ONCE + !duplicate() + retain() + rawResponseTopicName() == testResponseTopic.rawTopic() + subscriptionIds() == testSubscriptionIds + contentType() == testContentType + correlationData() == testCorrelationData + payload() == publishPayload + messageId() == testMessageId + userProperties() == testUserProperties + messageExpiryInterval() == testMessageExpiryInterval + topicAlias() == testTopicAlias + payloadFormat() == PayloadFormat.UTF8_STRING + } when: dataBuffer = BufferUtils.prepareBuffer(512) { it.putString(publishTopic.toString()) - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) it.put(publishPayload) } - message = new PublishMqttInMessage(0b0110_0011 as byte) - result = message.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + inMessage = new PublishMqttInMessage(0b0110_0011 as byte) + result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - message.qos() == QoS.AT_LEAST_ONCE - !message.duplicate() - message.retain() - message.rawResponseTopicName() == null - message.subscriptionIds() == IntArray.empty() - message.contentType() == null - message.correlationData() == null - message.payload() == publishPayload - message.messageId() == messageId - message.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES - message.messageExpiryInterval() == MqttProperties.MESSAGE_EXPIRY_INTERVAL_IS_NOT_SET - message.topicAlias() == MqttProperties.TOPIC_ALIAS_NOT_SET - message.payloadFormat() == PayloadFormat.UNDEFINED + with(inMessage) { + qos() == QoS.AT_LEAST_ONCE + !duplicate() + retain() + rawResponseTopicName() == null + subscriptionIds() == IntArray.empty() + contentType() == null + correlationData() == null + payload() == publishPayload + messageId() == testMessageId + userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + messageExpiryInterval() == MqttProperties.MESSAGE_EXPIRY_INTERVAL_IS_NOT_SET + topicAlias() == MqttProperties.TOPIC_ALIAS_NOT_SET + payloadFormat() == PayloadFormat.UNDEFINED + } } def "should not allow to send unexpected property"() { @@ -108,7 +114,7 @@ class PublishMqttInMessageTest extends BaseMqttInMessageTest { } def dataBuffer = BufferUtils.prepareBuffer(512) { it.putString(publishTopic.toString()) - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.put(publishPayload) @@ -118,8 +124,10 @@ class PublishMqttInMessageTest extends BaseMqttInMessageTest { def successful = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: !successful - inMessage.exception() instanceof MalformedProtocolMqttException - inMessage.exception().message == "Property:[$MqttMessageProperty.SERVER_KEEP_ALIVE] is not available for message:[$MqttMessageType.PUBLISH]" + with(inMessage) { + exception() instanceof MalformedProtocolMqttException + exception().message == "Property:[$MqttMessageProperty.SERVER_KEEP_ALIVE] is not available for message:[$MqttMessageType.PUBLISH]" + } } def "should not allow duplicated properties in message"(MqttMessageProperty property, Object value) { @@ -130,7 +138,7 @@ class PublishMqttInMessageTest extends BaseMqttInMessageTest { } def dataBuffer = BufferUtils.prepareBuffer(512) { it.putString(publishTopic.toString()) - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) } @@ -139,13 +147,15 @@ class PublishMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: !result - inMessage.exception() instanceof MalformedProtocolMqttException - inMessage.exception().message == "Property:[$property] is already presented in message:[$MqttMessageType.PUBLISH]" + with(inMessage) { + exception() instanceof MalformedProtocolMqttException + exception().message == "Property:[$property] is already presented in message:[$MqttMessageType.PUBLISH]" + } where: property | value - MqttMessageProperty.TOPIC_ALIAS | topicAlias - MqttMessageProperty.RESPONSE_TOPIC | responseTopic.rawTopic() - MqttMessageProperty.CONTENT_TYPE | contentType - MqttMessageProperty.CORRELATION_DATA | correlationData + MqttMessageProperty.TOPIC_ALIAS | testTopicAlias + MqttMessageProperty.RESPONSE_TOPIC | testResponseTopic.rawTopic() + MqttMessageProperty.CONTENT_TYPE | testContentType + MqttMessageProperty.CORRELATION_DATA | testCorrelationData } } 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 ea26cfec..fce24247 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 @@ -11,27 +11,29 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { def "should read message correctly as MQTT 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) } when: def inMessage = new PublishReceivedMqttInMessage(PublishReceivedMqttInMessage.MESSAGE_FLAGS) def result = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.reason() == null - inMessage.messageId() == messageId - inMessage.reasonCode() == PublishReceivedReasonCode.SUCCESS - inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + with(inMessage) { + reason() == null + messageId() == testMessageId + reasonCode() == PublishReceivedReasonCode.SUCCESS + userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + } } def "should read message correctly as MQTT 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishReceivedReasonCode.QUOTA_EXCEEDED) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -41,13 +43,15 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.reason() == reasonString - inMessage.messageId() == messageId - inMessage.reasonCode() == PublishReceivedReasonCode.QUOTA_EXCEEDED - inMessage.userProperties() == userProperties + with(inMessage) { + reason() == reasonString + messageId() == testMessageId + reasonCode() == PublishReceivedReasonCode.QUOTA_EXCEEDED + userProperties() == testUserProperties + } when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR) it.putMbi(0) } @@ -55,10 +59,12 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.reason() == null - inMessage.messageId() == messageId - inMessage.reasonCode() == PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR - inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + with(inMessage) { + reason() == null + messageId() == testMessageId + reasonCode() == PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR + userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + } } def "should not allow to put reason 2 times"() { @@ -68,7 +74,7 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.REASON_STRING, "reason1") } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishReceivedReasonCode.SUCCESS) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -78,14 +84,16 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: !result - inMessage.exception() instanceof MalformedProtocolMqttException - inMessage.exception().message == "Property:[$MqttMessageProperty.REASON_STRING] is already presented in message:[$MqttMessageType.PUBLISH_RECEIVED]" + with(inMessage) { + exception() instanceof MalformedProtocolMqttException + exception().message == "Property:[$MqttMessageProperty.REASON_STRING] is already presented in message:[$MqttMessageType.PUBLISH_RECEIVED]" + } } def "should not allow invalid message flags"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishReceivedReasonCode.SUCCESS) it.putMbi(0) } @@ -94,7 +102,9 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: !result - inMessage.exception() instanceof MalformedProtocolMqttException - inMessage.exception().message == "Unexpected message flags:[0b0101_0101] in message:[$MqttMessageType.PUBLISH_RECEIVED]" + with(inMessage) { + exception() instanceof MalformedProtocolMqttException + exception().message == "Unexpected message flags:[0b0101_0101] in message:[$MqttMessageType.PUBLISH_RECEIVED]" + } } } 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 4d909f52..ecf58d69 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 @@ -11,27 +11,29 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { def "should read message correctly as MQTT 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) } when: def inMessage = new PublishReleaseMqttInMessage(PublishReleaseMqttInMessage.MESSAGE_FLAGS) def result = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.reason() == null - inMessage.messageId() == messageId - inMessage.reasonCode() == PublishReleaseReasonCode.SUCCESS - inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + with(inMessage) { + reason() == null + messageId() == testMessageId + reasonCode() == PublishReleaseReasonCode.SUCCESS + userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + } } def "should read message correctly as MQTT 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -41,13 +43,15 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.reason() == reasonString - inMessage.messageId() == messageId - inMessage.reasonCode() == PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND - inMessage.userProperties() == userProperties + with(inMessage) { + reason() == reasonString + messageId() == testMessageId + reasonCode() == PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND + userProperties() == testUserProperties + } when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishReleaseReasonCode.SUCCESS) it.putMbi(0) } @@ -55,10 +59,12 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.reason() == null - inMessage.messageId() == messageId - inMessage.reasonCode() == PublishReleaseReasonCode.SUCCESS - inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + with(inMessage) { + reason() == null + messageId() == testMessageId + reasonCode() == PublishReleaseReasonCode.SUCCESS + userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES + } } def "should not allow to put reason 2 times"() { @@ -68,7 +74,7 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.REASON_STRING, "reason1") } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishReleaseReasonCode.SUCCESS) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -78,14 +84,16 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: !result - inMessage.exception() instanceof MalformedProtocolMqttException - inMessage.exception().message == "Property:[$MqttMessageProperty.REASON_STRING] is already presented in message:[$MqttMessageType.PUBLISH_RELEASE]" + with(inMessage) { + exception() instanceof MalformedProtocolMqttException + exception().message == "Property:[$MqttMessageProperty.REASON_STRING] is already presented in message:[$MqttMessageType.PUBLISH_RELEASE]" + } } def "should not allow invalid message flags"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(PublishReleaseReasonCode.SUCCESS) it.putMbi(0) } @@ -94,7 +102,9 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: !result - inMessage.exception() instanceof MalformedProtocolMqttException - inMessage.exception().message == "Unexpected message flags:[0b0101_0101] in message:[$MqttMessageType.PUBLISH_RELEASE]" + with(inMessage) { + exception() instanceof MalformedProtocolMqttException + exception().message == "Unexpected message flags:[0b0101_0101] in message:[$MqttMessageType.PUBLISH_RELEASE]" + } } } 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 05d3ec2f..433484c5 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 @@ -11,7 +11,7 @@ class SubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { def "should read message correctly as MQTT 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.put(SubscribeAckReasonCode.GRANTED_QOS_0) it.put(SubscribeAckReasonCode.GRANTED_QOS_2) it.put(SubscribeAckReasonCode.GRANTED_QOS_1) @@ -23,7 +23,7 @@ class SubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { then: result inMessage.reason() == null - inMessage.messageId() == messageId + inMessage.messageId() == testMessageId def reasonCodes = inMessage.reasonCodes() reasonCodes.size() == 4 reasonCodes.get(0) == SubscribeAckReasonCode.GRANTED_QOS_0 @@ -36,10 +36,10 @@ class SubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.put(SubscribeAckReasonCode.GRANTED_QOS_0) @@ -53,8 +53,8 @@ class SubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { then: result inMessage.reason() == reasonString - inMessage.messageId() == messageId - inMessage.userProperties() == userProperties + inMessage.messageId() == testMessageId + inMessage.userProperties() == testUserProperties def reasonCodes = inMessage.reasonCodes() reasonCodes.size() == 4 reasonCodes.get(0) == SubscribeAckReasonCode.GRANTED_QOS_0 @@ -63,7 +63,7 @@ class SubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { reasonCodes.get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR when: def dataBuffer2 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) it.putByte(SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED.code()) it.putByte(SubscribeAckReasonCode.GRANTED_QOS_2.code()) @@ -75,7 +75,7 @@ class SubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { then: result2 inMessage2.reason() == null - inMessage2.messageId() == messageId + inMessage2.messageId() == testMessageId inMessage2.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES def reasonCodes2 = inMessage2.reasonCodes() reasonCodes2.size() == 4 @@ -92,7 +92,7 @@ class SubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.REASON_STRING, "reason1") } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.put(SubscribeAckReasonCode.GRANTED_QOS_0) @@ -110,7 +110,7 @@ class SubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { def "should not allow invalid message flags"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) it.put(SubscribeAckReasonCode.GRANTED_QOS_0) it.put(SubscribeAckReasonCode.GRANTED_QOS_1) 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 7b81ec54..cd3099d8 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 @@ -14,7 +14,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { def "should read message correctly as MQTT 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putString(topicFilter) it.put(0b0000_0001 as byte) // QoS.AT_LEAST_ONCE it.putString(topicFilter2) @@ -37,7 +37,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { subscriptions.get(1).noLocal() subscriptions.get(1).retainAsPublished() subscriptions.get(1).retainHandling() == SubscribeRetainHandling.SEND - inMessage.messageId() == messageId + inMessage.messageId() == testMessageId inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET } @@ -46,10 +46,10 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { it.putProperty(MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, subscriptionId) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.putString(topicFilter) @@ -81,12 +81,12 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { subscriptions.get(2).noLocal() !subscriptions.get(2).retainAsPublished() subscriptions.get(2).retainHandling() == SubscribeRetainHandling.DO_NOT_SEND - inMessage.messageId() == messageId - inMessage.userProperties() == userProperties + inMessage.messageId() == testMessageId + inMessage.userProperties() == testUserProperties inMessage.subscriptionId() == subscriptionId when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) it.putString(topicFilter) it.put(0b0000_0001 as byte) @@ -109,7 +109,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { !subscriptions.get(1).noLocal() !subscriptions.get(1).retainAsPublished() subscriptions.get(1).retainHandling() == SubscribeRetainHandling.SEND - inMessage.messageId() == messageId + inMessage.messageId() == testMessageId inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET } @@ -117,7 +117,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { def "should not read invalid message as MQTT 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putString(topicFilter) it.put(0b0000_0001 as byte) } @@ -130,7 +130,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { inMessage.exception().message == "Unexpected message flags:[0b0000_0000] in message:[$MqttMessageType.SUBSCRIBE]" when: 'invalid QoS or Retain Handling' def dataBuffer2 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putString(topicFilter) it.put(0b0000_0011 as byte) } @@ -142,7 +142,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { inMessage2.exception().message == MqttProtocolErrors.UNSUPPORTED_QOS_OR_RETAIN_HANDLING when: 'not provided any topic filter' def dataBuffer3 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) } def inMessage3 = new SubscribeMqttInMessage(SubscribeMqttInMessage.MESSAGE_FLAGS) def successful3 = inMessage3.read(defaultMqtt311Connection, dataBuffer3, dataBuffer3.limit()) @@ -152,7 +152,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { inMessage3.exception().message == MqttProtocolErrors.NO_ANY_TOPIC_FILTERS when: 'unsupported no local option' def dataBuffer4 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putString(topicFilter) it.put(0b0000_0100 as byte) } @@ -164,7 +164,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { inMessage4.exception().message == MqttProtocolErrors.PROTOCOL_LEVEL_UNSUPPORTED_NO_LOCAL_OPTION when: 'unsupported retain as publish option' def dataBuffer5 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putString(topicFilter) it.put(0b0000_1000 as byte) } @@ -176,7 +176,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { inMessage5.exception().message == MqttProtocolErrors.PROTOCOL_LEVEL_UNSUPPORTED_RETAIN_AS_PUBLISH_OPTION when: 'unsupported retain handling option' def dataBuffer6 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putString(topicFilter) it.put(0b0011_0000 as byte) } @@ -191,7 +191,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { def "should not read invalid message as MQTT 5.0"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) it.putString(topicFilter) it.put(0b0000_0001 as byte) @@ -205,7 +205,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { inMessage.exception().message == "Unexpected message flags:[0b0000_0000] in message:[$MqttMessageType.SUBSCRIBE]" when: 'invalid QoS or retain handling' def dataBuffer2 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) it.putString(topicFilter) it.put(0b0011_1001 as byte) @@ -218,7 +218,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { inMessage2.exception().message == MqttProtocolErrors.UNSUPPORTED_QOS_OR_RETAIN_HANDLING when: 'no any topic filter' def dataBuffer3 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) } def inMessage3 = new SubscribeMqttInMessage(SubscribeMqttInMessage.MESSAGE_FLAGS) @@ -232,7 +232,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.SERVER_REFERENCE, "reference") } def dataBuffer4 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) } @@ -247,7 +247,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, -50) } def dataBuffer5 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer2.limit()) it.put(propertiesBuffer2) } @@ -263,7 +263,7 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, 90) } def dataBuffer6 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer3.limit()) it.put(propertiesBuffer3) } 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 e1cb4734..204797d5 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 @@ -11,7 +11,7 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { def "should read message correctly as MQTT 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) } when: def inMessage = new UnsubscribeAckMqttInMessage(UnsubscribeAckMqttInMessage.MESSAGE_FLAGS) @@ -19,7 +19,7 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { then: result inMessage.reason() == null - inMessage.messageId() == messageId + inMessage.messageId() == testMessageId inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES } @@ -27,10 +27,10 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.put(UnsubscribeAckReasonCode.SUCCESS) @@ -44,8 +44,8 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { then: result inMessage.reason() == reasonString - inMessage.messageId() == messageId - inMessage.userProperties() == userProperties + inMessage.messageId() == testMessageId + inMessage.userProperties() == testUserProperties def reasonCodes = inMessage.reasonCodes() reasonCodes.size() == 4 reasonCodes.get(0) == UnsubscribeAckReasonCode.SUCCESS @@ -54,7 +54,7 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { reasonCodes.get(3) == UnsubscribeAckReasonCode.UNSPECIFIED_ERROR when: def dataBuffer2 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) it.put(UnsubscribeAckReasonCode.UNSPECIFIED_ERROR) it.put(UnsubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR) @@ -64,7 +64,7 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { then: result2 inMessage2.reason() == null - inMessage2.messageId() == messageId + inMessage2.messageId() == testMessageId inMessage2.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES def reasonCodes2 = inMessage2.reasonCodes() reasonCodes2.size() == 2 @@ -79,7 +79,7 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.REASON_STRING, "reason1") } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.put(UnsubscribeAckReasonCode.SUCCESS) @@ -97,7 +97,7 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { def "should not allow invalid message flags"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) it.put(UnsubscribeAckReasonCode.SUCCESS) it.put(UnsubscribeAckReasonCode.SUCCESS) 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 794f7e95..cc86b3c1 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 @@ -11,7 +11,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def "should read message correctly as MQTT 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putString(topicFilter) it.putString(topicFilter2) } @@ -20,7 +20,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.messageId() == messageId + inMessage.messageId() == testMessageId inMessage.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES def rawTopicFilters = inMessage.rawTopicFilters() rawTopicFilters.size() == 2 @@ -31,10 +31,10 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def "should read message correctly as MQTT 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, testUserProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.putString(topicFilter) @@ -45,15 +45,15 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - inMessage.messageId() == messageId - inMessage.userProperties() == userProperties + inMessage.messageId() == testMessageId + inMessage.userProperties() == testUserProperties def rawTopicFilters = inMessage.rawTopicFilters() rawTopicFilters.size() == 2 rawTopicFilters.get(0).toString() == topicFilter rawTopicFilters.get(1).toString() == topicFilter2 when: def dataBuffer2 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) it.putString(topicFilter) it.putString(topicFilter2) @@ -62,7 +62,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def result2 = inMessage2.read(defaultMqtt5Connection, dataBuffer2, dataBuffer2.limit()) then: result2 - inMessage2.messageId() == messageId + inMessage2.messageId() == testMessageId inMessage2.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES def rawTopicFilters2 = inMessage2.rawTopicFilters() rawTopicFilters2.size() == 2 @@ -73,7 +73,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def "should not read invalid message as MQTT 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putString(topicFilter) } when: @@ -85,7 +85,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { inMessage.exception().message == "Unexpected message flags:[0b0000_0000] in message:[$MqttMessageType.UNSUBSCRIBE]" when: def dataBuffer2 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) } def inMessage2 = new UnsubscribeMqttInMessage(UnsubscribeMqttInMessage.MESSAGE_FLAGS) def successful2 = inMessage2.read(defaultMqtt311Connection, dataBuffer2, dataBuffer2.limit()) @@ -98,7 +98,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def "should not read invalid message as MQTT 5.0"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) } when: @@ -110,7 +110,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { inMessage.exception().message == "Unexpected message flags:[0b0000_0000] in message:[$MqttMessageType.UNSUBSCRIBE]" when: def dataBuffer2 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(0) } def inMessage2 = new UnsubscribeMqttInMessage(UnsubscribeMqttInMessage.MESSAGE_FLAGS) @@ -124,7 +124,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { it.putProperty(MqttMessageProperty.SERVER_REFERENCE, "reference") } def dataBuffer3 = BufferUtils.prepareBuffer(512) { - it.putShort(messageId) + it.putShort(testMessageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/AuthenticationMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/AuthenticationMqtt5OutMessageTest.groovy index 8db2f024..0999a9e7 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/AuthenticationMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/AuthenticationMqtt5OutMessageTest.groovy @@ -15,7 +15,7 @@ class AuthenticationMqtt5OutMessageTest extends BaseMqttOutMessageTest { reasonString, authMethod, authData, - userProperties) + testUserProperties) when: def typeAndFlags = outMessage.messageTypeAndFlags() byte type = NumberUtils.getHighByteBits(typeAndFlags); @@ -35,6 +35,6 @@ class AuthenticationMqtt5OutMessageTest extends BaseMqttOutMessageTest { reader.reason() == reasonString reader.authenticationMethod() == authMethod reader.authenticationData() == authData - reader.userProperties() == userProperties + reader.userProperties() == testUserProperties } } 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 8d94d698..4f1250bc 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 @@ -51,7 +51,7 @@ class ConnectAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { responseInformation, authMethod, authData, - userProperties) + testUserProperties) when: def typeAndFlags = outMessage.messageTypeAndFlags() byte type = NumberUtils.getHighByteBits(typeAndFlags); @@ -75,7 +75,7 @@ class ConnectAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { inMessage.assignedClientId() == mqtt5ClientId inMessage.topicAliasMaxValue() == 300 inMessage.reason() == reasonString - inMessage.userProperties() == userProperties + inMessage.userProperties() == testUserProperties inMessage.serverKeepAlive() == 30 inMessage.responseInformation() == responseInformation inMessage.serverReference() == serverReference diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectMqtt5OutMessageTest.groovy index 647ed5cd..1e1c9a67 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectMqtt5OutMessageTest.groovy @@ -19,7 +19,7 @@ class ConnectMqtt5OutMessageTest extends BaseMqttOutMessageTest { keepAlive, willRetain, cleanStart, - userProperties, + testUserProperties, authMethod, authData, sessionExpiryInterval, @@ -40,7 +40,7 @@ class ConnectMqtt5OutMessageTest extends BaseMqttOutMessageTest { reader.clientId() == mqtt311ClientId reader.password() == userPassword reader.keepAlive() == keepAlive - reader.userProperties() == userProperties + reader.userProperties() == testUserProperties reader.cleanStart() == cleanStart reader.willRetain() == willRetain reader.authenticationMethod() == authMethod diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/DisconnectAckMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/DisconnectAckMqtt5OutMessageTest.groovy index d0ef9041..fe176d22 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/DisconnectAckMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/DisconnectAckMqtt5OutMessageTest.groovy @@ -10,7 +10,7 @@ class DisconnectAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { given: def packet = new DisconnectMqtt5OutMessage( DisconnectReasonCode.PACKET_TOO_LARGE, - userProperties, + testUserProperties, reasonString, serverReference, sessionExpiryInterval) @@ -23,7 +23,7 @@ class DisconnectAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCode == DisconnectReasonCode.PACKET_TOO_LARGE - reader.userProperties() == userProperties + reader.userProperties() == testUserProperties reader.reason == reasonString reader.serverReference == serverReference reader.sessionExpiryInterval == sessionExpiryInterval 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 ad97fc87..bef845e7 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 @@ -10,7 +10,7 @@ class PublishAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: - def outMessage = new PublishAckMqtt311OutMessage(messageId) + def outMessage = new PublishAckMqtt311OutMessage(testMessageId) when: def typeAndFlags = outMessage.messageTypeAndFlags() byte type = NumberUtils.getHighByteBits(typeAndFlags); @@ -26,10 +26,12 @@ class PublishAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - reader.exception() == null - reader.reasonCode() == PublishAckReasonCode.SUCCESS - reader.messageId() == messageId - reader.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - reader.reason() == null + with(reader) { + exception() == null + reasonCode() == PublishAckReasonCode.SUCCESS + messageId() == testMessageId + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + reason() == null + } } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishAckMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishAckMqtt5OutMessageTest.groovy index 39a9083b..849b58ae 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishAckMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishAckMqtt5OutMessageTest.groovy @@ -11,10 +11,10 @@ class PublishAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: def outMessage = new PublishAckMqtt5OutMessage( - messageId, + testMessageId, PublishAckReasonCode.NOT_AUTHORIZED, reasonString, - userProperties) + testUserProperties) when: def typeAndFlags = outMessage.messageTypeAndFlags() byte type = NumberUtils.getHighByteBits(typeAndFlags); @@ -30,10 +30,12 @@ class PublishAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - reader.exception() == null - reader.reasonCode() == PublishAckReasonCode.NOT_AUTHORIZED - reader.messageId() == messageId - reader.userProperties() == userProperties - reader.reason() == reasonString + with(reader) { + exception() == null + reasonCode() == PublishAckReasonCode.NOT_AUTHORIZED + messageId() == testMessageId + userProperties() == testUserProperties + reason() == reasonString + } } } 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 ebcb6abd..e48529a1 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 @@ -10,7 +10,7 @@ class PublishCompleteMqtt311OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: - def outMessage = new PublishCompleteMqtt311OutMessage(messageId) + def outMessage = new PublishCompleteMqtt311OutMessage(testMessageId) when: def typeAndFlags = outMessage.messageTypeAndFlags() byte type = NumberUtils.getHighByteBits(typeAndFlags); @@ -26,9 +26,11 @@ class PublishCompleteMqtt311OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - reader.reasonCode() == PublishCompletedReasonCode.SUCCESS - reader.messageId() == messageId - reader.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - reader.reason() == null + with(reader) { + reasonCode() == PublishCompletedReasonCode.SUCCESS + messageId() == testMessageId + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + reason() == null + } } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishCompleteMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishCompleteMqtt5OutMessageTest.groovy index 5eea2268..12dd9f58 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishCompleteMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishCompleteMqtt5OutMessageTest.groovy @@ -11,9 +11,9 @@ class PublishCompleteMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: def outMessage = new PublishCompleteMqtt5OutMessage( - messageId, + testMessageId, PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND, - userProperties, + testUserProperties, reasonString) when: def typeAndFlags = outMessage.messageTypeAndFlags() @@ -30,9 +30,11 @@ class PublishCompleteMqtt5OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - reader.reasonCode() == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND - reader.messageId() == messageId - reader.userProperties() == userProperties - reader.reason() == reasonString + with(reader) { + reasonCode() == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND + messageId() == testMessageId + userProperties() == testUserProperties + reason() == reasonString + } } } 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 4d9bb5ff..561ea9d2 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 @@ -11,7 +11,7 @@ class PublishMqtt311OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: def outMessage = new PublishMqtt311OutMessage( - messageId, + testMessageId, QoS.EXACTLY_ONCE, true, true, @@ -31,17 +31,19 @@ class PublishMqtt311OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - reader.exception() == null - reader.messageId() == messageId - reader.qos() == QoS.EXACTLY_ONCE - reader.retain() - reader.duplicate() - reader.payload() == publishPayload - reader.rawTopicName() == publishTopic.rawTopic() - reader.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + with(reader) { + exception() == null + messageId() == testMessageId + qos() == QoS.EXACTLY_ONCE + retain() + duplicate() + payload() == publishPayload + rawTopicName() == publishTopic.rawTopic() + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } when: def outMessage2 = new PublishMqtt311OutMessage( - messageId, + testMessageId, QoS.AT_MOST_ONCE, false, false, @@ -60,13 +62,15 @@ class PublishMqtt311OutMessageTest extends BaseMqttOutMessageTest { def result2 = reader2.read(defaultMqtt311Connection, dataBuffer2, dataBuffer2.limit()) then: result2 - reader2.exception() == null - reader2.messageId() == 0 - reader2.qos() == QoS.AT_MOST_ONCE - !reader2.retain() - !reader2.duplicate() - reader2.payload() == publishPayload - reader2.rawTopicName() == publishTopic.rawTopic() - reader2.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + with(reader2) { + exception() == null + messageId() == 0 + qos() == QoS.AT_MOST_ONCE + !retain() + !duplicate() + payload() == publishPayload + rawTopicName() == publishTopic.rawTopic() + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + } } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishMqtt5OutMessageTest.groovy index 9f2aae6d..46bdbb81 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishMqtt5OutMessageTest.groovy @@ -12,17 +12,17 @@ class PublishMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: def outMessage = new PublishMqtt5OutMessage( - messageId, + testMessageId, QoS.EXACTLY_ONCE, true, true, publishTopic, publishPayload, - topicAlias, + testTopicAlias, PayloadFormat.BINARY, - responseTopic, - correlationData, - userProperties) + testResponseTopic, + testCorrelationData, + testUserProperties) when: def typeAndFlags = outMessage.messageTypeAndFlags() byte type = NumberUtils.getHighByteBits(typeAndFlags); @@ -37,31 +37,33 @@ class PublishMqtt5OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - reader.exception() == null - reader.messageId() == messageId - reader.qos() == QoS.EXACTLY_ONCE - reader.retain() - reader.duplicate() - reader.payload() == publishPayload - reader.rawTopicName() == publishTopic.rawTopic() - reader.userProperties() == userProperties - reader.topicAlias() == topicAlias - reader.payloadFormat() == PayloadFormat.BINARY - reader.rawResponseTopicName() == responseTopic.rawTopic() - reader.correlationData() == correlationData + with(reader) { + exception() == null + messageId() == testMessageId + qos() == QoS.EXACTLY_ONCE + retain() + duplicate() + payload() == publishPayload + rawTopicName() == publishTopic.rawTopic() + userProperties() == testUserProperties + topicAlias() == testTopicAlias + payloadFormat() == PayloadFormat.BINARY + rawResponseTopicName() == testResponseTopic.rawTopic() + correlationData() == testCorrelationData + } when: def outMessage2 = new PublishMqtt5OutMessage( - messageId, + testMessageId, QoS.AT_MOST_ONCE, false, false, publishTopic, publishPayload, - topicAlias, + testTopicAlias, PayloadFormat.UTF8_STRING, - responseTopic, - correlationData, - userProperties) + testResponseTopic, + testCorrelationData, + testUserProperties) def typeAndFlags2 = outMessage2.messageTypeAndFlags() byte type2 = NumberUtils.getHighByteBits(typeAndFlags2); byte info2 = NumberUtils.getLowByteBits(typeAndFlags2); @@ -75,17 +77,19 @@ class PublishMqtt5OutMessageTest extends BaseMqttOutMessageTest { def result2 = reader2.read(defaultMqtt5Connection, dataBuffer2, dataBuffer2.limit()) then: result2 - reader2.exception() == null - reader2.messageId() == 0 - reader2.qos() == QoS.AT_MOST_ONCE - !reader2.retain() - !reader2.duplicate() - reader2.payload() == publishPayload - reader2.rawTopicName() == publishTopic.rawTopic() - reader2.userProperties() == userProperties - reader2.topicAlias() == topicAlias - reader2.payloadFormat() == PayloadFormat.UTF8_STRING - reader2.rawResponseTopicName() == responseTopic.rawTopic() - reader2.correlationData() == correlationData + with(reader2) { + exception() == null + messageId() == 0 + qos() == QoS.AT_MOST_ONCE + !retain() + !duplicate() + payload() == publishPayload + rawTopicName() == publishTopic.rawTopic() + userProperties() == testUserProperties + topicAlias() == testTopicAlias + payloadFormat() == PayloadFormat.UTF8_STRING + rawResponseTopicName() == testResponseTopic.rawTopic() + correlationData() == testCorrelationData + } } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReceivedMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReceivedMqtt311OutMessageTest.groovy index 027738f2..267db84c 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReceivedMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReceivedMqtt311OutMessageTest.groovy @@ -11,7 +11,7 @@ class PublishReceivedMqtt311OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: - def outMessage = new PublishReceivedMqtt311OutMessage(messageId) + def outMessage = new PublishReceivedMqtt311OutMessage(testMessageId) when: def typeAndFlags = outMessage.messageTypeAndFlags() byte type = NumberUtils.getHighByteBits(typeAndFlags); @@ -27,9 +27,11 @@ class PublishReceivedMqtt311OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - reader.reasonCode() == PublishReceivedReasonCode.SUCCESS - reader.messageId() == messageId - reader.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - reader.reason() == null + with(reader) { + reasonCode() == PublishReceivedReasonCode.SUCCESS + messageId() == testMessageId + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + reason() == null + } } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReceivedMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReceivedMqtt5OutMessageTest.groovy index 1e1d316f..9573eca1 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReceivedMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/PublishReceivedMqtt5OutMessageTest.groovy @@ -11,9 +11,9 @@ class PublishReceivedMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: def outMessage = new PublishReceivedMqtt5OutMessage( - messageId, + testMessageId, PublishReceivedReasonCode.UNSPECIFIED_ERROR, - userProperties, + testUserProperties, reasonString) when: def typeAndFlags = outMessage.messageTypeAndFlags() @@ -30,9 +30,11 @@ class PublishReceivedMqtt5OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - reader.reasonCode() == PublishReceivedReasonCode.UNSPECIFIED_ERROR - reader.messageId() == messageId - reader.userProperties() == userProperties - reader.reason() == reasonString + with(reader) { + reasonCode() == PublishReceivedReasonCode.UNSPECIFIED_ERROR + messageId() == testMessageId + userProperties() == testUserProperties + reason() == reasonString + } } } 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 b606e4ea..3ecba96d 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 @@ -10,7 +10,7 @@ class PublishReleaseMqtt311OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: - def outMessage = new PublishReleaseMqtt311OutMessage(messageId) + def outMessage = new PublishReleaseMqtt311OutMessage(testMessageId) when: def typeAndFlags = outMessage.messageTypeAndFlags() byte type = NumberUtils.getHighByteBits(typeAndFlags); @@ -26,9 +26,11 @@ class PublishReleaseMqtt311OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - reader.reasonCode() == PublishReleaseReasonCode.SUCCESS - reader.messageId() == messageId - reader.userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES - reader.reason() == null + with(reader) { + reasonCode() == PublishReleaseReasonCode.SUCCESS + messageId() == testMessageId + userProperties() == MqttOutMessage.EMPTY_USER_PROPERTIES + reason() == null + } } } 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 23c1973b..85ca60ff 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 @@ -11,9 +11,9 @@ class PublishReleaseMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: def outMessage = new PublishReleaseMqtt5OutMessage( - messageId, + testMessageId, PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND, - userProperties, + testUserProperties, reasonString) when: def typeAndFlags = outMessage.messageTypeAndFlags() @@ -30,9 +30,11 @@ class PublishReleaseMqtt5OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - reader.reasonCode() == PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND - reader.messageId() == messageId - reader.userProperties() == userProperties - reader.reason() == reasonString + with(reader) { + reasonCode() == PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND + messageId() == testMessageId + userProperties() == testUserProperties + reason() == reasonString + } } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessageTest.groovy index b723a097..020bf9ec 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessageTest.groovy @@ -10,7 +10,7 @@ class SubscribeAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: - def outMessage = new SubscribeAckMqtt311OutMessage(messageId, subscribeAckReasonCodes) + def outMessage = new SubscribeAckMqtt311OutMessage(testMessageId, subscribeAckReasonCodes) when: def typeAndFlags = outMessage.messageTypeAndFlags() byte type = NumberUtils.getHighByteBits(typeAndFlags); @@ -28,7 +28,7 @@ class SubscribeAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { result reader.exception() == null reader.reasonCodes() == subscribeAckReasonCodes - reader.messageId() == messageId + reader.messageId() == testMessageId reader.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES reader.reason() == null } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessageTest.groovy index e972a311..0c5048e8 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessageTest.groovy @@ -10,9 +10,9 @@ class SubscribeAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: def outMessage = new SubscribeAckMqtt5OutMessage( - messageId, + testMessageId, subscribeAckReasonCodes, - userProperties, + testUserProperties, reasonString) when: def typeAndFlags = outMessage.messageTypeAndFlags() @@ -31,8 +31,8 @@ class SubscribeAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { result reader.exception() == null reader.reasonCodes() == subscribeAckReasonCodes - reader.messageId() == messageId - reader.userProperties() == userProperties + reader.messageId() == testMessageId + reader.userProperties() == testUserProperties reader.reason() == reasonString } } 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 3f720e36..6ce85591 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 @@ -38,7 +38,7 @@ class SubscribeMqtt5OutMessageTest extends BaseMqttOutMessageTest { def outMessage = new SubscribeMqtt5OutMessage( 1, subscriptions, - userProperties, + testUserProperties, MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET) when: def typeAndFlags = outMessage.messageTypeAndFlags() @@ -57,13 +57,13 @@ class SubscribeMqtt5OutMessageTest extends BaseMqttOutMessageTest { result inMessage.messageId() == 1 inMessage.subscriptions() == requestedSubscriptions - inMessage.userProperties() == userProperties + inMessage.userProperties() == testUserProperties inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_IS_NOT_SET when: def outMessage2 = new SubscribeMqtt5OutMessage( 25, subscriptions, - userProperties, + testUserProperties, 35) def dataBuffer2 = BufferUtils.prepareBuffer(512) { outMessage2.write(defaultMqtt5Connection, it) @@ -74,7 +74,7 @@ class SubscribeMqtt5OutMessageTest extends BaseMqttOutMessageTest { result2 inMessage2.messageId() == 25 inMessage2.subscriptions() == requestedSubscriptions - inMessage2.userProperties() == userProperties + inMessage2.userProperties() == testUserProperties inMessage2.subscriptionId() == 35 } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt311OutMessageTest.groovy index 67794210..df85fa24 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt311OutMessageTest.groovy @@ -10,7 +10,7 @@ class UnsubscribeAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: - def outMessage = new UnsubscribeAckMqtt311OutMessage(messageId) + def outMessage = new UnsubscribeAckMqtt311OutMessage(testMessageId) when: def typeAndFlags = outMessage.messageTypeAndFlags() byte type = NumberUtils.getHighByteBits(typeAndFlags); @@ -27,7 +27,7 @@ class UnsubscribeAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCodes() == UnsubscribeAckMqttInMessage.EMPTY_REASON_CODES - reader.messageId() == messageId + reader.messageId() == testMessageId reader.userProperties() == MqttInMessage.EMPTY_USER_PROPERTIES reader.reason() == null } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt5OutMessageTest.groovy index fc0840e8..cde6c18b 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt5OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt5OutMessageTest.groovy @@ -10,9 +10,9 @@ class UnsubscribeAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write message correctly"() { given: def outMessage = new UnsubscribeAckMqtt5OutMessage( - messageId, + testMessageId, unsubscribeAckReasonCodes, - userProperties, + testUserProperties, reasonString) when: def typeAndFlags = outMessage.messageTypeAndFlags() @@ -30,8 +30,8 @@ class UnsubscribeAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCodes() == unsubscribeAckReasonCodes - reader.messageId() == messageId - reader.userProperties() == userProperties + reader.messageId() == testMessageId + reader.userProperties() == testUserProperties reader.reason() == reasonString } } diff --git a/network/src/testFixtures/groovy/javasabr/mqtt/network/NetworkUnitSpecification.groovy b/network/src/testFixtures/groovy/javasabr/mqtt/network/NetworkUnitSpecification.groovy index c8d7da76..9d99d42a 100644 --- a/network/src/testFixtures/groovy/javasabr/mqtt/network/NetworkUnitSpecification.groovy +++ b/network/src/testFixtures/groovy/javasabr/mqtt/network/NetworkUnitSpecification.groovy @@ -37,13 +37,13 @@ class NetworkUnitSpecification extends UnitSpecification { public static final willRetain = false public static final mqtt311ClientId = "testMqtt311ClientId" public static final mqtt5ClientId = "testMqtt5ClientId" - public static final messageId = 1234 as short + public static final testMessageId = 1234 as short public static final userName = "testUser" public static final userPassword = "testPassword".getBytes(StandardCharsets.UTF_8) public static final keepAlive = 120 public static final sessionExpiryInterval = 300 - public static final messageExpiryInterval = 60 - public static final topicAlias = 252 + public static final testMessageExpiryInterval = 60 + public static final testTopicAlias = 252 public static final receiveMaxPublishes = 10 public static final maxMessageSize = 1024 public static final maxStringLength = 256 @@ -60,7 +60,7 @@ class NetworkUnitSpecification extends UnitSpecification { public static final authData = "testAuthData".getBytes(StandardCharsets.UTF_8) public static final reasonString = "reasonString" public static final publishTopic = TopicName.valueOf("publish/Topic") - public static final responseTopic = TopicName.valueOf("response/Topic") + public static final testResponseTopic = TopicName.valueOf("response/Topic") public static final topicFilter = "topic/Filter" public static final topicFilter1Obj311 = Subscription.minimal(TopicFilter.valueOf(topicFilter), QoS.AT_LEAST_ONCE) public static final topicFilter1Obj5 = new Subscription( @@ -84,7 +84,7 @@ class NetworkUnitSpecification extends UnitSpecification { public static final topicFilter4 = "topic/Filter4" public static final serverReference = "serverReference" - public static final contentType = "application/json" + public static final testContentType = "application/json" public static final subscribeAckReasonCodes = Array.typed( SubscribeAckReasonCode, SubscribeAckReasonCode.GRANTED_QOS_1, @@ -97,18 +97,18 @@ class NetworkUnitSpecification extends UnitSpecification { UnsubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR, UnsubscribeAckReasonCode.UNSPECIFIED_ERROR) - public static final userProperties = Array.typed( + public static final testUserProperties = Array.typed( StringPair, new StringPair("key1", "val1"), new StringPair("key2", "val2"), new StringPair("key3", "val3")) - public static final subscriptionIds = IntArray.of(subscriptionId, subscriptionId2) + public static final testSubscriptionIds = IntArray.of(subscriptionId, subscriptionId2) public static final topicFilters = Array.of(topicFilter, topicFilter2) public static final subscriptionsObj311 = Array.of(topicFilter1Obj311, topicFilter2Obj311) public static final topicFiltersObj5 = Array.of(topicFilter1Obj5, topicFilter2Obj5) public static final publishPayload = "publishPayload".getBytes(StandardCharsets.UTF_8) - public static final correlationData = "correlationData".getBytes(StandardCharsets.UTF_8) + public static final testCorrelationData = "correlationData".getBytes(StandardCharsets.UTF_8) public static final clientIdGenerator = new AtomicInteger(1) @Shared From bdc6380b9bc71f9db6ef75a075e3b15b2f56dc1c Mon Sep 17 00:00:00 2001 From: javasabr Date: Sun, 7 Dec 2025 21:22:54 +0100 Subject: [PATCH 12/19] fix on code review --- .../impl/Qos2MqttPublishOutMessageHandler.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java index be50d50d..86bbf3b9 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java @@ -15,6 +15,7 @@ import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; import lombok.CustomLog; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @CustomLog @@ -43,13 +44,9 @@ protected boolean handleReceivedTrackableMessageImpl( handlePublishComplete(user, session, message, trackedMessageMeta, publishComplete); return true; } else { - MqttMessageType expectedMessageType = MqttMessageType.PUBLISH_RECEIVED; - if (trackedMessageMeta != null && trackedMessageMeta.messageType() == MqttMessageType.PUBLISH_RELEASE) { - expectedMessageType = MqttMessageType.PUBLISH_COMPLETE; - } log.warning(user.clientId(), message.messageType(), message.messageId(), "[%s] Not expected message type:[%s] for messageId:[%d]"::formatted); - handleNotExpectedResponseMessage(user, message, expectedMessageType); + handleNotExpectedResponseMessage(user, message, calculateExpectedMessageType(trackedMessageMeta)); return true; } } @@ -141,4 +138,12 @@ private void handlePublishComplete( MessageTacker messageTacker = session.outMessageTracker(); messageTacker.remove(messageId); } + + private static MqttMessageType calculateExpectedMessageType(@Nullable TrackedMessageMeta trackedMessageMeta) { + if (trackedMessageMeta != null && trackedMessageMeta.messageType() == MqttMessageType.PUBLISH_RELEASE) { + return MqttMessageType.PUBLISH_COMPLETE; + } + // by default, we expect 'PUBLISH_RECEIVED' + return MqttMessageType.PUBLISH_RECEIVED; + } } From eaf8d3a9a5edbc4aa3f5cea148ef1850776723f3 Mon Sep 17 00:00:00 2001 From: javasabr Date: Mon, 8 Dec 2025 06:06:24 +0100 Subject: [PATCH 13/19] start working on integration ACL --- .../main/java/javasabr/mqtt/service/AclService.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 core-service/src/main/java/javasabr/mqtt/service/AclService.java diff --git a/core-service/src/main/java/javasabr/mqtt/service/AclService.java b/core-service/src/main/java/javasabr/mqtt/service/AclService.java new file mode 100644 index 00000000..cfa04a8e --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/AclService.java @@ -0,0 +1,10 @@ +package javasabr.mqtt.service; + +import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.acl.Operation; +import javasabr.mqtt.model.topic.AbstractTopic; + +public interface AclService { + + boolean authorize(MqttUser mqttUser, Operation operation, AbstractTopic topic); +} From 7cd86f5a71a48d1a368d55f491a5b7d4e4173221 Mon Sep 17 00:00:00 2001 From: javasabr Date: Mon, 8 Dec 2025 18:12:09 +0100 Subject: [PATCH 14/19] working on updating message validation --- .../config/MqttBrokerSpringConfig.java | 31 ++++++++++- .../javasabr/mqtt/service/AclService.java | 8 +-- .../mqtt/service/impl/DisabledAclService.java | 18 +++++++ .../FieldsValidatedMqttInMessageHandler.java | 54 +++++++++++++++++++ .../impl/PublishMqttInMessageHandler.java | 43 +++++++-------- .../MqttInMessageFieldValidator.java | 10 ++++ ...ishPayloadMqttInMessageFieldValidator.java | 21 ++++++++ ...PublishQosMqttInMessageFieldValidator.java | 40 ++++++++++++++ 8 files changed, 199 insertions(+), 26 deletions(-) create mode 100644 core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAclService.java create mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/FieldsValidatedMqttInMessageHandler.java create mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/validator/MqttInMessageFieldValidator.java create mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishPayloadMqttInMessageFieldValidator.java create mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishQosMqttInMessageFieldValidator.java diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 731fb107..667dfc12 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -2,6 +2,7 @@ import java.net.InetSocketAddress; import java.util.Collection; +import java.util.List; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.QoS; @@ -9,7 +10,9 @@ import javasabr.mqtt.network.MqttConnectionFactory; import javasabr.mqtt.network.handler.NetworkMqttUserReleaseHandler; import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; +import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.network.user.NetworkMqttUserFactory; +import javasabr.mqtt.service.AclService; import javasabr.mqtt.service.AuthenticationService; import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.ConnectionService; @@ -26,6 +29,7 @@ import javasabr.mqtt.service.impl.DefaultPublishDeliveringService; import javasabr.mqtt.service.impl.DefaultPublishReceivingService; import javasabr.mqtt.service.impl.DefaultTopicService; +import javasabr.mqtt.service.impl.DisabledAclService; import javasabr.mqtt.service.impl.ExternalNetworkMqttUserFactory; import javasabr.mqtt.service.impl.FileCredentialsSource; import javasabr.mqtt.service.impl.InMemoryClientIdRegistry; @@ -44,6 +48,9 @@ import javasabr.mqtt.service.message.out.factory.Mqtt311MessageOutFactory; import javasabr.mqtt.service.message.out.factory.Mqtt5MessageOutFactory; import javasabr.mqtt.service.message.out.factory.MqttMessageOutFactory; +import javasabr.mqtt.service.message.validator.MqttInMessageFieldValidator; +import javasabr.mqtt.service.message.validator.PublishPayloadMqttInMessageFieldValidator; +import javasabr.mqtt.service.message.validator.PublishQosMqttInMessageFieldValidator; import javasabr.mqtt.service.publish.handler.MqttPublishInMessageHandler; import javasabr.mqtt.service.publish.handler.MqttPublishOutMessageHandler; import javasabr.mqtt.service.publish.handler.impl.Qos0MqttPublishInMessageHandler; @@ -96,6 +103,11 @@ AuthenticationService authenticationService( @Value("${authentication.allow.anonymous:false}") boolean allowAnonymousAuth) { return new SimpleAuthenticationService(credentialSource, allowAnonymousAuth); } + + @Bean + AclService aclService() { + return new DisabledAclService(); + } @Bean SubscriptionService subscriptionService() { @@ -147,16 +159,31 @@ MqttInMessageHandler publishAckMqttInMessageHandler(MessageOutFactoryService mes MqttInMessageHandler publishCompleteMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { return new PublishCompleteMqttInMessageHandler(messageOutFactoryService); } + + @Bean + PublishPayloadMqttInMessageFieldValidator publishPayloadMqttInMessageFieldValidator() { + return new PublishPayloadMqttInMessageFieldValidator(); + } + + @Bean + PublishQosMqttInMessageFieldValidator publishQosMqttInMessageFieldValidator( + MessageOutFactoryService messageOutFactoryService) { + return new PublishQosMqttInMessageFieldValidator(messageOutFactoryService); + } @Bean MqttInMessageHandler publishMqttInMessageHandler( PublishReceivingService publishReceivingService, MessageOutFactoryService messageOutFactoryService, - TopicService topicService) { + TopicService topicService, + AclService aclService, + List> fieldValidators) { return new PublishMqttInMessageHandler( publishReceivingService, messageOutFactoryService, - topicService); + topicService, + aclService, + fieldValidators); } @Bean diff --git a/core-service/src/main/java/javasabr/mqtt/service/AclService.java b/core-service/src/main/java/javasabr/mqtt/service/AclService.java index cfa04a8e..9db4098c 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/AclService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/AclService.java @@ -1,10 +1,12 @@ package javasabr.mqtt.service; import javasabr.mqtt.model.MqttUser; -import javasabr.mqtt.model.acl.Operation; -import javasabr.mqtt.model.topic.AbstractTopic; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; public interface AclService { - boolean authorize(MqttUser mqttUser, Operation operation, AbstractTopic topic); + boolean authorizePublish(MqttUser mqttUser, TopicName topicName); + + boolean authorizeSubscribe(MqttUser mqttUser, TopicFilter topicFilter); } diff --git a/core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAclService.java b/core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAclService.java new file mode 100644 index 00000000..769cd480 --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAclService.java @@ -0,0 +1,18 @@ +package javasabr.mqtt.service.impl; + +import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.mqtt.service.AclService; + +public class DisabledAclService implements AclService { + @Override + public boolean authorizePublish(MqttUser mqttUser, TopicName topicName) { + return true; + } + + @Override + public boolean authorizeSubscribe(MqttUser mqttUser, TopicFilter topicFilter) { + return true; + } +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/FieldsValidatedMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/FieldsValidatedMqttInMessageHandler.java new file mode 100644 index 00000000..5afc1cf3 --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/FieldsValidatedMqttInMessageHandler.java @@ -0,0 +1,54 @@ +package javasabr.mqtt.service.message.handler.impl; + +import java.util.List; +import javasabr.mqtt.network.MqttConnection; +import javasabr.mqtt.network.message.in.MqttInMessage; +import javasabr.mqtt.network.session.NetworkMqttSession; +import javasabr.mqtt.network.user.NetworkMqttUser; +import javasabr.mqtt.service.MessageOutFactoryService; +import javasabr.mqtt.service.message.validator.MqttInMessageFieldValidator; +import lombok.AccessLevel; +import lombok.CustomLog; +import lombok.experimental.FieldDefaults; + +@CustomLog +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public abstract class FieldsValidatedMqttInMessageHandler + extends AbstractMqttInMessageHandler { + + MqttInMessageFieldValidator[] fieldValidators; + + protected FieldsValidatedMqttInMessageHandler( + Class expectedUser, + Class expectedMessage, + MessageOutFactoryService messageOutFactoryService, + List> fieldValidators) { + super(expectedUser, expectedMessage, messageOutFactoryService); + //noinspection unchecked + this.fieldValidators = fieldValidators.toArray(MqttInMessageFieldValidator[]::new); + } + + @Override + protected void processValidMessage(MqttConnection connection, U user, M message) { + for (MqttInMessageFieldValidator fieldValidator : fieldValidators) { + if (!fieldValidator.validate(connection, user, message)) { + return; + } + } + super.processValidMessage(connection, user, message); + } + + @Override + protected void processValidMessage( + MqttConnection connection, + U user, + NetworkMqttSession session, + M message) { + for (MqttInMessageFieldValidator fieldValidator : fieldValidators) { + if (!fieldValidator.validate(connection, user, message)) { + return; + } + } + super.processValidMessage(connection, user, session, message); + } +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java index af2a2d6e..ebc30f75 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java @@ -1,10 +1,10 @@ package javasabr.mqtt.service.message.handler.impl; +import java.util.List; import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttProtocolErrors; import javasabr.mqtt.model.PayloadFormat; -import javasabr.mqtt.model.QoS; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.reason.code.DisconnectReasonCode; @@ -16,9 +16,11 @@ import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.network.message.out.MqttOutMessage; import javasabr.mqtt.network.session.NetworkMqttSession; +import javasabr.mqtt.service.AclService; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.PublishReceivingService; import javasabr.mqtt.service.TopicService; +import javasabr.mqtt.service.message.validator.MqttInMessageFieldValidator; import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; import lombok.CustomLog; @@ -27,18 +29,22 @@ @CustomLog @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class PublishMqttInMessageHandler - extends AbstractMqttInMessageHandler { + extends FieldsValidatedMqttInMessageHandler { PublishReceivingService publishReceivingService; TopicService topicService; + AclService aclService; public PublishMqttInMessageHandler( PublishReceivingService publishReceivingService, MessageOutFactoryService messageOutFactoryService, - TopicService topicService) { - super(ExternalNetworkMqttUser.class, PublishMqttInMessage.class, messageOutFactoryService); + TopicService topicService, + AclService aclService, + List> fieldValidators) { + super(ExternalNetworkMqttUser.class, PublishMqttInMessage.class, messageOutFactoryService, fieldValidators); this.publishReceivingService = publishReceivingService; this.topicService = topicService; + this.aclService = aclService; } @Override @@ -111,6 +117,11 @@ protected void processValidMessage( topicName = topicNameByAlias; } + if (!aclService.authorizePublish(user, topicName)) { + handleNotAuthorize(user); + return; + } + byte[] payload = publishMessage.payload(); //noinspection DataFlowIssue everything is already validated @@ -137,19 +148,8 @@ private boolean validateBaseFields( MqttConnection connection, ExternalNetworkMqttUser user, PublishMqttInMessage publishMessage) { - byte[] payload = publishMessage.payload(); - if (payload == null) { - log.warning(user.clientId(), "[%s] Unexpected missed payload"::formatted); - return false; - } - - QoS requestedQos = publishMessage.qos(); + MqttClientConnectionConfig connectionConfig = connection.clientConnectionConfig(); - if (connectionConfig.maxQos().isLowerThan(requestedQos)) { - log.warning(user.clientId(), requestedQos, "[%s] Requested QoS:[%s] is not supported"::formatted); - handleNotSupportedQos(user); - return false; - } boolean retain = publishMessage.retain(); if (retain && !connectionConfig.retainAvailable()) { @@ -175,11 +175,6 @@ private boolean validateBaseFields( return true; } - private void handleNotSupportedQos(ExternalNetworkMqttUser user) { - user.closeWithReason(messageOutFactoryService - .resolveFactory(user) - .newDisconnect(user, DisconnectReasonCode.QOS_NOT_SUPPORTED)); - } private void handleNotSupportedRetain(ExternalNetworkMqttUser user) { user.closeWithReason(messageOutFactoryService @@ -222,6 +217,12 @@ private void handleInvalidMessageExpiryInterval(ExternalNetworkMqttUser user) { user.closeWithReason(response); } + private void handleNotAuthorize(ExternalNetworkMqttUser user) { + user.closeWithReason(messageOutFactoryService + .resolveFactory(user) + .newDisconnect(user, DisconnectReasonCode.NOT_AUTHORIZED)); + } + private void handleInvalidTopicName( ExternalNetworkMqttUser user, NetworkMqttSession session, diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/validator/MqttInMessageFieldValidator.java b/core-service/src/main/java/javasabr/mqtt/service/message/validator/MqttInMessageFieldValidator.java new file mode 100644 index 00000000..9296dcb6 --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/message/validator/MqttInMessageFieldValidator.java @@ -0,0 +1,10 @@ +package javasabr.mqtt.service.message.validator; + +import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.network.MqttConnection; +import javasabr.mqtt.network.message.in.MqttInMessage; + +public abstract class MqttInMessageFieldValidator { + + public abstract boolean validate(MqttConnection connection, U user, M message); +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishPayloadMqttInMessageFieldValidator.java b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishPayloadMqttInMessageFieldValidator.java new file mode 100644 index 00000000..4dfc6e8e --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishPayloadMqttInMessageFieldValidator.java @@ -0,0 +1,21 @@ +package javasabr.mqtt.service.message.validator; + +import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.network.MqttConnection; +import javasabr.mqtt.network.message.in.PublishMqttInMessage; +import lombok.CustomLog; + +@CustomLog +public class PublishPayloadMqttInMessageFieldValidator extends + MqttInMessageFieldValidator { + + @Override + public boolean validate(MqttConnection connection, MqttUser user, PublishMqttInMessage message) { + byte[] payload = message.payload(); + if (payload == null) { + log.warning(user.clientId(), "[%s] Missed payload"::formatted); + return false; + } + return true; + } +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishQosMqttInMessageFieldValidator.java b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishQosMqttInMessageFieldValidator.java new file mode 100644 index 00000000..4519b4c7 --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishQosMqttInMessageFieldValidator.java @@ -0,0 +1,40 @@ +package javasabr.mqtt.service.message.validator; + +import javasabr.mqtt.model.MqttClientConnectionConfig; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import javasabr.mqtt.network.MqttConnection; +import javasabr.mqtt.network.message.in.PublishMqttInMessage; +import javasabr.mqtt.network.user.NetworkMqttUser; +import javasabr.mqtt.service.MessageOutFactoryService; +import lombok.AccessLevel; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +@CustomLog +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class PublishQosMqttInMessageFieldValidator extends + MqttInMessageFieldValidator { + + MessageOutFactoryService messageOutFactoryService; + + @Override + public boolean validate(MqttConnection connection, NetworkMqttUser user, PublishMqttInMessage message) { + QoS requestedQos = message.qos(); + MqttClientConnectionConfig connectionConfig = connection.clientConnectionConfig(); + if (connectionConfig.maxQos().isLowerThan(requestedQos)) { + log.warning(user.clientId(), requestedQos, "[%s] Requested QoS:[%s] is not supported"::formatted); + handleNotSupportedQos(user); + return false; + } + return true; + } + + private void handleNotSupportedQos(NetworkMqttUser user) { + user.closeWithReason(messageOutFactoryService + .resolveFactory(user) + .newDisconnect(user, DisconnectReasonCode.QOS_NOT_SUPPORTED)); + } +} From 8f0945bf36b7f14d14e0d0431acaab0a248a21d1 Mon Sep 17 00:00:00 2001 From: javasabr Date: Mon, 8 Dec 2025 18:43:33 +0100 Subject: [PATCH 15/19] working on updating message validation --- .../config/MqttBrokerSpringConfig.java | 19 +++++- .../FieldsValidatedMqttInMessageHandler.java | 30 +++++---- .../impl/PublishMqttInMessageHandler.java | 64 ++----------------- .../MqttInMessageFieldValidator.java | 6 +- ...ryIntervalMqttInMessageFieldValidator.java | 50 +++++++++++++++ ...ishPayloadMqttInMessageFieldValidator.java | 43 +++++++++++-- ...PublishQosMqttInMessageFieldValidator.java | 13 +++- ...lishRetainMqttInMessageFieldValidator.java | 46 +++++++++++++ .../Qos2MqttPublishOutMessageHandler.java | 1 - .../IntegrationServiceSpecification.groovy | 20 +++++- .../PublishMqttInMessageHandlerTest.groovy | 62 +++++++++++++----- .../mqtt/model/MqttProtocolErrors.java | 10 +-- 12 files changed, 259 insertions(+), 105 deletions(-) create mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishMessageExpiryIntervalMqttInMessageFieldValidator.java create mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishRetainMqttInMessageFieldValidator.java diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 667dfc12..a5cb3ac7 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -49,8 +49,10 @@ import javasabr.mqtt.service.message.out.factory.Mqtt5MessageOutFactory; import javasabr.mqtt.service.message.out.factory.MqttMessageOutFactory; import javasabr.mqtt.service.message.validator.MqttInMessageFieldValidator; +import javasabr.mqtt.service.message.validator.PublishMessageExpiryIntervalMqttInMessageFieldValidator; import javasabr.mqtt.service.message.validator.PublishPayloadMqttInMessageFieldValidator; import javasabr.mqtt.service.message.validator.PublishQosMqttInMessageFieldValidator; +import javasabr.mqtt.service.message.validator.PublishRetainMqttInMessageFieldValidator; import javasabr.mqtt.service.publish.handler.MqttPublishInMessageHandler; import javasabr.mqtt.service.publish.handler.MqttPublishOutMessageHandler; import javasabr.mqtt.service.publish.handler.impl.Qos0MqttPublishInMessageHandler; @@ -161,8 +163,9 @@ MqttInMessageHandler publishCompleteMqttInMessageHandler(MessageOutFactoryServic } @Bean - PublishPayloadMqttInMessageFieldValidator publishPayloadMqttInMessageFieldValidator() { - return new PublishPayloadMqttInMessageFieldValidator(); + PublishPayloadMqttInMessageFieldValidator publishPayloadMqttInMessageFieldValidator( + MessageOutFactoryService messageOutFactoryService) { + return new PublishPayloadMqttInMessageFieldValidator(messageOutFactoryService); } @Bean @@ -170,6 +173,18 @@ PublishQosMqttInMessageFieldValidator publishQosMqttInMessageFieldValidator( MessageOutFactoryService messageOutFactoryService) { return new PublishQosMqttInMessageFieldValidator(messageOutFactoryService); } + + @Bean + PublishRetainMqttInMessageFieldValidator publishRetainMqttInMessageFieldValidator( + MessageOutFactoryService messageOutFactoryService) { + return new PublishRetainMqttInMessageFieldValidator(messageOutFactoryService); + } + + @Bean + PublishMessageExpiryIntervalMqttInMessageFieldValidator publishMessageExpiryIntervalMqttInMessageFieldValidator( + MessageOutFactoryService messageOutFactoryService) { + return new PublishMessageExpiryIntervalMqttInMessageFieldValidator(messageOutFactoryService); + } @Bean MqttInMessageHandler publishMqttInMessageHandler( diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/FieldsValidatedMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/FieldsValidatedMqttInMessageHandler.java index 5afc1cf3..967628b9 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/FieldsValidatedMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/FieldsValidatedMqttInMessageHandler.java @@ -1,5 +1,6 @@ package javasabr.mqtt.service.message.handler.impl; +import java.util.Comparator; import java.util.List; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.in.MqttInMessage; @@ -25,30 +26,37 @@ protected FieldsValidatedMqttInMessageHandler( List> fieldValidators) { super(expectedUser, expectedMessage, messageOutFactoryService); //noinspection unchecked - this.fieldValidators = fieldValidators.toArray(MqttInMessageFieldValidator[]::new); + this.fieldValidators = fieldValidators + .stream() + .sorted(Comparator.comparingInt((MqttInMessageFieldValidator validator) -> validator.order())) + .toArray(MqttInMessageFieldValidator[]::new); } @Override - protected void processValidMessage(MqttConnection connection, U user, M message) { + protected final void processValidMessage(MqttConnection connection, U user, M message) { for (MqttInMessageFieldValidator fieldValidator : fieldValidators) { - if (!fieldValidator.validate(connection, user, message)) { + if (fieldValidator.isNotValid(connection, user, message)) { return; } } - super.processValidMessage(connection, user, message); + processMessageWithValidFields(connection, user, message); } + protected void processMessageWithValidFields(MqttConnection connection, U user, M message) {} + @Override - protected void processValidMessage( - MqttConnection connection, - U user, - NetworkMqttSession session, - M message) { + protected final void processValidMessage(MqttConnection connection, U user, NetworkMqttSession session, M message) { for (MqttInMessageFieldValidator fieldValidator : fieldValidators) { - if (!fieldValidator.validate(connection, user, message)) { + if (fieldValidator.isNotValid(connection, user, message)) { return; } } - super.processValidMessage(connection, user, session, message); + processMessageWithValidFields(connection, user, session, message); } + + protected void processMessageWithValidFields( + MqttConnection connection, + U user, + NetworkMqttSession session, + M message) {} } diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java index ebc30f75..98bf05da 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java @@ -4,7 +4,6 @@ import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttProtocolErrors; -import javasabr.mqtt.model.PayloadFormat; import javasabr.mqtt.model.message.MqttMessageType; import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.reason.code.DisconnectReasonCode; @@ -53,16 +52,12 @@ public MqttMessageType messageType() { } @Override - protected void processValidMessage( + protected void processMessageWithValidFields( MqttConnection connection, ExternalNetworkMqttUser user, NetworkMqttSession session, PublishMqttInMessage publishMessage) { - - if (!validateBaseFields(connection, user, publishMessage)) { - return; - } - + String rawResponseTopicName = publishMessage.rawResponseTopicName(); TopicName responseTopicName = null; if (rawResponseTopicName != null) { @@ -144,44 +139,6 @@ protected void processValidMessage( publishReceivingService.processPublish(user, publish); } - private boolean validateBaseFields( - MqttConnection connection, - ExternalNetworkMqttUser user, - PublishMqttInMessage publishMessage) { - - MqttClientConnectionConfig connectionConfig = connection.clientConnectionConfig(); - - boolean retain = publishMessage.retain(); - if (retain && !connectionConfig.retainAvailable()) { - log.warning(user.clientId(), "[%s] 'RETAIN' option is not supported"::formatted); - handleNotSupportedRetain(user); - return false; - } - - PayloadFormat payloadFormat = publishMessage.payloadFormat(); - if (payloadFormat == PayloadFormat.INVALID) { - log.warning(user.clientId(), "[%s] Provided invalid PayloadFormat"::formatted); - handleInvalidPayloadFormat(user); - return false; - } - - long messageExpiryInterval = publishMessage.messageExpiryInterval(); - if (messageExpiryInterval != MqttProperties.MESSAGE_EXPIRY_INTERVAL_IS_NOT_SET - && messageExpiryInterval < MqttProperties.MESSAGE_EXPIRY_INTERVAL_MIN) { - log.warning(user.clientId(), "[%s] Provided invalid MessageExpiryInterval"::formatted); - handleInvalidMessageExpiryInterval(user); - return false; - } - return true; - } - - - private void handleNotSupportedRetain(ExternalNetworkMqttUser user) { - user.closeWithReason(messageOutFactoryService - .resolveFactory(user) - .newDisconnect(user, DisconnectReasonCode.RETAIN_NOT_SUPPORTED)); - } - private void handleNotProvidedTopicName(ExternalNetworkMqttUser user) { MqttOutMessage response = messageOutFactoryService .resolveFactory(user) @@ -196,27 +153,14 @@ private void handleInvalidTopicAlias(ExternalNetworkMqttUser user) { user.closeWithReason(response); } - private void handleInvalidPayloadFormat(ExternalNetworkMqttUser user) { - MqttOutMessage response = messageOutFactoryService - .resolveFactory(user) - .newDisconnect(user, DisconnectReasonCode.PROTOCOL_ERROR, MqttProtocolErrors.PROVIDED_INVALID_PAYLOAD_FORMAT); - user.closeWithReason(response); - } - private void handleInvalidResponseTopicName(ExternalNetworkMqttUser user) { MqttOutMessage response = messageOutFactoryService .resolveFactory(user) - .newDisconnect(user, DisconnectReasonCode.PROTOCOL_ERROR, MqttProtocolErrors.INVALID_RESPONSE_TOPIC_NAME); - user.closeWithReason(response); - } - - private void handleInvalidMessageExpiryInterval(ExternalNetworkMqttUser user) { - MqttOutMessage response = messageOutFactoryService - .resolveFactory(user) - .newDisconnect(user, DisconnectReasonCode.PROTOCOL_ERROR, MqttProtocolErrors.PROVIDED_INVALID_MESSAGE_EXPIRY_INTERVAL); + .newDisconnect(user, DisconnectReasonCode.PROTOCOL_ERROR, MqttProtocolErrors.PROVIDED_INVALID_RESPONSE_TOPIC_NAME); user.closeWithReason(response); } + private void handleNotAuthorize(ExternalNetworkMqttUser user) { user.closeWithReason(messageOutFactoryService .resolveFactory(user) diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/validator/MqttInMessageFieldValidator.java b/core-service/src/main/java/javasabr/mqtt/service/message/validator/MqttInMessageFieldValidator.java index 9296dcb6..6bcd29f2 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/validator/MqttInMessageFieldValidator.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/validator/MqttInMessageFieldValidator.java @@ -6,5 +6,9 @@ public abstract class MqttInMessageFieldValidator { - public abstract boolean validate(MqttConnection connection, U user, M message); + public abstract boolean isNotValid(MqttConnection connection, U user, M message); + + public int order() { + return 0; + } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishMessageExpiryIntervalMqttInMessageFieldValidator.java b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishMessageExpiryIntervalMqttInMessageFieldValidator.java new file mode 100644 index 00000000..3092bcba --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishMessageExpiryIntervalMqttInMessageFieldValidator.java @@ -0,0 +1,50 @@ +package javasabr.mqtt.service.message.validator; + +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttProtocolErrors; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import javasabr.mqtt.network.MqttConnection; +import javasabr.mqtt.network.message.in.PublishMqttInMessage; +import javasabr.mqtt.network.user.NetworkMqttUser; +import javasabr.mqtt.service.MessageOutFactoryService; +import lombok.AccessLevel; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +@CustomLog +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class PublishMessageExpiryIntervalMqttInMessageFieldValidator extends + MqttInMessageFieldValidator { + + public static final int ORDER = PublishRetainMqttInMessageFieldValidator.ORDER + 1; + + MessageOutFactoryService messageOutFactoryService; + + @Override + public boolean isNotValid(MqttConnection connection, NetworkMqttUser user, PublishMqttInMessage message) { + long messageExpiryInterval = message.messageExpiryInterval(); + if (messageExpiryInterval != MqttProperties.MESSAGE_EXPIRY_INTERVAL_IS_NOT_SET + && messageExpiryInterval < MqttProperties.MESSAGE_EXPIRY_INTERVAL_MIN) { + log.warning(user.clientId(), "[%s] Provided invalid MessageExpiryInterval"::formatted); + handleInvalidMessageExpiryInterval(user); + return true; + } + return false; + } + + @Override + public int order() { + return ORDER; + } + + private void handleInvalidMessageExpiryInterval(NetworkMqttUser user) { + user.closeWithReason(messageOutFactoryService + .resolveFactory(user) + .newDisconnect( + user, + DisconnectReasonCode.PROTOCOL_ERROR, + MqttProtocolErrors.PROVIDED_INVALID_MESSAGE_EXPIRY_INTERVAL)); + } +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishPayloadMqttInMessageFieldValidator.java b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishPayloadMqttInMessageFieldValidator.java index 4dfc6e8e..63b8ce46 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishPayloadMqttInMessageFieldValidator.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishPayloadMqttInMessageFieldValidator.java @@ -1,21 +1,54 @@ package javasabr.mqtt.service.message.validator; -import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.MqttProtocolErrors; +import javasabr.mqtt.model.PayloadFormat; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.in.PublishMqttInMessage; +import javasabr.mqtt.network.user.NetworkMqttUser; +import javasabr.mqtt.service.MessageOutFactoryService; +import lombok.AccessLevel; import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; @CustomLog +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class PublishPayloadMqttInMessageFieldValidator extends - MqttInMessageFieldValidator { + MqttInMessageFieldValidator { + public static final int ORDER = 10; + + MessageOutFactoryService messageOutFactoryService; + @Override - public boolean validate(MqttConnection connection, MqttUser user, PublishMqttInMessage message) { + public boolean isNotValid(MqttConnection connection, NetworkMqttUser user, PublishMqttInMessage message) { byte[] payload = message.payload(); if (payload == null) { log.warning(user.clientId(), "[%s] Missed payload"::formatted); - return false; + return true; + } + PayloadFormat payloadFormat = message.payloadFormat(); + if (payloadFormat == PayloadFormat.INVALID) { + log.warning(user.clientId(), "[%s] Provided invalid PayloadFormat"::formatted); + handleInvalidPayloadFormat(user); + return true; } - return true; + return false; + } + + @Override + public int order() { + return ORDER; + } + + private void handleInvalidPayloadFormat(NetworkMqttUser user) { + user.closeWithReason(messageOutFactoryService + .resolveFactory(user) + .newDisconnect( + user, + DisconnectReasonCode.PROTOCOL_ERROR, + MqttProtocolErrors.PROVIDED_INVALID_PAYLOAD_FORMAT)); } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishQosMqttInMessageFieldValidator.java b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishQosMqttInMessageFieldValidator.java index 4519b4c7..2076b711 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishQosMqttInMessageFieldValidator.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishQosMqttInMessageFieldValidator.java @@ -17,19 +17,26 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class PublishQosMqttInMessageFieldValidator extends MqttInMessageFieldValidator { + + public static final int ORDER = PublishPayloadMqttInMessageFieldValidator.ORDER + 1; MessageOutFactoryService messageOutFactoryService; @Override - public boolean validate(MqttConnection connection, NetworkMqttUser user, PublishMqttInMessage message) { + public boolean isNotValid(MqttConnection connection, NetworkMqttUser user, PublishMqttInMessage message) { QoS requestedQos = message.qos(); MqttClientConnectionConfig connectionConfig = connection.clientConnectionConfig(); if (connectionConfig.maxQos().isLowerThan(requestedQos)) { log.warning(user.clientId(), requestedQos, "[%s] Requested QoS:[%s] is not supported"::formatted); handleNotSupportedQos(user); - return false; + return true; } - return true; + return false; + } + + @Override + public int order() { + return ORDER; } private void handleNotSupportedQos(NetworkMqttUser user) { diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishRetainMqttInMessageFieldValidator.java b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishRetainMqttInMessageFieldValidator.java new file mode 100644 index 00000000..6fff81d2 --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishRetainMqttInMessageFieldValidator.java @@ -0,0 +1,46 @@ +package javasabr.mqtt.service.message.validator; + +import javasabr.mqtt.model.MqttClientConnectionConfig; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import javasabr.mqtt.network.MqttConnection; +import javasabr.mqtt.network.message.in.PublishMqttInMessage; +import javasabr.mqtt.network.user.NetworkMqttUser; +import javasabr.mqtt.service.MessageOutFactoryService; +import lombok.AccessLevel; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +@CustomLog +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class PublishRetainMqttInMessageFieldValidator extends + MqttInMessageFieldValidator { + + public static final int ORDER = PublishQosMqttInMessageFieldValidator.ORDER + 1; + + MessageOutFactoryService messageOutFactoryService; + + @Override + public boolean isNotValid(MqttConnection connection, NetworkMqttUser user, PublishMqttInMessage message) { + MqttClientConnectionConfig connectionConfig = connection.clientConnectionConfig(); + boolean retain = message.retain(); + if (retain && !connectionConfig.retainAvailable()) { + log.warning(user.clientId(), "[%s] 'RETAIN' option is not supported"::formatted); + handleNotSupportedRetain(user); + return true; + } + return false; + } + + @Override + public int order() { + return ORDER; + } + + private void handleNotSupportedRetain(NetworkMqttUser user) { + user.closeWithReason(messageOutFactoryService + .resolveFactory(user) + .newDisconnect(user, DisconnectReasonCode.RETAIN_NOT_SUPPORTED)); + } +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java index 86bbf3b9..040a8721 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java @@ -15,7 +15,6 @@ import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; import lombok.CustomLog; -import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @CustomLog diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy index f1054d75..1ebc1843 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy @@ -1,6 +1,5 @@ package javasabr.mqtt.service - import javasabr.mqtt.model.MqttClientConnectionConfig import javasabr.mqtt.model.MqttProperties import javasabr.mqtt.model.MqttServerConnectionConfig @@ -8,14 +7,22 @@ import javasabr.mqtt.model.MqttVersion import javasabr.mqtt.model.QoS import javasabr.mqtt.network.MqttConnection import javasabr.mqtt.network.handler.NetworkMqttUserReleaseHandler +import javasabr.mqtt.network.message.in.PublishMqttInMessage +import javasabr.mqtt.network.user.NetworkMqttUser import javasabr.mqtt.service.impl.DefaultMessageOutFactoryService import javasabr.mqtt.service.impl.DefaultPublishDeliveringService import javasabr.mqtt.service.impl.DefaultPublishReceivingService import javasabr.mqtt.service.impl.DefaultTopicService +import javasabr.mqtt.service.impl.DisabledAclService import javasabr.mqtt.service.impl.InMemorySubscriptionService import javasabr.mqtt.service.message.handler.impl.PublishReleaseMqttInMessageHandler import javasabr.mqtt.service.message.out.factory.Mqtt311MessageOutFactory import javasabr.mqtt.service.message.out.factory.Mqtt5MessageOutFactory +import javasabr.mqtt.service.message.validator.MqttInMessageFieldValidator +import javasabr.mqtt.service.message.validator.PublishMessageExpiryIntervalMqttInMessageFieldValidator +import javasabr.mqtt.service.message.validator.PublishPayloadMqttInMessageFieldValidator +import javasabr.mqtt.service.message.validator.PublishQosMqttInMessageFieldValidator +import javasabr.mqtt.service.message.validator.PublishRetainMqttInMessageFieldValidator import javasabr.mqtt.service.publish.handler.impl.Qos0MqttPublishInMessageHandler import javasabr.mqtt.service.publish.handler.impl.Qos0MqttPublishOutMessageHandler import javasabr.mqtt.service.publish.handler.impl.Qos1MqttPublishInMessageHandler @@ -87,6 +94,17 @@ abstract class IntegrationServiceSpecification extends Specification { @Shared def defaultMqttSessionService = new InMemoryMqttSessionService(60_000); + + @Shared + def disabledAclService = new DisabledAclService() + + @Shared + List> publishInFieldValidators = [ + new PublishRetainMqttInMessageFieldValidator(defaultMessageOutFactoryService), + new PublishQosMqttInMessageFieldValidator(defaultMessageOutFactoryService), + new PublishPayloadMqttInMessageFieldValidator(defaultMessageOutFactoryService), + new PublishMessageExpiryIntervalMqttInMessageFieldValidator(defaultMessageOutFactoryService), + ] @Shared def defaultExternalServerConnectionConfig = new MqttServerConnectionConfig( diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandlerTest.groovy index cc47a675..46c6014b 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandlerTest.groovy @@ -23,7 +23,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def expectedTopicAlias = 5 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser @@ -51,7 +53,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser mqttUser.returnCompletedFeatures(false) @@ -77,7 +81,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser mqttUser.session(null) when: @@ -96,7 +102,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser when: def publishMessage = new PublishMqttInMessage(0b0110_0010 as byte) {{ @@ -117,7 +125,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser mqttUser @@ -145,7 +155,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser when: def publishMessage = new PublishMqttInMessage(0 as byte) @@ -162,7 +174,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser when: @@ -186,7 +200,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser when: @@ -208,7 +224,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser when: @@ -231,7 +249,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser when: @@ -254,7 +274,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser when: @@ -267,7 +289,7 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { then: def disconnectReason = mqttUser.nextSentMessage(DisconnectMqtt5OutMessage) disconnectReason.reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR - disconnectReason.reason() == MqttProtocolErrors.INVALID_RESPONSE_TOPIC_NAME + disconnectReason.reason() == MqttProtocolErrors.PROVIDED_INVALID_RESPONSE_TOPIC_NAME disconnectReason.serverReference() == null } @@ -277,7 +299,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser when: @@ -299,7 +323,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser when: 'topic alias is too high' @@ -334,7 +360,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser when: @@ -357,7 +385,9 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { def messageHandler = new PublishMqttInMessageHandler( publishReceivingService, defaultMessageOutFactoryService, - defaultTopicService) + defaultTopicService, + disabledAclService, + publishInFieldValidators) def expectedMessageId = 15 def mqttUser = mqttConnection.user() as TestExternalNetworkMqttUser when: diff --git a/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java b/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java index f59e317f..41c1ba91 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java @@ -2,11 +2,11 @@ public interface MqttProtocolErrors { String NO_ANY_TOPIC_FILTERS = "Not provided any information about 'Topic Filters'"; - String NO_ANY_TOPIC_NANE = "Not provided any information about TopicName"; + String NO_ANY_TOPIC_NANE = "Not provided any information about `Topic Name`"; //String INVALID_TOPIC_ALIAS = "Provided invalid TopicAlias"; - String PROVIDED_INVALID_PAYLOAD_FORMAT = "Provided invalid PayloadFormat"; - String PROVIDED_INVALID_MESSAGE_EXPIRY_INTERVAL = "Provided invalid MessageExpiryInterval"; + String PROVIDED_INVALID_PAYLOAD_FORMAT = "Provided invalid 'Payload Format Indicator'"; + String PROVIDED_INVALID_MESSAGE_EXPIRY_INTERVAL = "Provided invalid 'Message Expiry Interval'"; String PROVIDED_INVALID_SESSION_EXPIRY_INTERVAL = "Provided invalid 'Session Expiry Interval'"; String PROVIDED_INVALID_RECEIVED_MAX_PUBLISHES = "Provided invalid 'Receive Maximum'"; String PROVIDED_INVALID_MAX_QOS = "Provided invalid 'Maximum QoS'"; @@ -17,8 +17,8 @@ public interface MqttProtocolErrors { String PROVIDED_INVALID_SUBSCRIPTION_IDENTIFIERS_AVAILABLE = "Provided invalid 'Subscription Identifiers Available'"; String PROVIDED_INVALID_SHARED_SUBSCRIPTION_AVAILABLE = "Provided invalid 'Shared Subscription Available'"; String PROVIDED_INVALID_SERVER_KEEP_ALIVE = "Provided invalid 'Server Keep Alive'"; - - String INVALID_RESPONSE_TOPIC_NAME = "Provided invalid ResponseTopicName"; + String PROVIDED_INVALID_RESPONSE_TOPIC_NAME = "Provided invalid 'Response Topic''"; + String UNSUPPORTED_QOS_OR_RETAIN_HANDLING = "Provided unsupported 'QoS' or 'RetainHandling'"; String MISSED_REQUIRED_MESSAGE_ID = "'Packet Identifier' must be presented'"; String NOT_EXPECTED_MESSAGE_ID = "'Packet Identifier' must be zero'"; From 08244ddef59a1cf6258f72e375d0871272423ea5 Mon Sep 17 00:00:00 2001 From: javasabr Date: Mon, 8 Dec 2025 19:51:12 +0100 Subject: [PATCH 16/19] working on updating message validation --- .../config/MqttBrokerSpringConfig.java | 14 ++ .../impl/PublishMqttInMessageHandler.java | 148 +++++++----------- ...ponseTopicMqttInMessageFieldValidator.java | 51 ++++++ ...TopicAliasMqttInMessageFieldValidator.java | 64 ++++++++ .../IntegrationServiceSpecification.groovy | 4 + .../PublishMqttInMessageHandlerTest.groovy | 2 +- .../mqtt/model/MqttProtocolErrors.java | 2 +- 7 files changed, 191 insertions(+), 94 deletions(-) create mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishResponseTopicMqttInMessageFieldValidator.java create mode 100644 core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishTopicAliasMqttInMessageFieldValidator.java diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index a5cb3ac7..765c7a4e 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -52,7 +52,9 @@ import javasabr.mqtt.service.message.validator.PublishMessageExpiryIntervalMqttInMessageFieldValidator; import javasabr.mqtt.service.message.validator.PublishPayloadMqttInMessageFieldValidator; import javasabr.mqtt.service.message.validator.PublishQosMqttInMessageFieldValidator; +import javasabr.mqtt.service.message.validator.PublishResponseTopicMqttInMessageFieldValidator; import javasabr.mqtt.service.message.validator.PublishRetainMqttInMessageFieldValidator; +import javasabr.mqtt.service.message.validator.PublishTopicAliasMqttInMessageFieldValidator; import javasabr.mqtt.service.publish.handler.MqttPublishInMessageHandler; import javasabr.mqtt.service.publish.handler.MqttPublishOutMessageHandler; import javasabr.mqtt.service.publish.handler.impl.Qos0MqttPublishInMessageHandler; @@ -185,6 +187,18 @@ PublishMessageExpiryIntervalMqttInMessageFieldValidator publishMessageExpiryInte MessageOutFactoryService messageOutFactoryService) { return new PublishMessageExpiryIntervalMqttInMessageFieldValidator(messageOutFactoryService); } + + @Bean + PublishResponseTopicMqttInMessageFieldValidator publishResponseTopicMqttInMessageFieldValidator( + MessageOutFactoryService messageOutFactoryService) { + return new PublishResponseTopicMqttInMessageFieldValidator(messageOutFactoryService); + } + + @Bean + PublishTopicAliasMqttInMessageFieldValidator publishTopicAliasMqttInMessageFieldValidator( + MessageOutFactoryService messageOutFactoryService) { + return new PublishTopicAliasMqttInMessageFieldValidator(messageOutFactoryService); + } @Bean MqttInMessageHandler publishMqttInMessageHandler( diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java index 98bf05da..53d2c709 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java @@ -1,7 +1,6 @@ package javasabr.mqtt.service.message.handler.impl; import java.util.List; -import javasabr.mqtt.model.MqttClientConnectionConfig; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttProtocolErrors; import javasabr.mqtt.model.message.MqttMessageType; @@ -13,7 +12,6 @@ import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.network.message.in.PublishMqttInMessage; -import javasabr.mqtt.network.message.out.MqttOutMessage; import javasabr.mqtt.network.session.NetworkMqttSession; import javasabr.mqtt.service.AclService; import javasabr.mqtt.service.MessageOutFactoryService; @@ -24,6 +22,7 @@ import lombok.AccessLevel; import lombok.CustomLog; import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; @CustomLog @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @@ -58,72 +57,22 @@ protected void processMessageWithValidFields( NetworkMqttSession session, PublishMqttInMessage publishMessage) { - String rawResponseTopicName = publishMessage.rawResponseTopicName(); - TopicName responseTopicName = null; - if (rawResponseTopicName != null) { - if (!TopicValidator.validateTopicName(rawResponseTopicName)) { - log.warning(user.clientId(), rawResponseTopicName, "[%s] Provided invalid response TopicName:[%s]"::formatted); - handleInvalidResponseTopicName(user); - return; - } - responseTopicName = topicService.createTopicName(user, rawResponseTopicName); - } - - MqttClientConnectionConfig connectionConfig = connection.clientConnectionConfig(); - int topicAliasMaxValue = connectionConfig.topicAliasMaxValue(); - TopicNameMapping topicNameMapping = session.topicNameMapping(); - TopicName topicNameByAlias = null; - - String rawTopicName = publishMessage.rawTopicName(); - boolean providedRawTopicName = !StringUtils.isEmpty(rawTopicName); - int topicAlias = publishMessage.topicAlias(); - - if (!providedRawTopicName) { - if (topicAlias == MqttProperties.TOPIC_ALIAS_NOT_SET) { - log.warning(user.clientId(), "[%s] Not provided any information about TopicName"::formatted); - handleNotProvidedTopicName(user); - return; - } else if (topicAlias < MqttProperties.TOPIC_ALIAS_MIN || topicAlias > topicAliasMaxValue) { - log.warning(user.clientId(), topicAlias, "[%s] Provided invalid TopicAlias:[%d]"::formatted); - handleInvalidTopicAlias(user); - return; - } - topicNameByAlias = topicNameMapping.resolve(topicAlias); - if (topicNameByAlias == null) { - log.warning(user.clientId(), topicAlias, "[%s] Unknown TopicAlias:[%d]"::formatted); - handleNotProvidedTopicName(user); - return; - } - } - - TopicName topicName; - - if (providedRawTopicName) { - if (!TopicValidator.validateTopicName(rawTopicName)) { - handleInvalidTopicName(user, session, publishMessage); - log.warning(user.clientId(), publishMessage.rawTopicName(), "[%s] TopicName:[%s] is invalid"::formatted); - return; - } - topicName = topicService.createTopicName(user, rawTopicName); - if (topicAlias != MqttProperties.TOPIC_ALIAS_NOT_SET) { - topicNameMapping.update(topicAlias, topicName); - } - } else { - topicName = topicNameByAlias; - } - - if (!aclService.authorizePublish(user, topicName)) { + TopicName finalTopicName = resolveFinalTopicName(user, session, publishMessage); + if (finalTopicName == null) { + return; + } else if (!aclService.authorizePublish(user, finalTopicName)) { handleNotAuthorize(user); return; } byte[] payload = publishMessage.payload(); + TopicName responseTopicName = resolveResponseTopic(user, publishMessage); //noinspection DataFlowIssue everything is already validated Publish publish = new Publish( publishMessage.messageId(), publishMessage.qos(), - topicName, + finalTopicName, responseTopicName, payload, publishMessage.duplicate(), @@ -132,58 +81,73 @@ protected void processMessageWithValidFields( publishMessage.subscriptionIds(), publishMessage.correlationData(), publishMessage.messageExpiryInterval(), - topicAlias, + publishMessage.topicAlias(), publishMessage.payloadFormat(), publishMessage.userProperties()); publishReceivingService.processPublish(user, publish); } + + @Nullable + private TopicName resolveFinalTopicName( + ExternalNetworkMqttUser user, + NetworkMqttSession session, + PublishMqttInMessage publishMessage) { - private void handleNotProvidedTopicName(ExternalNetworkMqttUser user) { - MqttOutMessage response = messageOutFactoryService - .resolveFactory(user) - .newDisconnect(user, DisconnectReasonCode.PROTOCOL_ERROR, MqttProtocolErrors.NO_ANY_TOPIC_NANE); - user.closeWithReason(response); + TopicNameMapping topicNameMapping = session.topicNameMapping(); + String rawTopicName = publishMessage.rawTopicName(); + boolean providedRawTopicName = !StringUtils.isEmpty(rawTopicName); + int topicAlias = publishMessage.topicAlias(); + + TopicName topicNameByAlias; + TopicName finalTopicName; + + if (!providedRawTopicName) { + topicNameByAlias = topicNameMapping.resolve(topicAlias); + if (topicNameByAlias == null) { + log.warning(user.clientId(), topicAlias, "[%s] Unknown TopicAlias:[%d]"::formatted); + handleNotProvidedTopicName(user); + return null; + } + finalTopicName = topicNameByAlias; + } else { + if (!TopicValidator.validateTopicName(rawTopicName)) { + handleInvalidTopicName(user); + log.warning(user.clientId(), rawTopicName, "[%s] TopicName:[%s] is invalid"::formatted); + return null; + } + finalTopicName = topicService.createTopicName(user, rawTopicName); + if (topicAlias != MqttProperties.TOPIC_ALIAS_NOT_SET) { + topicNameMapping.update(topicAlias, finalTopicName); + } + } + return finalTopicName; } - private void handleInvalidTopicAlias(ExternalNetworkMqttUser user) { - MqttOutMessage response = messageOutFactoryService - .resolveFactory(user) - .newDisconnect(user, DisconnectReasonCode.TOPIC_ALIAS_INVALID); - user.closeWithReason(response); + @Nullable + private TopicName resolveResponseTopic(ExternalNetworkMqttUser user, PublishMqttInMessage publishMessage) { + String rawResponseTopicName = publishMessage.rawResponseTopicName(); + if (rawResponseTopicName != null) { + return topicService.createTopicName(user, rawResponseTopicName); + } + return null; } - private void handleInvalidResponseTopicName(ExternalNetworkMqttUser user) { - MqttOutMessage response = messageOutFactoryService + private void handleNotProvidedTopicName(ExternalNetworkMqttUser user) { + user.closeWithReason(messageOutFactoryService .resolveFactory(user) - .newDisconnect(user, DisconnectReasonCode.PROTOCOL_ERROR, MqttProtocolErrors.PROVIDED_INVALID_RESPONSE_TOPIC_NAME); - user.closeWithReason(response); + .newDisconnect(user, DisconnectReasonCode.PROTOCOL_ERROR, MqttProtocolErrors.NO_ANY_TOPIC_NANE)); } - - + private void handleNotAuthorize(ExternalNetworkMqttUser user) { user.closeWithReason(messageOutFactoryService .resolveFactory(user) .newDisconnect(user, DisconnectReasonCode.NOT_AUTHORIZED)); } - private void handleInvalidTopicName( - ExternalNetworkMqttUser user, - NetworkMqttSession session, - PublishMqttInMessage publishMessage) { - int messagedId = publishMessage.messageId(); - MqttOutMessage response = messageOutFactoryService + private void handleInvalidTopicName(ExternalNetworkMqttUser user) { + user.closeWithReason(messageOutFactoryService .resolveFactory(user) - .newDisconnect(user, DisconnectReasonCode.TOPIC_NAME_INVALID); - // without messageId we do not need to clean it - if (messagedId == MqttProperties.MESSAGE_ID_IS_NOT_SET) { - user.closeWithReason(response); - return; - } - user - .closeWithReason(response) - .thenAccept(_ -> session - .inMessageTracker() - .remove(messagedId)); + .newDisconnect(user, DisconnectReasonCode.TOPIC_NAME_INVALID)); } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishResponseTopicMqttInMessageFieldValidator.java b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishResponseTopicMqttInMessageFieldValidator.java new file mode 100644 index 00000000..299d16d7 --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishResponseTopicMqttInMessageFieldValidator.java @@ -0,0 +1,51 @@ +package javasabr.mqtt.service.message.validator; + +import javasabr.mqtt.model.MqttProtocolErrors; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import javasabr.mqtt.model.topic.TopicValidator; +import javasabr.mqtt.network.MqttConnection; +import javasabr.mqtt.network.message.in.PublishMqttInMessage; +import javasabr.mqtt.network.user.NetworkMqttUser; +import javasabr.mqtt.service.MessageOutFactoryService; +import lombok.AccessLevel; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +@CustomLog +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class PublishResponseTopicMqttInMessageFieldValidator extends + MqttInMessageFieldValidator { + + public static final int ORDER = PublishMessageExpiryIntervalMqttInMessageFieldValidator.ORDER + 1; + + MessageOutFactoryService messageOutFactoryService; + + @Override + public boolean isNotValid(MqttConnection connection, NetworkMqttUser user, PublishMqttInMessage message) { + String rawResponseTopicName = message.rawResponseTopicName(); + if (rawResponseTopicName != null) { + if (!TopicValidator.validateTopicName(rawResponseTopicName)) { + log.warning(user.clientId(), rawResponseTopicName, "[%s] Provided invalid ResponseTopic:[%s]"::formatted); + handleInvalidResponseTopicName(user); + return true; + } + } + return false; + } + + @Override + public int order() { + return ORDER; + } + + private void handleInvalidResponseTopicName(NetworkMqttUser user) { + user.closeWithReason(messageOutFactoryService + .resolveFactory(user) + .newDisconnect( + user, + DisconnectReasonCode.PROTOCOL_ERROR, + MqttProtocolErrors.PROVIDED_INVALID_RESPONSE_TOPIC)); + } +} diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishTopicAliasMqttInMessageFieldValidator.java b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishTopicAliasMqttInMessageFieldValidator.java new file mode 100644 index 00000000..9e8d023f --- /dev/null +++ b/core-service/src/main/java/javasabr/mqtt/service/message/validator/PublishTopicAliasMqttInMessageFieldValidator.java @@ -0,0 +1,64 @@ +package javasabr.mqtt.service.message.validator; + +import javasabr.mqtt.model.MqttClientConnectionConfig; +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttProtocolErrors; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import javasabr.mqtt.network.MqttConnection; +import javasabr.mqtt.network.message.in.PublishMqttInMessage; +import javasabr.mqtt.network.user.NetworkMqttUser; +import javasabr.mqtt.service.MessageOutFactoryService; +import javasabr.rlib.common.util.StringUtils; +import lombok.AccessLevel; +import lombok.CustomLog; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +@CustomLog +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class PublishTopicAliasMqttInMessageFieldValidator extends + MqttInMessageFieldValidator { + + public static final int ORDER = PublishResponseTopicMqttInMessageFieldValidator.ORDER + 1; + + MessageOutFactoryService messageOutFactoryService; + + @Override + public boolean isNotValid(MqttConnection connection, NetworkMqttUser user, PublishMqttInMessage message) { + boolean providedRawTopicName = !StringUtils.isEmpty(message.rawTopicName()); + if (!providedRawTopicName) { + int topicAlias = message.topicAlias(); + if (topicAlias == MqttProperties.TOPIC_ALIAS_NOT_SET) { + log.warning(user.clientId(), "[%s] Not provided any information about TopicName"::formatted); + handleNotProvidedTopicName(user); + return true; + } + MqttClientConnectionConfig connectionConfig = connection.clientConnectionConfig(); + int topicAliasMaxValue = connectionConfig.topicAliasMaxValue(); + if (topicAlias < MqttProperties.TOPIC_ALIAS_MIN || topicAlias > topicAliasMaxValue) { + log.warning(user.clientId(), topicAlias, "[%s] Provided invalid TopicAlias:[%d]"::formatted); + handleInvalidTopicAlias(user); + return true; + } + } + return false; + } + + @Override + public int order() { + return ORDER; + } + + private void handleNotProvidedTopicName(NetworkMqttUser user) { + user.closeWithReason(messageOutFactoryService + .resolveFactory(user) + .newDisconnect(user, DisconnectReasonCode.PROTOCOL_ERROR, MqttProtocolErrors.NO_ANY_TOPIC_NANE)); + } + + private void handleInvalidTopicAlias(NetworkMqttUser user) { + user.closeWithReason(messageOutFactoryService + .resolveFactory(user) + .newDisconnect(user, DisconnectReasonCode.TOPIC_ALIAS_INVALID)); + } +} diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy index 1ebc1843..85ceee67 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy @@ -22,7 +22,9 @@ import javasabr.mqtt.service.message.validator.MqttInMessageFieldValidator import javasabr.mqtt.service.message.validator.PublishMessageExpiryIntervalMqttInMessageFieldValidator import javasabr.mqtt.service.message.validator.PublishPayloadMqttInMessageFieldValidator import javasabr.mqtt.service.message.validator.PublishQosMqttInMessageFieldValidator +import javasabr.mqtt.service.message.validator.PublishResponseTopicMqttInMessageFieldValidator import javasabr.mqtt.service.message.validator.PublishRetainMqttInMessageFieldValidator +import javasabr.mqtt.service.message.validator.PublishTopicAliasMqttInMessageFieldValidator import javasabr.mqtt.service.publish.handler.impl.Qos0MqttPublishInMessageHandler import javasabr.mqtt.service.publish.handler.impl.Qos0MqttPublishOutMessageHandler import javasabr.mqtt.service.publish.handler.impl.Qos1MqttPublishInMessageHandler @@ -104,6 +106,8 @@ abstract class IntegrationServiceSpecification extends Specification { new PublishQosMqttInMessageFieldValidator(defaultMessageOutFactoryService), new PublishPayloadMqttInMessageFieldValidator(defaultMessageOutFactoryService), new PublishMessageExpiryIntervalMqttInMessageFieldValidator(defaultMessageOutFactoryService), + new PublishResponseTopicMqttInMessageFieldValidator(defaultMessageOutFactoryService), + new PublishTopicAliasMqttInMessageFieldValidator(defaultMessageOutFactoryService) ] @Shared diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandlerTest.groovy index 46c6014b..daaca7b1 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandlerTest.groovy @@ -289,7 +289,7 @@ class PublishMqttInMessageHandlerTest extends IntegrationServiceSpecification { then: def disconnectReason = mqttUser.nextSentMessage(DisconnectMqtt5OutMessage) disconnectReason.reasonCode() == DisconnectReasonCode.PROTOCOL_ERROR - disconnectReason.reason() == MqttProtocolErrors.PROVIDED_INVALID_RESPONSE_TOPIC_NAME + disconnectReason.reason() == MqttProtocolErrors.PROVIDED_INVALID_RESPONSE_TOPIC disconnectReason.serverReference() == null } diff --git a/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java b/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java index 41c1ba91..f66d1dc0 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttProtocolErrors.java @@ -17,7 +17,7 @@ public interface MqttProtocolErrors { String PROVIDED_INVALID_SUBSCRIPTION_IDENTIFIERS_AVAILABLE = "Provided invalid 'Subscription Identifiers Available'"; String PROVIDED_INVALID_SHARED_SUBSCRIPTION_AVAILABLE = "Provided invalid 'Shared Subscription Available'"; String PROVIDED_INVALID_SERVER_KEEP_ALIVE = "Provided invalid 'Server Keep Alive'"; - String PROVIDED_INVALID_RESPONSE_TOPIC_NAME = "Provided invalid 'Response Topic''"; + String PROVIDED_INVALID_RESPONSE_TOPIC = "Provided invalid 'Response Topic'"; String UNSUPPORTED_QOS_OR_RETAIN_HANDLING = "Provided unsupported 'QoS' or 'RetainHandling'"; String MISSED_REQUIRED_MESSAGE_ID = "'Packet Identifier' must be presented'"; From b8c4ada3d945fcd4656e2cd54354a167e4ba734f Mon Sep 17 00:00:00 2001 From: javasabr Date: Tue, 9 Dec 2025 18:06:43 +0100 Subject: [PATCH 17/19] continue updating --- .../config/MqttBrokerSpringConfig.java | 12 ++---- .../service/PublishDeliveringService.java | 2 +- .../impl/DefaultPublishDeliveringService.java | 5 +-- .../impl/DefaultPublishReceivingService.java | 3 +- .../impl/AbstractMqttInMessageHandler.java | 4 +- .../impl/PublishMqttInMessageHandler.java | 36 ++++++++--------- .../handler/MqttPublishOutMessageHandler.java | 2 +- .../AbstractMqttPublishInMessageHandler.java | 2 +- .../AbstractMqttPublishOutMessageHandler.java | 10 ++--- .../Qos0MqttPublishOutMessageHandler.java | 7 +--- .../Qos1MqttPublishOutMessageHandler.java | 7 +--- .../impl/Qos2MqttPublishInMessageHandler.java | 13 ++++--- .../Qos2MqttPublishOutMessageHandler.java | 7 +--- ...TrackableMqttPublishOutMessageHandler.java | 39 ++++++++----------- .../impl/InMemoryNetworkMqttSession.java | 2 +- .../IntegrationServiceSpecification.groovy | 6 +-- 16 files changed, 67 insertions(+), 90 deletions(-) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 765c7a4e..0729bbd6 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -255,24 +255,20 @@ ConnectionService externalMqttConnectionService(Collection expectedUserType; - SubscriptionService subscriptionService; MessageOutFactoryService messageOutFactoryService; @Override - public PublishHandlingResult handle(Publish publish, SingleSubscriber subscriber) { + public final void handle(Publish publish, SingleSubscriber subscriber) { MqttUser user = subscriber.resolveUser(); if (!expectedUserType.isInstance(user)) { log.warning(user.clientId(), user.getClass(), "[%s] Not expected user of type:[%s]"::formatted); @@ -55,7 +52,7 @@ protected PublishHandlingResult handleImpl(U user, MqttSession session, Publish } protected void send(U user, Publish publish) { - MqttOutMessage outMessage = messageOutFactoryService + user.sendInBackground(messageOutFactoryService .resolveFactory(user) .newPublish( publish.messageId(), @@ -68,7 +65,6 @@ protected void send(U user, Publish publish) { publish.payloadFormat(), publish.responseTopicName(), publish.correlationData(), - publish.userProperties()); - user.sendInBackground(outMessage); + publish.userProperties())); } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java index 8803aa3a..b7825bc9 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java @@ -6,16 +6,13 @@ import javasabr.mqtt.model.session.MqttSession; import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.service.MessageOutFactoryService; -import javasabr.mqtt.service.SubscriptionService; import org.jspecify.annotations.Nullable; public class Qos0MqttPublishOutMessageHandler extends AbstractMqttPublishOutMessageHandler { - public Qos0MqttPublishOutMessageHandler( - SubscriptionService subscriptionService, - MessageOutFactoryService messageOutFactoryService) { - super(ExternalNetworkMqttUser.class, subscriptionService, messageOutFactoryService); + public Qos0MqttPublishOutMessageHandler(MessageOutFactoryService messageOutFactoryService) { + super(ExternalNetworkMqttUser.class, messageOutFactoryService); } @Override diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java index 072f7dca..38a8ff52 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java @@ -10,17 +10,14 @@ import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.network.message.in.PublishAckMqttInMessage; import javasabr.mqtt.service.MessageOutFactoryService; -import javasabr.mqtt.service.SubscriptionService; import lombok.CustomLog; import org.jspecify.annotations.Nullable; @CustomLog public class Qos1MqttPublishOutMessageHandler extends TrackableMqttPublishOutMessageHandler { - public Qos1MqttPublishOutMessageHandler( - SubscriptionService subscriptionService, - MessageOutFactoryService messageOutFactoryService) { - super(subscriptionService, messageOutFactoryService); + public Qos1MqttPublishOutMessageHandler(MessageOutFactoryService messageOutFactoryService) { + super(messageOutFactoryService); } @Override diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java index 532b0b8f..62632168 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java @@ -143,22 +143,23 @@ private void handleMessageIdIsInUse(ExternalNetworkMqttUser user, int messageId) } private boolean handleReceivedTrackableMessage(MqttUser user, MqttSession session, TrackableMqttMessage message) { - ExternalNetworkMqttUser networkUser = (ExternalNetworkMqttUser) user; + ExternalNetworkMqttUser networkMqttUser = expectedUserType.cast(user); + String clientId = networkMqttUser.clientId(); int messageId = message.messageId(); MessageTacker messageTacker = session.inMessageTracker(); TrackedMessageMeta messageMeta = messageTacker.stored(messageId); if (messageMeta == null) { - log.warning(networkUser.clientId(), messageId, "[%s] No any stored information for messageId:[%d]"::formatted); + log.warning(clientId, messageId, "[%s] No any stored information for messageId:[%d]"::formatted); return true; } if (messageMeta.messageType() != MqttMessageType.PUBLISH) { - log.warning(networkUser.clientId(), messageMeta, messageId, + log.warning(clientId, messageMeta, messageId, "[%s] Not expected tracked message meta:[%s] for messageId:[%d]"::formatted); return true; } else if (!(message instanceof PublishReleaseMqttInMessage release)) { - log.warning(networkUser.clientId(), message, "[%s] Not expected message:[%s]"::formatted); + log.warning(clientId, message.messageType(), "[%s] Not expected message:[%s]"::formatted); return true; } @@ -168,10 +169,10 @@ private boolean handleReceivedTrackableMessage(MqttUser user, MqttSession sessio PublishCompletedReasonCode.SUCCESS); MqttOutMessage response = messageOutFactoryService - .resolveFactory(networkUser) + .resolveFactory(networkMqttUser) .newPublishCompleted(message.messageId(), PublishCompletedReasonCode.SUCCESS); - sendFeedback(networkUser, session, response, messageId); + sendFeedback(networkMqttUser, session, response, messageId); return true; } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java index 040a8721..38c4949c 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java @@ -13,17 +13,14 @@ import javasabr.mqtt.network.message.in.PublishCompleteMqttInMessage; import javasabr.mqtt.network.message.in.PublishReceivedMqttInMessage; import javasabr.mqtt.service.MessageOutFactoryService; -import javasabr.mqtt.service.SubscriptionService; import lombok.CustomLog; import org.jspecify.annotations.Nullable; @CustomLog public class Qos2MqttPublishOutMessageHandler extends TrackableMqttPublishOutMessageHandler { - public Qos2MqttPublishOutMessageHandler( - SubscriptionService subscriptionService, - MessageOutFactoryService messageOutFactoryService) { - super(subscriptionService, messageOutFactoryService); + public Qos2MqttPublishOutMessageHandler(MessageOutFactoryService messageOutFactoryService) { + super(messageOutFactoryService); } @Override diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java index 7977f9a7..6b571d42 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java @@ -15,7 +15,6 @@ import javasabr.mqtt.model.session.TrackedMessageMeta; import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.service.MessageOutFactoryService; -import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.message.out.factory.MqttMessageOutFactory; import javasabr.mqtt.service.publish.handler.PublishHandlingResult; import lombok.AccessLevel; @@ -31,10 +30,8 @@ public abstract class TrackableMqttPublishOutMessageHandler extends TrackableMessageCallback trackableMessageCallback; PublishRetryer publishRetryer; - protected TrackableMqttPublishOutMessageHandler( - SubscriptionService subscriptionService, - MessageOutFactoryService messageOutFactoryService) { - super(ExternalNetworkMqttUser.class, subscriptionService, messageOutFactoryService); + protected TrackableMqttPublishOutMessageHandler(MessageOutFactoryService messageOutFactoryService) { + super(ExternalNetworkMqttUser.class, messageOutFactoryService); this.trackableMessageCallback = this::handleReceivedTrackableMessage; this.publishRetryer = this::retryDelivering; } @@ -51,7 +48,10 @@ protected Publish reconstruct(ExternalNetworkMqttUser user, MqttSession session, } @Override - protected PublishHandlingResult handleImpl(ExternalNetworkMqttUser user, MqttSession session, Publish publish) { + protected final PublishHandlingResult handleImpl( + ExternalNetworkMqttUser user, + MqttSession session, + Publish publish) { // register message id MessageTacker messageTacker = session.outMessageTracker(); messageTacker.add(publish.messageId(), MqttMessageType.PUBLISH); @@ -61,15 +61,13 @@ protected PublishHandlingResult handleImpl(ExternalNetworkMqttUser user, MqttSes return super.handleImpl(user, session, publish); } - protected boolean handleReceivedTrackableMessage( + protected final boolean handleReceivedTrackableMessage( MqttUser user, MqttSession session, TrackableMqttMessage message) { - int messageId = message.messageId(); MessageTacker messageTacker = session.outMessageTracker(); TrackedMessageMeta trackedMessageMeta = messageTacker.stored(messageId); - return handleReceivedTrackableMessageImpl( expectedUserType.cast(user), session, @@ -83,24 +81,21 @@ protected abstract boolean handleReceivedTrackableMessageImpl( TrackableMqttMessage message, @Nullable TrackedMessageMeta trackedMessageMeta); - protected void retryDelivering(MqttUser user, MqttSession session, Publish publish) { - retryDeliveringImpl(expectedUserType.cast(user), session, publish); - } - - protected void retryDeliveringImpl(ExternalNetworkMqttUser user, MqttSession session, Publish publish) { - String clientId = user.clientId(); + protected final void retryDelivering(MqttUser user, MqttSession session, Publish publish) { + ExternalNetworkMqttUser networkMqttUser = expectedUserType.cast(user); + String clientId = networkMqttUser.clientId(); int messageId = publish.messageId(); - TrackedMessageMeta messageMeta = session - .outMessageTracker() - .stored(messageId); - if (messageMeta == null) { + + MessageTacker outMessageTracker = session.outMessageTracker(); + TrackedMessageMeta trackedMessageMeta = outMessageTracker.stored(messageId); + if (trackedMessageMeta == null) { log.warning(clientId, messageId, "[%s] No any stored information for messageId:[%d]"::formatted); - } else if (messageMeta.messageType() != MqttMessageType.PUBLISH) { - log.warning(clientId, messageMeta, messageId, + } else if (trackedMessageMeta.messageType() != MqttMessageType.PUBLISH) { + log.warning(clientId, trackedMessageMeta, messageId, "[%s] Not expected tracked message meta:[%s] for messageId:[%d]"::formatted); } else { log.debug(clientId, messageId, "[%s] Retry to deliver publish:[%s]"::formatted); - send(user, publish.withDuplicated()); + send(networkMqttUser, publish.withDuplicated()); } } diff --git a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java index 9b331f69..080d12ca 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java +++ b/core-service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryNetworkMqttSession.java @@ -14,9 +14,9 @@ import lombok.experimental.FieldDefaults; @CustomLog +@Accessors @ToString(of = "clientId") @EqualsAndHashCode(of = "clientId") -@Accessors(fluent = true, chain = false) @FieldDefaults(level = AccessLevel.PRIVATE) public class InMemoryNetworkMqttSession implements ConfigurableNetworkMqttSession { diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy index 85ceee67..baeb245f 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy @@ -64,9 +64,9 @@ abstract class IntegrationServiceSpecification extends Specification { @Shared def defaultPublishDeliveringService = new DefaultPublishDeliveringService([ - new Qos0MqttPublishOutMessageHandler(defaultSubscriptionService, defaultMessageOutFactoryService), - new Qos1MqttPublishOutMessageHandler(defaultSubscriptionService, defaultMessageOutFactoryService), - new Qos2MqttPublishOutMessageHandler(defaultSubscriptionService, defaultMessageOutFactoryService) + new Qos0MqttPublishOutMessageHandler(defaultMessageOutFactoryService), + new Qos1MqttPublishOutMessageHandler(defaultMessageOutFactoryService), + new Qos2MqttPublishOutMessageHandler(defaultMessageOutFactoryService) ]) @Shared From 2266e331508071993377572cf36c9f8ca50bc082 Mon Sep 17 00:00:00 2001 From: javasabr Date: Tue, 9 Dec 2025 18:11:36 +0100 Subject: [PATCH 18/19] rename ACL service --- .../application/config/MqttBrokerSpringConfig.java | 13 ++++++------- .../{AclService.java => AuthorizationService.java} | 2 +- ...rvice.java => DisabledAuthorizationService.java} | 4 ++-- .../handler/impl/PublishMqttInMessageHandler.java | 10 +++++----- .../service/IntegrationServiceSpecification.groovy | 4 ++-- 5 files changed, 16 insertions(+), 17 deletions(-) rename core-service/src/main/java/javasabr/mqtt/service/{AclService.java => AuthorizationService.java} (88%) rename core-service/src/main/java/javasabr/mqtt/service/impl/{DisabledAclService.java => DisabledAuthorizationService.java} (75%) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 765c7a4e..a4f79d94 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -12,7 +12,7 @@ import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.network.user.NetworkMqttUserFactory; -import javasabr.mqtt.service.AclService; +import javasabr.mqtt.service.AuthorizationService; import javasabr.mqtt.service.AuthenticationService; import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.ConnectionService; @@ -29,7 +29,7 @@ import javasabr.mqtt.service.impl.DefaultPublishDeliveringService; import javasabr.mqtt.service.impl.DefaultPublishReceivingService; import javasabr.mqtt.service.impl.DefaultTopicService; -import javasabr.mqtt.service.impl.DisabledAclService; +import javasabr.mqtt.service.impl.DisabledAuthorizationService; import javasabr.mqtt.service.impl.ExternalNetworkMqttUserFactory; import javasabr.mqtt.service.impl.FileCredentialsSource; import javasabr.mqtt.service.impl.InMemoryClientIdRegistry; @@ -109,8 +109,8 @@ AuthenticationService authenticationService( } @Bean - AclService aclService() { - return new DisabledAclService(); + AuthorizationService authorizationService() { + return new DisabledAuthorizationService(); } @Bean @@ -205,13 +205,12 @@ MqttInMessageHandler publishMqttInMessageHandler( PublishReceivingService publishReceivingService, MessageOutFactoryService messageOutFactoryService, TopicService topicService, - AclService aclService, + AuthorizationService authorizationService, List> fieldValidators) { return new PublishMqttInMessageHandler( publishReceivingService, messageOutFactoryService, - topicService, - aclService, + topicService, authorizationService, fieldValidators); } diff --git a/core-service/src/main/java/javasabr/mqtt/service/AclService.java b/core-service/src/main/java/javasabr/mqtt/service/AuthorizationService.java similarity index 88% rename from core-service/src/main/java/javasabr/mqtt/service/AclService.java rename to core-service/src/main/java/javasabr/mqtt/service/AuthorizationService.java index 9db4098c..d6fbbd02 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/AclService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/AuthorizationService.java @@ -4,7 +4,7 @@ import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.model.topic.TopicName; -public interface AclService { +public interface AuthorizationService { boolean authorizePublish(MqttUser mqttUser, TopicName topicName); diff --git a/core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAclService.java b/core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAuthorizationService.java similarity index 75% rename from core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAclService.java rename to core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAuthorizationService.java index 769cd480..e40270ee 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAclService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/impl/DisabledAuthorizationService.java @@ -3,9 +3,9 @@ import javasabr.mqtt.model.MqttUser; import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.model.topic.TopicName; -import javasabr.mqtt.service.AclService; +import javasabr.mqtt.service.AuthorizationService; -public class DisabledAclService implements AclService { +public class DisabledAuthorizationService implements AuthorizationService { @Override public boolean authorizePublish(MqttUser mqttUser, TopicName topicName) { return true; diff --git a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java index 53d2c709..e846fc1e 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java @@ -13,7 +13,7 @@ import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.network.session.NetworkMqttSession; -import javasabr.mqtt.service.AclService; +import javasabr.mqtt.service.AuthorizationService; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.PublishReceivingService; import javasabr.mqtt.service.TopicService; @@ -31,18 +31,18 @@ public class PublishMqttInMessageHandler PublishReceivingService publishReceivingService; TopicService topicService; - AclService aclService; + AuthorizationService authorizationService; public PublishMqttInMessageHandler( PublishReceivingService publishReceivingService, MessageOutFactoryService messageOutFactoryService, TopicService topicService, - AclService aclService, + AuthorizationService authorizationService, List> fieldValidators) { super(ExternalNetworkMqttUser.class, PublishMqttInMessage.class, messageOutFactoryService, fieldValidators); this.publishReceivingService = publishReceivingService; this.topicService = topicService; - this.aclService = aclService; + this.authorizationService = authorizationService; } @Override @@ -60,7 +60,7 @@ protected void processMessageWithValidFields( TopicName finalTopicName = resolveFinalTopicName(user, session, publishMessage); if (finalTopicName == null) { return; - } else if (!aclService.authorizePublish(user, finalTopicName)) { + } else if (!authorizationService.authorizePublish(user, finalTopicName)) { handleNotAuthorize(user); return; } diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy index 85ceee67..2da0a9f0 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy @@ -13,7 +13,7 @@ import javasabr.mqtt.service.impl.DefaultMessageOutFactoryService import javasabr.mqtt.service.impl.DefaultPublishDeliveringService import javasabr.mqtt.service.impl.DefaultPublishReceivingService import javasabr.mqtt.service.impl.DefaultTopicService -import javasabr.mqtt.service.impl.DisabledAclService +import javasabr.mqtt.service.impl.DisabledAuthorizationService import javasabr.mqtt.service.impl.InMemorySubscriptionService import javasabr.mqtt.service.message.handler.impl.PublishReleaseMqttInMessageHandler import javasabr.mqtt.service.message.out.factory.Mqtt311MessageOutFactory @@ -98,7 +98,7 @@ abstract class IntegrationServiceSpecification extends Specification { def defaultMqttSessionService = new InMemoryMqttSessionService(60_000); @Shared - def disabledAclService = new DisabledAclService() + def disabledAclService = new DisabledAuthorizationService() @Shared List> publishInFieldValidators = [ From e506129434e63971e28335fbd7e64b80ee4f0d1a Mon Sep 17 00:00:00 2001 From: javasabr Date: Tue, 9 Dec 2025 18:31:59 +0100 Subject: [PATCH 19/19] finish refactoring publishes --- .../config/MqttBrokerSpringConfig.java | 2 +- .../service/PublishDeliveringService.java | 1 - .../impl/DefaultPublishDeliveringService.java | 1 - .../AbstractMqttPublishOutMessageHandler.java | 15 ++-- ...TrackableMqttPublishOutMessageHandler.java | 16 +--- ...os0MqttPublishOutMessageHandlerTest.groovy | 8 +- ...os1MqttPublishOutMessageHandlerTest.groovy | 78 ++++++++----------- ...os2MqttPublishOutMessageHandlerTest.groovy | 62 +++++---------- 8 files changed, 64 insertions(+), 119 deletions(-) diff --git a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java index 5090c75a..ce7cbe61 100644 --- a/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java +++ b/application/src/main/java/javasabr/mqtt/broker/application/config/MqttBrokerSpringConfig.java @@ -12,8 +12,8 @@ import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.network.user.NetworkMqttUserFactory; -import javasabr.mqtt.service.AuthorizationService; import javasabr.mqtt.service.AuthenticationService; +import javasabr.mqtt.service.AuthorizationService; import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.ConnectionService; import javasabr.mqtt.service.CredentialSource; diff --git a/core-service/src/main/java/javasabr/mqtt/service/PublishDeliveringService.java b/core-service/src/main/java/javasabr/mqtt/service/PublishDeliveringService.java index 1f971c00..761064fe 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/PublishDeliveringService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/PublishDeliveringService.java @@ -2,7 +2,6 @@ import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.subscriber.SingleSubscriber; -import javasabr.mqtt.service.publish.handler.PublishHandlingResult; public interface PublishDeliveringService { diff --git a/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishDeliveringService.java b/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishDeliveringService.java index dbd9c10d..f2b43e94 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishDeliveringService.java +++ b/core-service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishDeliveringService.java @@ -6,7 +6,6 @@ import javasabr.mqtt.model.subscriber.SingleSubscriber; import javasabr.mqtt.service.PublishDeliveringService; import javasabr.mqtt.service.publish.handler.MqttPublishOutMessageHandler; -import javasabr.mqtt.service.publish.handler.PublishHandlingResult; import lombok.AccessLevel; import lombok.CustomLog; import lombok.experimental.FieldDefaults; diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java index 6ed2b317..92361aa5 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java @@ -7,7 +7,6 @@ import javasabr.mqtt.network.user.NetworkMqttUser; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.publish.handler.MqttPublishOutMessageHandler; -import javasabr.mqtt.service.publish.handler.PublishHandlingResult; import lombok.AccessLevel; import lombok.CustomLog; import lombok.RequiredArgsConstructor; @@ -28,27 +27,25 @@ public final void handle(Publish publish, SingleSubscriber subscriber) { MqttUser user = subscriber.resolveUser(); if (!expectedUserType.isInstance(user)) { log.warning(user.clientId(), user.getClass(), "[%s] Not expected user of type:[%s]"::formatted); - return PublishHandlingResult.NOT_EXPECTED_CLIENT; + return; } U expectedUser = expectedUserType.cast(user); MqttSession session = expectedUser.session(); if (session == null) { log.warning(user.clientId(), "[%s] Session is already closed"::formatted); - return PublishHandlingResult.SESSION_IS_ALREADY_CLOSED; + return; } - publish = reconstruct(expectedUser, session, publish); - if (publish == null) { - return PublishHandlingResult.SKIPPED; + Publish publishToSend = reconstruct(expectedUser, session, publish); + if (publishToSend != null) { + handleImpl(expectedUser, session, publishToSend); } - return handleImpl(expectedUser, session, publish); } @Nullable protected abstract Publish reconstruct(U user, MqttSession session, Publish original); - protected PublishHandlingResult handleImpl(U user, MqttSession session, Publish publish) { + protected void handleImpl(U user, MqttSession session, Publish publish) { send(user, publish); - return PublishHandlingResult.SUCCESS; } protected void send(U user, Publish publish) { diff --git a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java index 6b571d42..2405c913 100644 --- a/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java +++ b/core-service/src/main/java/javasabr/mqtt/service/publish/handler/impl/TrackableMqttPublishOutMessageHandler.java @@ -16,7 +16,6 @@ import javasabr.mqtt.network.impl.ExternalNetworkMqttUser; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.message.out.factory.MqttMessageOutFactory; -import javasabr.mqtt.service.publish.handler.PublishHandlingResult; import lombok.AccessLevel; import lombok.CustomLog; import lombok.experimental.FieldDefaults; @@ -48,31 +47,25 @@ protected Publish reconstruct(ExternalNetworkMqttUser user, MqttSession session, } @Override - protected final PublishHandlingResult handleImpl( - ExternalNetworkMqttUser user, - MqttSession session, - Publish publish) { + protected final void handleImpl(ExternalNetworkMqttUser user, MqttSession session, Publish publish) { // register message id MessageTacker messageTacker = session.outMessageTracker(); messageTacker.add(publish.messageId(), MqttMessageType.PUBLISH); // register callback and retrier ProcessingPublishes processingPublishes = session.outProcessingPublishes(); processingPublishes.register(publish, trackableMessageCallback, publishRetryer); - return super.handleImpl(user, session, publish); + super.handleImpl(user, session, publish); } protected final boolean handleReceivedTrackableMessage( MqttUser user, MqttSession session, TrackableMqttMessage message) { + ExternalNetworkMqttUser networkMqttUser = expectedUserType.cast(user); int messageId = message.messageId(); MessageTacker messageTacker = session.outMessageTracker(); TrackedMessageMeta trackedMessageMeta = messageTacker.stored(messageId); - return handleReceivedTrackableMessageImpl( - expectedUserType.cast(user), - session, - message, - trackedMessageMeta); + return handleReceivedTrackableMessageImpl(networkMqttUser, session, message, trackedMessageMeta); } protected abstract boolean handleReceivedTrackableMessageImpl( @@ -85,7 +78,6 @@ protected final void retryDelivering(MqttUser user, MqttSession session, Publish ExternalNetworkMqttUser networkMqttUser = expectedUserType.cast(user); String clientId = networkMqttUser.clientId(); int messageId = publish.messageId(); - MessageTacker outMessageTracker = session.outMessageTracker(); TrackedMessageMeta trackedMessageMeta = outMessageTracker.stored(messageId); if (trackedMessageMeta == null) { diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy index 2781da71..3184e8f4 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandlerTest.groovy @@ -8,15 +8,12 @@ import javasabr.mqtt.model.subscriber.SingleSubscriber import javasabr.mqtt.model.subscription.Subscription import javasabr.mqtt.network.message.out.PublishMqtt5OutMessage import javasabr.mqtt.service.TestExternalNetworkMqttUser -import javasabr.mqtt.service.publish.handler.PublishHandlingResult class Qos0MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandlerTest { def "should deliver publish to subscriber"() { given: - def publishOutHandler = new Qos0MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos0MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def testTopicName = defaultTopicService.createTopicName(user, "Qos0MqttPublishOutMessageHandlerTest/1") @@ -27,9 +24,8 @@ class Qos0MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(testPublish, subscriber) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS with(user.nextSentMessage(PublishMqtt5OutMessage)) { qos() == QoS.AT_MOST_ONCE !duplicate() diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy index d1adf56a..b8f60514 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandlerTest.groovy @@ -16,15 +16,12 @@ import javasabr.mqtt.network.message.in.PublishReceivedMqttInMessage import javasabr.mqtt.network.message.out.DisconnectMqtt5OutMessage import javasabr.mqtt.network.message.out.PublishMqtt5OutMessage import javasabr.mqtt.service.TestExternalNetworkMqttUser -import javasabr.mqtt.service.publish.handler.PublishHandlingResult class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandlerTest { def "should deliver publish to subscriber"() { given: - def publishOutHandler = new Qos1MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos1MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def testTopicName = defaultTopicService.createTopicName(user, "Qos1MqttPublishOutMessageHandlerTest/1") @@ -32,12 +29,11 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) def subscriber = new SingleSubscriber(user, subscription) def originalMessageId = 60 - def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(publish, subscriber) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS with(user.nextSentMessage(PublishMqtt5OutMessage)) { qos() == QoS.AT_LEAST_ONCE !duplicate() @@ -50,9 +46,7 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def "should wait for ack response for publish"() { given: - def publishOutHandler = new Qos1MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos1MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def session = user.session() @@ -61,15 +55,14 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) def subscriber = new SingleSubscriber(user, subscription) def originalMessageId = 60 - def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(publish, subscriber) - def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS + def publish = user.nextSentMessage(PublishMqtt5OutMessage) with(session.outMessageTracker()) { - with(stored(receivedPublish.messageId())) { + with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH reasonCode() == null } @@ -79,13 +72,13 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl } when: 'send publish ack' def publishAck = PublishAckMqttInMessage - .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + .of(publish.messageId(), PublishAckReasonCode.SUCCESS) session .outProcessingPublishes() .apply(user, publishAck) then: with(session.outMessageTracker()) { - stored(receivedPublish.messageId()) == null + stored(publish.messageId()) == null } with(session.outProcessingPublishes()) { size() == 0 @@ -98,9 +91,7 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def "should correctly handle publish ack when no stored trackable meta about the publish"() { given: - def publishOutHandler = new Qos1MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos1MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def session = user.session() @@ -109,15 +100,14 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) def subscriber = new SingleSubscriber(user, subscription) def originalMessageId = 60 - def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(publish, subscriber) - def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS + def publish = user.nextSentMessage(PublishMqtt5OutMessage) with(session.outMessageTracker()) { - with(stored(receivedPublish.messageId())) { + with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH reasonCode() == null } @@ -128,15 +118,15 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl when: 'remove trackable info and send publish ack' session .outMessageTracker() - .remove(receivedPublish.messageId()) + .remove(publish.messageId()) def publishAck = PublishAckMqttInMessage - .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + .of(publish.messageId(), PublishAckReasonCode.SUCCESS) session .outProcessingPublishes() .apply(user, publishAck) then: with(session.outMessageTracker()) { - stored(receivedPublish.messageId()) == null + stored(publish.messageId()) == null } with(session.outProcessingPublishes()) { size() == 0 @@ -149,9 +139,7 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def "should handle as protocol error receiving unexpected response message"() { given: - def publishOutHandler = new Qos1MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos1MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def session = user.session() @@ -160,15 +148,14 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) def subscriber = new SingleSubscriber(user, subscription) def originalMessageId = 60 - def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(publish, subscriber) - def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS + def publish = user.nextSentMessage(PublishMqtt5OutMessage) with(session.outMessageTracker()) { - with(stored(receivedPublish.messageId())) { + with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH reasonCode() == null } @@ -178,7 +165,7 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl } when: 'send unexpected publish receive to get protocol error' def publishReceive = PublishReceivedMqttInMessage - .of(receivedPublish.messageId(), PublishReceivedReasonCode.SUCCESS) + .of(publish.messageId(), PublishReceivedReasonCode.SUCCESS) session .outProcessingPublishes() .apply(user, publishReceive) @@ -191,9 +178,7 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def "should handle as protocol error for unexpected flow state"() { given: - def publishOutHandler = new Qos1MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos1MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def session = user.session() @@ -202,15 +187,14 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def subscription = Subscription.minimal(topicFilter, QoS.AT_MOST_ONCE) def subscriber = new SingleSubscriber(user, subscription) def originalMessageId = 60 - def publish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) + def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(publish, subscriber) - def receivedPublish = user.nextSentMessage(PublishMqtt5OutMessage) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS + def publish = user.nextSentMessage(PublishMqtt5OutMessage) with(session.outMessageTracker()) { - with(stored(receivedPublish.messageId())) { + with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH reasonCode() == null } @@ -221,9 +205,9 @@ class Qos1MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl when: 'change trackable info to publish release and send publish ack' session .outMessageTracker() - .update(receivedPublish.messageId(), MqttMessageType.PUBLISH_RELEASE, PublishReleaseReasonCode.SUCCESS) + .update(publish.messageId(), MqttMessageType.PUBLISH_RELEASE, PublishReleaseReasonCode.SUCCESS) def publishAck = PublishAckMqttInMessage - .of(receivedPublish.messageId(), PublishAckReasonCode.SUCCESS) + .of(publish.messageId(), PublishAckReasonCode.SUCCESS) session .outProcessingPublishes() .apply(user, publishAck) diff --git a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy index d6d0ef56..7dbb467b 100644 --- a/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy +++ b/core-service/src/test/groovy/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandlerTest.groovy @@ -19,15 +19,12 @@ import javasabr.mqtt.network.message.out.DisconnectMqtt5OutMessage import javasabr.mqtt.network.message.out.PublishMqtt5OutMessage import javasabr.mqtt.network.message.out.PublishReleaseMqtt5OutMessage import javasabr.mqtt.service.TestExternalNetworkMqttUser -import javasabr.mqtt.service.publish.handler.PublishHandlingResult class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandlerTest { def "should deliver publish to subscriber"() { given: - def publishOutHandler = new Qos2MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos2MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def testTopicName = defaultTopicService.createTopicName(user, "Qos2MqttPublishOutMessageHandlerTest/1") @@ -38,9 +35,8 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(testPublish, subscriber) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS with(user.nextSentMessage(PublishMqtt5OutMessage)) { qos() == QoS.EXACTLY_ONCE !duplicate() @@ -53,9 +49,7 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def "should wait for receive-complete responses for publish"() { given: - def publishOutHandler = new Qos2MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos2MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def session = user.session() @@ -67,10 +61,9 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(testPublish, subscriber) - def publish = user.nextSentMessage(PublishMqtt5OutMessage) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS + def publish = user.nextSentMessage(PublishMqtt5OutMessage) with(session.outMessageTracker()) { with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH @@ -124,9 +117,7 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def "should correctly handle publish receive when no stored trackable meta about the publish"() { given: - def publishOutHandler = new Qos2MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos2MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def session = user.session() @@ -138,10 +129,9 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(testPublish, subscriber) - def publish = user.nextSentMessage(PublishMqtt5OutMessage) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS + def publish = user.nextSentMessage(PublishMqtt5OutMessage) with(session.outMessageTracker()) { with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH @@ -176,9 +166,7 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def "should handle as protocol error receiving unexpected response message for first stage"() { given: - def publishOutHandler = new Qos2MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos2MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def session = user.session() @@ -190,10 +178,9 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(testPublish, subscriber) - def publish = user.nextSentMessage(PublishMqtt5OutMessage) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS + def publish = user.nextSentMessage(PublishMqtt5OutMessage) with(session.outMessageTracker()) { with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH @@ -218,9 +205,7 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def "should handle as protocol error receiving unexpected response message for second stage"() { given: - def publishOutHandler = new Qos2MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos2MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def session = user.session() @@ -232,10 +217,9 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(testPublish, subscriber) - def publish = user.nextSentMessage(PublishMqtt5OutMessage) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS + def publish = user.nextSentMessage(PublishMqtt5OutMessage) with(session.outMessageTracker()) { with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH @@ -271,9 +255,7 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def "should handle as protocol error for unexpected flow state for publish received"() { given: - def publishOutHandler = new Qos2MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos2MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def session = user.session() @@ -285,10 +267,9 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(testPublish, subscriber) - def publish = user.nextSentMessage(PublishMqtt5OutMessage) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS + def publish = user.nextSentMessage(PublishMqtt5OutMessage) with(session.outMessageTracker()) { with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH @@ -316,9 +297,7 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def "should handle as protocol error for unexpected flow state for publish complete"() { given: - def publishOutHandler = new Qos2MqttPublishOutMessageHandler( - defaultSubscriptionService, - defaultMessageOutFactoryService) + def publishOutHandler = new Qos2MqttPublishOutMessageHandler(defaultMessageOutFactoryService) def connection = mockedExternalConnection(MqttVersion.MQTT_5) def user = connection.user() as TestExternalNetworkMqttUser def session = user.session() @@ -330,10 +309,9 @@ class Qos2MqttPublishOutMessageHandlerTest extends QosMqttPublishOutMessageHandl def testPublish = Publish.minimal(originalMessageId, QoS.EXACTLY_ONCE, testTopicName, testPayload) .withDuplicated() when: - def result = publishOutHandler.handle(testPublish, subscriber) - def publish = user.nextSentMessage(PublishMqtt5OutMessage) + publishOutHandler.handle(testPublish, subscriber) then: - result == PublishHandlingResult.SUCCESS + def publish = user.nextSentMessage(PublishMqtt5OutMessage) with(session.outMessageTracker()) { with(stored(publish.messageId())) { messageType() == MqttMessageType.PUBLISH