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 ac31d115..c28f17cb 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 @@ -17,17 +17,19 @@ import javasabr.mqtt.service.PublishDeliveringService; import javasabr.mqtt.service.PublishReceivingService; import javasabr.mqtt.service.SubscriptionService; +import javasabr.mqtt.service.TopicService; import javasabr.mqtt.service.handler.client.ExternalMqttClientReleaseHandler; import javasabr.mqtt.service.impl.DefaultConnectionService; import javasabr.mqtt.service.impl.DefaultMessageOutFactoryService; import javasabr.mqtt.service.impl.DefaultMqttConnectionFactory; import javasabr.mqtt.service.impl.DefaultPublishDeliveringService; import javasabr.mqtt.service.impl.DefaultPublishReceivingService; +import javasabr.mqtt.service.impl.DefaultTopicService; import javasabr.mqtt.service.impl.ExternalMqttClientFactory; import javasabr.mqtt.service.impl.FileCredentialsSource; import javasabr.mqtt.service.impl.InMemoryClientIdRegistry; +import javasabr.mqtt.service.impl.InMemorySubscriptionService; import javasabr.mqtt.service.impl.SimpleAuthenticationService; -import javasabr.mqtt.service.impl.SimpleSubscriptionService; import javasabr.mqtt.service.message.handler.MqttInMessageHandler; import javasabr.mqtt.service.message.handler.impl.ConnectInMqttInMessageHandler; import javasabr.mqtt.service.message.handler.impl.DisconnectMqttInMessageHandler; @@ -96,7 +98,7 @@ AuthenticationService authenticationService( @Bean SubscriptionService subscriptionService() { - return new SimpleSubscriptionService(); + return new InMemorySubscriptionService(); } @Bean @@ -115,6 +117,11 @@ MessageOutFactoryService mqttMessageOutFactoryService( return new DefaultMessageOutFactoryService(knownFactories); } + @Bean + TopicService topicService() { + return new DefaultTopicService(); + } + @Bean MqttInMessageHandler connectInMqttInMessageHandler( ClientIdRegistry clientIdRegistry, @@ -131,47 +138,58 @@ MqttInMessageHandler connectInMqttInMessageHandler( } @Bean - MqttInMessageHandler publishAckMqttInMessageHandler() { - return new PublishAckMqttInMessageHandler(); + MqttInMessageHandler publishAckMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { + return new PublishAckMqttInMessageHandler(messageOutFactoryService); } @Bean - MqttInMessageHandler publishCompleteMqttInMessageHandler() { - return new PublishCompleteMqttInMessageHandler(); + MqttInMessageHandler publishCompleteMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { + return new PublishCompleteMqttInMessageHandler(messageOutFactoryService); } @Bean - MqttInMessageHandler publishMqttInMessageHandler(PublishReceivingService publishReceivingService) { - return new PublishMqttInMessageHandler(publishReceivingService); + MqttInMessageHandler publishMqttInMessageHandler( + PublishReceivingService publishReceivingService, + MessageOutFactoryService messageOutFactoryService, + TopicService topicService) { + return new PublishMqttInMessageHandler( + publishReceivingService, + messageOutFactoryService, + topicService); } @Bean - MqttInMessageHandler publishReceiveMqttInMessageHandler() { - return new PublishReceiveMqttInMessageHandler(); + MqttInMessageHandler publishReceiveMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { + return new PublishReceiveMqttInMessageHandler(messageOutFactoryService); } @Bean - MqttInMessageHandler publishReleaseMqttInMessageHandler() { - return new PublishReleaseMqttInMessageHandler(); + MqttInMessageHandler publishReleaseMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { + return new PublishReleaseMqttInMessageHandler(messageOutFactoryService); } @Bean - MqttInMessageHandler disconnectMqttInMessageHandler() { - return new DisconnectMqttInMessageHandler(); + MqttInMessageHandler disconnectMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { + return new DisconnectMqttInMessageHandler(messageOutFactoryService); } @Bean MqttInMessageHandler subscribeMqttInMessageHandler( SubscriptionService subscriptionService, - MessageOutFactoryService messageOutFactoryService) { - return new SubscribeMqttInMessageHandler(subscriptionService, messageOutFactoryService); + MessageOutFactoryService messageOutFactoryService, + TopicService topicService) { + return new SubscribeMqttInMessageHandler(subscriptionService, messageOutFactoryService, topicService); } @Bean MqttInMessageHandler unsubscribeMqttInMessageHandler( SubscriptionService subscriptionService, - MessageOutFactoryService messageOutFactoryService) { - return new UnsubscribeMqttInMessageHandler(subscriptionService, messageOutFactoryService); + MessageOutFactoryService messageOutFactoryService, + TopicService topicService) { + return new UnsubscribeMqttInMessageHandler( + subscriptionService, + messageOutFactoryService, + topicService); } @Bean @@ -252,11 +270,11 @@ MqttClientReleaseHandler externalMqttClientReleaseHandler( @Bean MqttServerConnectionConfig externalConnectionConfig(Environment env) { return new MqttServerConnectionConfig( - QoS.of(env.getProperty("mqtt.connection.max.qos", int.class, 2)), + QoS.ofCode(env.getProperty("mqtt.connection.max.qos", int.class, 2)), env.getProperty( - "mqtt.external.connection.max.packet.size", + "mqtt.external.connection.max.message.size", int.class, - MqttProperties.MAXIMUM_PACKET_SIZE_DEFAULT), + MqttProperties.MAXIMUM_MESSAGE_SIZE_DEFAULT), env.getProperty( "mqtt.external.connection.max.string.length", int.class, @@ -265,6 +283,10 @@ MqttServerConnectionConfig externalConnectionConfig(Environment env) { "mqtt.external.connection.max.binary.size", int.class, MqttProperties.MAXIMUM_BINARY_SIZE), + env.getProperty( + "mqtt.external.connection.max.topic.levels", + int.class, + MqttProperties.MAXIMUM_TOPIC_LEVELS), env.getProperty( "mqtt.external.connection.min.keep.alive", int.class, @@ -272,11 +294,11 @@ MqttServerConnectionConfig externalConnectionConfig(Environment env) { env.getProperty( "mqtt.external.connection.receive.maximum", int.class, - MqttProperties.RECEIVE_MAXIMUM_DEFAULT), + MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_DEFAULT), env.getProperty( "mqtt.external.connection.topic.alias.maximum", int.class, - MqttProperties.TOPIC_ALIAS_MAXIMUM_DISABLED), + MqttProperties.TOPIC_ALIAS_DEFAULT), env.getProperty( "mqtt.external.connection.default.session.expiration.time", long.class, diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/ConnectSubscribePublishTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/ConnectSubscribePublishTest.groovy index d38112f9..ec103027 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/ConnectSubscribePublishTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/ConnectSubscribePublishTest.groovy @@ -11,13 +11,13 @@ import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5PayloadFormatIndicator import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish import com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAckReasonCode -import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.CompletableFuture class ConnectSubscribePublishTest extends IntegrationSpecification { - def "publisher should publish message QoS 0 using mqtt 3.1.1"() { + def "should deliver publish message QoS 0 using mqtt 3.1.1"() { given: - def received = new AtomicReference() + def received = new CompletableFuture() def subscriber = buildExternalMqtt311Client() def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() def publisher = buildExternalMqtt311Client() @@ -26,7 +26,6 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { publisher.connect().join() def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_MOST_ONCE, received) def publishResult = publish(publisher, subscriberId, MqttQos.AT_MOST_ONCE) - Thread.sleep(100) then: noExceptionThrown() subscribeResult != null @@ -35,17 +34,17 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { publishResult != null publishResult.qos == MqttQos.AT_MOST_ONCE publishResult.type == Mqtt3MessageType.PUBLISH - received.get() != null - received.get().qos == MqttQos.AT_MOST_ONCE - received.get().type == Mqtt3MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.AT_MOST_ONCE + received.join().type == Mqtt3MessageType.PUBLISH cleanup: subscriber.disconnect().join() publisher.disconnect().join() } - def "publisher should publish message QoS 0 using mqtt 5"() { + def "should deliver publish message QoS 0 using mqtt 5"() { given: - def received = new AtomicReference() + def received = new CompletableFuture() def subscriber = buildExternalMqtt5Client() def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() def publisher = buildExternalMqtt5Client() @@ -54,7 +53,6 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { publisher.connect().join() def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_MOST_ONCE, received) def publishResult = publish(publisher, subscriberId, MqttQos.AT_MOST_ONCE) - Thread.sleep(100) then: noExceptionThrown() subscribeResult != null @@ -63,17 +61,17 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { publishResult != null publishResult.publish.qos == MqttQos.AT_MOST_ONCE publishResult.publish.type == Mqtt5MessageType.PUBLISH - received.get() != null - received.get().qos == MqttQos.AT_MOST_ONCE - received.get().type == Mqtt5MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.AT_MOST_ONCE + received.join().type == Mqtt5MessageType.PUBLISH cleanup: subscriber.disconnect().join() publisher.disconnect().join() } - def "publisher should publish message QoS 1 using mqtt 3.1.1"() { + def "should deliver publish message QoS 1 using mqtt 3.1.1"() { given: - def received = new AtomicReference() + def received = new CompletableFuture() def subscriber = buildExternalMqtt311Client() def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() def publisher = buildExternalMqtt311Client() @@ -82,7 +80,6 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { publisher.connect().join() def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_LEAST_ONCE, received) def publishResult = publish(publisher, subscriberId, MqttQos.AT_LEAST_ONCE) - Thread.sleep(100) then: noExceptionThrown() subscribeResult != null @@ -91,17 +88,17 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { publishResult != null publishResult.qos == MqttQos.AT_LEAST_ONCE publishResult.type == Mqtt3MessageType.PUBLISH - received.get() != null - received.get().qos == MqttQos.AT_LEAST_ONCE - received.get().type == Mqtt3MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.AT_LEAST_ONCE + received.join().type == Mqtt3MessageType.PUBLISH cleanup: subscriber.disconnect().join() publisher.disconnect().join() } - def "publisher should publish message QoS 1 using mqtt 5"() { + def "should deliver publish message QoS 1 using mqtt 5"() { given: - def received = new AtomicReference() + def received = new CompletableFuture() def subscriber = buildExternalMqtt5Client() def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() def publisher = buildExternalMqtt5Client() @@ -110,7 +107,6 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { publisher.connect().join() def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_LEAST_ONCE, received) def publishResult = publish(publisher, subscriberId, MqttQos.AT_LEAST_ONCE) - Thread.sleep(100) then: noExceptionThrown() subscribeResult != null @@ -119,17 +115,17 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { publishResult != null publishResult.publish.qos == MqttQos.AT_LEAST_ONCE publishResult.publish.type == Mqtt5MessageType.PUBLISH - received.get() != null - received.get().qos == MqttQos.AT_LEAST_ONCE - received.get().type == Mqtt5MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.AT_LEAST_ONCE + received.join().type == Mqtt5MessageType.PUBLISH cleanup: subscriber.disconnect().join() publisher.disconnect().join() } - def "publisher should publish message QoS 2 using mqtt 3.1.1"() { + def "should deliver publish message QoS 2 using mqtt 3.1.1"() { given: - def received = new AtomicReference() + def received = new CompletableFuture() def subscriber = buildExternalMqtt311Client() def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() def publisher = buildExternalMqtt311Client() @@ -138,7 +134,6 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { publisher.connect().join() def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.EXACTLY_ONCE, received) def publishResult = publish(publisher, subscriberId, MqttQos.EXACTLY_ONCE) - Thread.sleep(100) then: noExceptionThrown() subscribeResult != null @@ -147,14 +142,14 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { publishResult != null publishResult.qos == MqttQos.EXACTLY_ONCE publishResult.type == Mqtt3MessageType.PUBLISH - received.get() != null - received.get().qos == MqttQos.EXACTLY_ONCE - received.get().type == Mqtt3MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.EXACTLY_ONCE + received.join().type == Mqtt3MessageType.PUBLISH } - def "publisher should publish message QoS 2 using mqtt 5"() { + def "should deliver publish message QoS 2 using mqtt 5"() { given: - def received = new AtomicReference() + def received = new CompletableFuture() def subscriber = buildExternalMqtt5Client() def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() def publisher = buildExternalMqtt5Client() @@ -172,9 +167,9 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { publishResult != null publishResult.publish.qos == MqttQos.EXACTLY_ONCE publishResult.publish.type == Mqtt5MessageType.PUBLISH - received.get() != null - received.get().qos == MqttQos.EXACTLY_ONCE - received.get().type == Mqtt5MessageType.PUBLISH + received.join() != null + received.join().qos == MqttQos.EXACTLY_ONCE + received.join().type == Mqtt5MessageType.PUBLISH cleanup: subscriber.disconnect().join() publisher.disconnect().join() @@ -194,11 +189,11 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { Mqtt5AsyncClient subscriber, String subscriberId, MqttQos qos, - AtomicReference received) { + CompletableFuture received) { return subscriber.subscribeWith() .topicFilter("test/$subscriberId") .qos(qos) - .callback({ publish -> received.set(publish) }) + .callback({ publish -> received.complete(publish) }) .send() .join() } @@ -216,11 +211,11 @@ class ConnectSubscribePublishTest extends IntegrationSpecification { Mqtt3AsyncClient subscriber, String subscriberId, MqttQos qos, - AtomicReference received) { + CompletableFuture received) { return subscriber.subscribeWith() .topicFilter("test/$subscriberId") .qos(qos) - .callback({ publish -> received.set(publish) }) + .callback({ publish -> received.complete(publish) }) .send() .join() } diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy index 366e9321..8fec4102 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/IntegrationSpecification.groovy @@ -137,20 +137,16 @@ class IntegrationSpecification extends Specification { def mqtt5MockedConnection(MqttServerConnectionConfig serverConnConfig) { MqttClientConnectionConfig clientConnConfig = new MqttClientConnectionConfig( + serverConnConfig, serverConnConfig.maxQos(), MqttVersion.MQTT_5, MqttProperties.SESSION_EXPIRY_INTERVAL_DISABLED, serverConnConfig.receiveMaxPublishes(), - serverConnConfig.maxPacketSize(), + serverConnConfig.maxMessageSize(), serverConnConfig.topicAliasMaxValue(), MqttProperties.SERVER_KEEP_ALIVE_DEFAULT, false, - false, - serverConnConfig.sessionsEnabled(), - serverConnConfig.retainAvailable(), - serverConnConfig.wildcardSubscriptionAvailable(), - serverConnConfig.subscriptionIdAvailable(), - serverConnConfig.sharedSubscriptionAvailable()) + false) def connectionRef = new AtomicReference() def connection = Stub(MqttConnection) { isSupported(MqttVersion.MQTT_5) >> true @@ -168,20 +164,16 @@ class IntegrationSpecification extends Specification { def mqtt311MockedConnection(MqttServerConnectionConfig serverConnConfig) { MqttClientConnectionConfig clientConnConfig = new MqttClientConnectionConfig( + serverConnConfig, serverConnConfig.maxQos(), MqttVersion.MQTT_3_1_1, MqttProperties.SESSION_EXPIRY_INTERVAL_DISABLED, serverConnConfig.receiveMaxPublishes(), - serverConnConfig.maxPacketSize(), + serverConnConfig.maxMessageSize(), serverConnConfig.topicAliasMaxValue(), MqttProperties.SERVER_KEEP_ALIVE_DEFAULT, false, - false, - serverConnConfig.sessionsEnabled(), - serverConnConfig.retainAvailable(), - serverConnConfig.wildcardSubscriptionAvailable(), - serverConnConfig.subscriptionIdAvailable(), - serverConnConfig.sharedSubscriptionAvailable()) + false) def connectionRef = new AtomicReference() def connection = Stub(MqttConnection) { isSupported(MqttVersion.MQTT_5) >> false diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/PublishRetryTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/PublishRetryTest.groovy index fc291d25..5ae84a2f 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/PublishRetryTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/PublishRetryTest.groovy @@ -6,7 +6,8 @@ import javasabr.mqtt.model.reason.code.ConnectAckReasonCode import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter +import javasabr.mqtt.model.subscribtion.Subscription +import javasabr.mqtt.model.topic.TopicFilter import javasabr.mqtt.network.message.in.ConnectAckMqttInMessage import javasabr.mqtt.network.message.in.PublishMqttInMessage import javasabr.mqtt.network.message.in.PublishReleaseMqttInMessage @@ -35,9 +36,8 @@ class PublishRetryTest extends IntegrationSpecification { connectAck.reasonCode == ConnectAckReasonCode.SUCCESS when: subscriber.send(new SubscribeMqtt311OutMessage( - Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.AT_LEAST_ONCE)), - 1 - )) + 1, + Array.of(Subscription.minimal(TopicFilter.valueOf("test/retry/$subscriberId"), QoS.AT_LEAST_ONCE)))) def subscribeAck = subscriber.readNext() as SubscribeAckMqttInMessage then: subscribeAck.reasonCodes.stream() @@ -83,9 +83,8 @@ class PublishRetryTest extends IntegrationSpecification { connectAck.reasonCode == ConnectAckReasonCode.SUCCESS when: subscriber.send(new SubscribeMqtt5OutMessage( - Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.AT_LEAST_ONCE)), - 1 - )) + 1, + Array.of(Subscription.minimal(TopicFilter.valueOf("test/retry/$subscriberId"), QoS.AT_LEAST_ONCE)))) def subscribeAck = subscriber.readNext() as SubscribeAckMqttInMessage then: subscribeAck.reasonCodes.stream() @@ -131,9 +130,8 @@ class PublishRetryTest extends IntegrationSpecification { connectAck.reasonCode == ConnectAckReasonCode.SUCCESS when: subscriber.send(new SubscribeMqtt311OutMessage( - Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.EXACTLY_ONCE)), - 1 - )) + 1, + Array.of(Subscription.minimal(TopicFilter.valueOf("test/retry/$subscriberId"), QoS.EXACTLY_ONCE)))) def subscribeAck = subscriber.readNext() as SubscribeAckMqttInMessage then: subscribeAck.reasonCodes.stream() @@ -194,9 +192,8 @@ class PublishRetryTest extends IntegrationSpecification { connectAck.reasonCode == ConnectAckReasonCode.SUCCESS when: subscriber.send(new SubscribeMqtt5OutMessage( - Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.EXACTLY_ONCE)), - 1 - )) + 1, + Array.of(Subscription.minimal(TopicFilter.valueOf("test/retry/$subscriberId"), QoS.EXACTLY_ONCE)))) def subscribeAck = subscriber.readNext() as SubscribeAckMqttInMessage then: subscribeAck.reasonCodes.stream() diff --git a/application/src/test/groovy/javasabr/mqtt/broker/application/service/SubscribtionServiceTest.groovy b/application/src/test/groovy/javasabr/mqtt/broker/application/service/SubscribtionServiceTest.groovy index 1f5c859d..cd544d96 100644 --- a/application/src/test/groovy/javasabr/mqtt/broker/application/service/SubscribtionServiceTest.groovy +++ b/application/src/test/groovy/javasabr/mqtt/broker/application/service/SubscribtionServiceTest.groovy @@ -3,40 +3,27 @@ package javasabr.mqtt.broker.application.service import com.hivemq.client.mqtt.datatypes.MqttQos import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5SubAckException import javasabr.mqtt.broker.application.IntegrationSpecification -import javasabr.mqtt.model.ActionResult -import javasabr.mqtt.model.subscriber.SingleSubscriber +import javasabr.mqtt.model.topic.TopicName +import javasabr.mqtt.network.MqttClient import javasabr.mqtt.service.ClientIdRegistry -import javasabr.mqtt.service.impl.SimpleSubscriptionService -import org.spockframework.util.Pair +import javasabr.mqtt.service.impl.InMemorySubscriptionService import org.springframework.beans.factory.annotation.Autowired import spock.lang.Unroll import java.util.concurrent.CompletionException -import static com.hivemq.client.mqtt.datatypes.MqttQos.* -import static javasabr.mqtt.model.util.TopicUtils.buildTopicName -import static org.spockframework.util.Pair.of - class SubscribtionServiceTest extends IntegrationSpecification { @Autowired ClientIdRegistry clientIdRegistry @Autowired - SimpleSubscriptionService subscriptionService + InMemorySubscriptionService subscriptionService def "should clear/restore topic subscribers after disconnect/reconnect"() { given: def subscriber = buildExternalMqtt5Client(clientId) - def topicName = buildTopicName(topicFilter) - - def matchesCount = 0 - SingleSubscriber matchedSubscriber = null - def action = { subs, empty -> - matchesCount++ - matchedSubscriber = subs - ActionResult.SUCCESS - } + def topicName = TopicName.valueOf(topicFilter) when: subscriber.connectWith() .cleanStart(true) @@ -44,29 +31,44 @@ class SubscribtionServiceTest extends IntegrationSpecification { .join() subscriber.subscribeWith() .topicFilter(topicFilter) - .qos(AT_MOST_ONCE) + .qos(MqttQos.AT_MOST_ONCE) .send() .join() - - def actionResult = subscriptionService.forEachTopicSubscriber(topicName, null, action) + def subscribers = subscriptionService + .findSubscribers(topicName) + then: "should find the subscriber" + subscribers.size() == 1 + subscribers.get(0).owner() instanceof MqttClient + when: + def matchedSubscriber = subscribers.get(0) + def subscription = matchedSubscriber.subscription() + def owner = matchedSubscriber.owner() as MqttClient then: - matchesCount == 1 - matchedSubscriber.user.clientId() == clientId - matchedSubscriber.subscribe.topicFilter.getRawTopic() == topicFilter - actionResult == ActionResult.SUCCESS + owner.clientId() == clientId + subscription.topicFilter().rawTopic() == topicFilter when: subscriber.disconnect().join() + def subscribers2 = subscriptionService + .findSubscribers(topicName) + then: "shot not find anything after disconnection" + subscribers2.size() == 0 + when: subscriber.connectWith() .cleanStart(false) .send() .join() - - actionResult = subscriptionService.forEachTopicSubscriber(topicName, clientId, action) + def subscribers3 = subscriptionService + .findSubscribers(topicName) + then: "should find the reconnected subscriber" + subscribers3.size() == 1 + subscribers3.get(0).owner() instanceof MqttClient + when: + matchedSubscriber = subscribers3.get(0) + subscription = matchedSubscriber.subscription() + owner = matchedSubscriber.owner() as MqttClient then: - matchesCount == 2 - matchedSubscriber.user.clientId() == clientId - matchedSubscriber.subscribe.topicFilter.getRawTopic() == topicFilter - actionResult == ActionResult.SUCCESS + owner.clientId() == clientId + subscription.topicFilter().rawTopic() == topicFilter cleanup: subscriber.disconnect().join() } @@ -74,110 +76,92 @@ class SubscribtionServiceTest extends IntegrationSpecification { @Unroll def "should match subscriber with the highest QoS"( String topicName, - Pair topicFilter1, - Pair topicFilter2, - String targetTopicFilter - ) { + String topicFilter1, + MqttQos qos1, + String topicFilter2, + MqttQos qos2, + String expectedTopicFilter) { given: def subscriber = buildExternalMqtt5Client() - - def matchesCount = 0 - SingleSubscriber matchedSubscriber = null - def action = { subs, empty -> - matchesCount++ - matchedSubscriber = subs - ActionResult.SUCCESS - } subscriber.connectWith() .send() .join() subscriber.subscribeWith() - .topicFilter(topicFilter1.first()) - .qos(topicFilter1.second()) + .topicFilter(topicFilter1) + .qos(qos1) .send() .join() subscriber.subscribeWith() - .topicFilter(topicFilter2.first()) - .qos(topicFilter2.second()) + .topicFilter(topicFilter2) + .qos(qos2) .send() .join() when: - subscriptionService.forEachTopicSubscriber(buildTopicName(topicName), null, action) + def subscribers = subscriptionService + .findSubscribers(TopicName.valueOf(topicName)) then: - matchesCount == 1 - matchedSubscriber.subscribe.topicFilter.getRawTopic() == targetTopicFilter + subscribers.size() == 1 + subscribers.get(0).subscription().topicFilter().rawTopic() == expectedTopicFilter cleanup: subscriber.disconnect().join() where: - topicName | topicFilter1 | topicFilter2 | targetTopicFilter - "topic/Filter" | of("topic/Filter", AT_MOST_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/#" - "topic/Filter" | of("topic/Filter", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/Filter" - "topic/Another" | of("topic/Filter", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/#" - "topic/Filter/First" | of("topic/+/First", AT_MOST_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/#" - "topic/Filter/First" | of("topic/+/First", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/+/First" + topicName | topicFilter1 | qos1 | topicFilter2 | qos2 | expectedTopicFilter + "topic/Filter" | "topic/Filter" | MqttQos.AT_MOST_ONCE | "topic/#" | MqttQos.AT_LEAST_ONCE | "topic/#" + "topic/Filter" | "topic/Filter" | MqttQos.EXACTLY_ONCE | "topic/#" | MqttQos.AT_LEAST_ONCE | "topic/Filter" + "topic/Another" | "topic/Filter" | MqttQos.EXACTLY_ONCE | "topic/#" | MqttQos.AT_LEAST_ONCE | "topic/#" + "topic/Filter/First" | "topic/+/First" | MqttQos.AT_MOST_ONCE | "topic/#" | MqttQos.AT_LEAST_ONCE | "topic/#" + "topic/Filter/First" | "topic/+/First" | MqttQos.EXACTLY_ONCE | "topic/#" | MqttQos.AT_LEAST_ONCE | "topic/+/First" } @Unroll def "should match all subscribers with shared and single topic"( String topicName, - Pair topicFilter1, - Pair topicFilter2, + String topicFilter1, + MqttQos qos1, + String topicFilter2, + MqttQos qos2, String targetTopicFilter, - int targetCount - ) { + int targetCount) { given: def clientId1 = clientIdRegistry.generate().block() def clientId2 = clientIdRegistry.generate().block() def subscriber1 = buildExternalMqtt5Client(clientId1) def subscriber2 = buildExternalMqtt5Client(clientId2) - - def matchesCount = 0 - def matchedSubscribers = new LinkedHashSet() - def action = { SingleSubscriber subscriber, String clientId -> - matchesCount++ - matchedSubscribers.add(subscriber.user.clientId) - ActionResult.SUCCESS - } - subscriber1.connectWith() .send() .join() subscriber2.connectWith() .send() .join() - subscriber1.subscribeWith() - .topicFilter(topicFilter1.first()) - .qos(topicFilter1.second()) + .topicFilter(topicFilter1) + .qos(qos1) .send() .join() subscriber2.subscribeWith() - .topicFilter(topicFilter2.first()) - .qos(topicFilter2.second()) + .topicFilter(topicFilter2) + .qos(qos2) .send() .join() when: - subscriptionService.forEachTopicSubscriber(buildTopicName(topicName), clientId, action) + def subscribers = subscriptionService.findSubscribers(TopicName.valueOf(topicName)) then: - matchesCount == targetCount - matchedSubscribers[0] == clientId1 - matchedSubscribers[1] == clientId2 + subscribers.size() == targetCount + (subscribers[0].owner() as MqttClient).clientId() == clientId1 + (subscribers[1].owner() as MqttClient).clientId() == clientId2 cleanup: subscriber1.disconnect().join() subscriber2.disconnect().join() where: - topicName | topicFilter1 | topicFilter2 | targetTopicFilter | targetCount - "topic/Filter" | of("\$share/group1/topic/Filter", AT_MOST_ONCE) | of("\$share/group2/topic/#", AT_LEAST_ONCE) | "topic/#" | 2 - "topic/Filter" | of("\$share/group1/topic/Filter", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/Filter" | 2 - "topic/Filter/First" | of("topic/+/First", AT_MOST_ONCE) | of("\$share/group2/topic/#", AT_LEAST_ONCE) | "topic/#" | 2 - "topic/Filter/First" | of("topic/+/First", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/+/First" | 2 + topicName | topicFilter1 | qos1 | topicFilter2 | qos2 | targetTopicFilter | targetCount + "topic/Filter" | "\$share/group1/topic/Filter" | MqttQos.AT_MOST_ONCE | "\$share/group2/topic/#" | MqttQos.AT_LEAST_ONCE | "topic/#" | 2 + "topic/Filter" | "\$share/group1/topic/Filter" | MqttQos.EXACTLY_ONCE | "topic/#" | MqttQos.AT_LEAST_ONCE | "topic/Filter" | 2 + "topic/Filter/First" | "topic/+/First" | MqttQos.AT_MOST_ONCE | "\$share/group2/topic/#" | MqttQos.AT_LEAST_ONCE | "topic/#" | 2 + "topic/Filter/First" | "topic/+/First" | MqttQos.EXACTLY_ONCE | "topic/#" | MqttQos.AT_LEAST_ONCE | "topic/+/First" | 2 } @Unroll - def "should reject subscribe with wrong topic filter"( - String wrongTopicFilter, - Class exception - ) { + def "should reject subscribe with wrong topic filter"(String wrongTopicFilter, Class exception) { given: def subscriber = buildExternalMqtt5Client() when: @@ -197,11 +181,11 @@ class SubscribtionServiceTest extends IntegrationSpecification { cleanup: subscriber.disconnect().join() where: - wrongTopicFilter | exception - "topic/" | CompletionException - "topic//Filter" | CompletionException - "/topic/Another" | CompletionException - "topic/##" | IllegalArgumentException - "++/Filter/First" | IllegalArgumentException + wrongTopicFilter | exception + "\$sys/topic/" | CompletionException + "topic//Filter" | CompletionException + "/topic/\u0000Another" | IllegalArgumentException + "topic/##" | IllegalArgumentException + "++/Filter/First" | IllegalArgumentException } } diff --git a/application/src/test/resources/log4j2.xml b/application/src/test/resources/log4j2.xml index 881eaa86..8cbe6688 100644 --- a/application/src/test/resources/log4j2.xml +++ b/application/src/test/resources/log4j2.xml @@ -15,6 +15,15 @@ + + + + + + + + + diff --git a/base/src/main/java/javasabr/mqtt/base/util/DebugUtils.java b/base/src/main/java/javasabr/mqtt/base/util/DebugUtils.java index 9d7e821a..fb04b0ce 100644 --- a/base/src/main/java/javasabr/mqtt/base/util/DebugUtils.java +++ b/base/src/main/java/javasabr/mqtt/base/util/DebugUtils.java @@ -7,6 +7,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; import tools.jackson.core.JacksonException; import tools.jackson.core.JsonGenerator; import tools.jackson.databind.SerializationContext; @@ -85,17 +86,34 @@ public ArraySerializer() { @Override public void serialize( - Array value, + Array array, JsonGenerator gen, SerializationContext provider) throws JacksonException { gen.writeStartArray(); - for (Object element : value) { + for (Object element : array) { gen.writePOJO(element); } gen.writeEndArray(); } } + public static class RefToRefDictionarySerializer extends StdSerializer> { + + public RefToRefDictionarySerializer() { + super(RefToRefDictionary.class); + } + + @Override + public void serialize( + RefToRefDictionary dictionary, + JsonGenerator gen, + SerializationContext provider) throws JacksonException { + gen.writeStartObject(); + dictionary.forEach((key, value) -> gen.writePOJOProperty(key.toString(), value)); + gen.writeEndObject(); + } + } + private static final SimpleFilterProvider DEBUG_FIELDS_FILTER = new SimpleFilterProvider(); private static final IncludeDebugFieldsPropertyFilter INCLUDE_DEBUG_FIELDS_PROPERTY_FILTER = new IncludeDebugFieldsPropertyFilter(); @@ -113,7 +131,8 @@ public void serialize( .withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)) .addMixIn(Object.class, DebugFieldsFilterMixIn.class) .addModule(new SimpleModule() - .addSerializer(new ArraySerializer())) + .addSerializer(new ArraySerializer()) + .addSerializer(new RefToRefDictionarySerializer())) .filterProvider(DEBUG_FIELDS_FILTER) .build(); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2297629a..95c406d4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # https://gitlab.com/JavaSaBr/maven-repo/-/packages -rlib = "10.0.alpha5" +rlib = "10.0.alpha6" # https://mvnrepository.com/artifact/org.projectlombok/lombok lombok = "1.18.38" # https://mvnrepository.com/artifact/org.jspecify/jspecify diff --git a/model/build.gradle b/model/build.gradle index df6832c1..61eb2494 100644 --- a/model/build.gradle +++ b/model/build.gradle @@ -9,4 +9,5 @@ dependencies { testImplementation projects.testSupport testImplementation libs.rlib.logger.impl + testFixturesApi projects.testSupport } \ No newline at end of file diff --git a/model/src/main/java/javasabr/mqtt/model/ActionResult.java b/model/src/main/java/javasabr/mqtt/model/ActionResult.java deleted file mode 100644 index fd7495a6..00000000 --- a/model/src/main/java/javasabr/mqtt/model/ActionResult.java +++ /dev/null @@ -1,17 +0,0 @@ -package javasabr.mqtt.model; - -public enum ActionResult { - SUCCESS, - FAILED, - EMPTY; - - public ActionResult and(ActionResult another) { - if (this == FAILED || another == FAILED) { - return FAILED; - } else if (this == SUCCESS || another == SUCCESS) { - return SUCCESS; - } else { - return EMPTY; - } - } -} diff --git a/model/src/main/java/javasabr/mqtt/model/MqttClientConnectionConfig.java b/model/src/main/java/javasabr/mqtt/model/MqttClientConnectionConfig.java index 06e4d32d..ca78b81f 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttClientConnectionConfig.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttClientConnectionConfig.java @@ -1,17 +1,13 @@ package javasabr.mqtt.model; public record MqttClientConnectionConfig( + MqttServerConnectionConfig server, QoS maxQos, MqttVersion mqttVersion, long sessionExpiryInterval, int receiveMaxPublishes, - int maxPacketSize, + int maxMessageSize, int topicAliasMaxValue, int keepAlive, boolean requestResponseInformation, - boolean requestProblemInformation, - boolean sessionsEnabled, - boolean retainAvailable, - boolean wildcardSubscriptionAvailable, - boolean subscriptionIdAvailable, - boolean sharedSubscriptionAvailable) {} + boolean requestProblemInformation) {} diff --git a/model/src/main/java/javasabr/mqtt/model/MqttMessageProperty.java b/model/src/main/java/javasabr/mqtt/model/MqttMessageProperty.java new file mode 100644 index 00000000..50b9606b --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/MqttMessageProperty.java @@ -0,0 +1,92 @@ +package javasabr.mqtt.model; + +import java.util.stream.Stream; +import javasabr.mqtt.model.data.type.MqttDataType; +import javasabr.rlib.common.util.ClassUtils; +import javasabr.rlib.common.util.ObjectUtils; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; + +@Accessors(fluent = true, chain = false) +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public enum MqttMessageProperty { + PAYLOAD_FORMAT_INDICATOR(0x01, MqttDataType.BYTE), + MESSAGE_EXPIRY_INTERVAL(0x02, MqttDataType.INTEGER), + CONTENT_TYPE(0x03, MqttDataType.UTF_8_STRING), + RESPONSE_TOPIC(0x08, MqttDataType.UTF_8_STRING), + CORRELATION_DATA(0x09, MqttDataType.BINARY), + SUBSCRIPTION_IDENTIFIER(0x0B, MqttDataType.MULTI_BYTE_INTEGER), + SESSION_EXPIRY_INTERVAL(0x11, MqttDataType.INTEGER), + ASSIGNED_CLIENT_IDENTIFIER(0x12, MqttDataType.UTF_8_STRING), + SERVER_KEEP_ALIVE(0x13, MqttDataType.SHORT), + AUTHENTICATION_METHOD(0x15, MqttDataType.UTF_8_STRING), + AUTHENTICATION_DATA(0x16, MqttDataType.BINARY), + REQUEST_PROBLEM_INFORMATION(0x17, MqttDataType.BYTE), + WILL_DELAY_INTERVAL(0x18, MqttDataType.INTEGER), + REQUEST_RESPONSE_INFORMATION(0x19, MqttDataType.BYTE), + RESPONSE_INFORMATION(0x1A, MqttDataType.UTF_8_STRING), + SERVER_REFERENCE(0x1C, MqttDataType.UTF_8_STRING), + REASON_STRING(0x1F, MqttDataType.UTF_8_STRING), + RECEIVE_MAXIMUM_PUBLISHES(0x21, MqttDataType.SHORT), + TOPIC_ALIAS_MAXIMUM(0x22, MqttDataType.SHORT), + TOPIC_ALIAS(0x23, MqttDataType.SHORT), + MAXIMUM_QOS(0x24, MqttDataType.BYTE), + RETAIN_AVAILABLE(0x25, MqttDataType.BYTE), + USER_PROPERTY(0x26, MqttDataType.UTF_8_STRING_PAIR), + MAXIMUM_MESSAGE_SIZE(0x27, MqttDataType.INTEGER), + WILDCARD_SUBSCRIPTION_AVAILABLE(0x28, MqttDataType.BYTE), + SUBSCRIPTION_IDENTIFIER_AVAILABLE(0x29, MqttDataType.BYTE), + SHARED_SUBSCRIPTION_AVAILABLE(0x2A, MqttDataType.BYTE); + + private static final MqttMessageProperty[] PROPERTIES; + + static { + + int maxId = Stream + .of(values()) + .mapToInt(MqttMessageProperty::id) + .max() + .orElse(0); + + var result = new MqttMessageProperty[maxId + 1]; + + Stream + .of(values()) + .forEach(prop -> result[prop.id] = prop); + + PROPERTIES = result; + } + + public static MqttMessageProperty byId(int id) { + if (id < 0 || id >= PROPERTIES.length) { + throw new IllegalArgumentException("Unknown property with id: " + id); + } else { + return PROPERTIES[id]; + } + } + + @Getter + byte id; + @Getter + MqttDataType dataType; + + @Nullable + Object defaultValue; + + MqttMessageProperty(int id, MqttDataType dataType) { + this(id, dataType, null); + } + + MqttMessageProperty(int id, MqttDataType dataType, @Nullable Object defaultValue) { + this.id = (byte) id; + this.dataType = dataType; + this.defaultValue = defaultValue; + } + + public T defaultValue() { + return ClassUtils.unsafeNNCast(ObjectUtils.notNull(defaultValue)); + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/MqttProperties.java b/model/src/main/java/javasabr/mqtt/model/MqttProperties.java index 27906f8b..334559b0 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttProperties.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttProperties.java @@ -4,7 +4,7 @@ public interface MqttProperties { QoS MAXIMUM_QOS_DEFAULT = QoS.EXACTLY_ONCE; - int MAXIMUM_PROTOCOL_PACKET_SIZE = 256 * 1024 * 1024; + int MAXIMUM_PROTOCOL_MESSAGE_SIZE = 256 * 1024 * 1024; int MAXIMUM_PACKET_ID = 0xFFFF; long SESSION_EXPIRY_INTERVAL_DISABLED = 0; @@ -13,20 +13,19 @@ public interface MqttProperties { long SESSION_EXPIRY_INTERVAL_INFINITY = 0xFFFFFFFFL; long SESSION_EXPIRY_INTERVAL_UNDEFINED = -1; - int RECEIVE_MAXIMUM_UNDEFINED = -1; - int RECEIVE_MAXIMUM_MIN = 1; - int RECEIVE_MAXIMUM_DEFAULT = 10; - int RECEIVE_MAXIMUM_MAX = 0xFFFF; + int RECEIVE_MAXIMUM_PUBLISHES_UNDEFINED = -1; + int RECEIVE_MAXIMUM_PUBLISHES_MIN = 1; + int RECEIVE_MAXIMUM_PUBLISHES_DEFAULT = 10; + int RECEIVE_MAXIMUM_PUBLISHES_MAX = 0xFFFF; - int MAXIMUM_PACKET_SIZE_UNDEFINED = -1; - int MAXIMUM_PACKET_SIZE_DEFAULT = 1024; - int MAXIMUM_PACKET_SIZE_MIN = 1; - int MAXIMUM_PACKET_SIZE_MAX = MAXIMUM_PROTOCOL_PACKET_SIZE; + int MAXIMUM_MESSAGE_SIZE_UNDEFINED = -1; + int MAXIMUM_MESSAGE_SIZE_DEFAULT = 3074; + int MAXIMUM_MESSAGE_SIZE_MIN = 128; + int MAXIMUM_MESSAGE_SIZE_MAX = MAXIMUM_PROTOCOL_MESSAGE_SIZE; - int MAXIMUM_STRING_LENGTH = 2048; + int MAXIMUM_STRING_LENGTH = 1024; int MAXIMUM_BINARY_SIZE = 2048; - - boolean PAYLOAD_FORMAT_INDICATOR_DEFAULT = false; + int MAXIMUM_TOPIC_LEVELS = 10; long MESSAGE_EXPIRY_INTERVAL_UNDEFINED = -1; long MESSAGE_EXPIRY_INTERVAL_INFINITY = 0; @@ -40,19 +39,23 @@ public interface MqttProperties { int SERVER_KEEP_ALIVE_MIN = 0; int SERVER_KEEP_ALIVE_MAX = 0xFFFF; - int TOPIC_ALIAS_DEFAULT = 0; - int TOPIC_ALIAS_MIN = 0; + int TOPIC_ALIAS_UNDEFINED = 0; + int TOPIC_ALIAS_MIN = 1; + int TOPIC_ALIAS_DEFAULT = 10; int TOPIC_ALIAS_MAX = 0xFFFF; int TOPIC_ALIAS_NOT_SET = 0; int SUBSCRIPTION_ID_UNDEFINED = 0; + int MESSAGE_ID_UNDEFINED = -1; + int MESSAGE_ID_IS_NOT_SET = 0; + boolean SESSIONS_ENABLED_DEFAULT = true; boolean KEEP_ALIVE_ENABLED_DEFAULT = false; boolean RETAIN_AVAILABLE_DEFAULT = false; boolean WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT = false; boolean SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT = false; - boolean SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT = false; + boolean SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT = true; int PACKET_ID_FOR_QOS_0 = 0; } diff --git a/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java b/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java index 179e95ff..c41095b1 100644 --- a/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java +++ b/model/src/main/java/javasabr/mqtt/model/MqttServerConnectionConfig.java @@ -1,10 +1,13 @@ package javasabr.mqtt.model; +import javasabr.rlib.common.util.NumberUtils; + public record MqttServerConnectionConfig( QoS maxQos, - int maxPacketSize, + int maxMessageSize, int maxStringLength, int maxBinarySize, + int maxTopicLevels, int minKeepAliveTime, int receiveMaxPublishes, int topicAliasMaxValue, @@ -15,4 +18,112 @@ public record MqttServerConnectionConfig( boolean wildcardSubscriptionAvailable, boolean subscriptionIdAvailable, boolean sharedSubscriptionAvailable) { + + public MqttServerConnectionConfig( + QoS maxQos, + int maxMessageSize, + int maxStringLength, + int maxBinarySize, + int maxTopicLevels, + int minKeepAliveTime, + int receiveMaxPublishes, + int topicAliasMaxValue, + long defaultSessionExpiryInterval, + boolean keepAliveEnabled, + boolean sessionsEnabled, + boolean retainAvailable, + boolean wildcardSubscriptionAvailable, + boolean subscriptionIdAvailable, + boolean sharedSubscriptionAvailable) { + this.maxQos = maxQos; + this.maxMessageSize = NumberUtils.validate( + maxMessageSize, + MqttProperties.MAXIMUM_MESSAGE_SIZE_MIN, + MqttProperties.MAXIMUM_MESSAGE_SIZE_MAX); + this.maxStringLength = NumberUtils.validate( + maxStringLength, + 1, + maxMessageSize / 2); + this.maxBinarySize = NumberUtils.validate( + maxBinarySize, + 1, + maxMessageSize); + this.maxTopicLevels = NumberUtils.validate( + maxTopicLevels, + 1, + Byte.MAX_VALUE); + this.minKeepAliveTime = NumberUtils.validate( + minKeepAliveTime, + MqttProperties.SERVER_KEEP_ALIVE_MIN, + MqttProperties.SERVER_KEEP_ALIVE_MAX); + this.receiveMaxPublishes = receiveMaxPublishes; + this.topicAliasMaxValue = NumberUtils.validate( + topicAliasMaxValue, + MqttProperties.TOPIC_ALIAS_MIN, + MqttProperties.TOPIC_ALIAS_MAX); + this.defaultSessionExpiryInterval = defaultSessionExpiryInterval; + this.keepAliveEnabled = keepAliveEnabled; + this.sessionsEnabled = sessionsEnabled; + this.retainAvailable = retainAvailable; + this.wildcardSubscriptionAvailable = wildcardSubscriptionAvailable; + this.subscriptionIdAvailable = subscriptionIdAvailable; + this.sharedSubscriptionAvailable = sharedSubscriptionAvailable; + } + + public MqttServerConnectionConfig withMaxQos(QoS maxQos) { + return new MqttServerConnectionConfig( + maxQos, + maxMessageSize, + maxStringLength, + maxBinarySize, + maxTopicLevels, + minKeepAliveTime, + receiveMaxPublishes, + topicAliasMaxValue, + defaultSessionExpiryInterval, + keepAliveEnabled, + sessionsEnabled, + retainAvailable, + wildcardSubscriptionAvailable, + subscriptionIdAvailable, + sharedSubscriptionAvailable); + } + + public MqttServerConnectionConfig withWildcardSubscriptionAvailable(boolean wildcardSubscriptionAvailable) { + return new MqttServerConnectionConfig( + maxQos, + maxMessageSize, + maxStringLength, + maxBinarySize, + maxTopicLevels, + minKeepAliveTime, + receiveMaxPublishes, + topicAliasMaxValue, + defaultSessionExpiryInterval, + keepAliveEnabled, + sessionsEnabled, + retainAvailable, + wildcardSubscriptionAvailable, + subscriptionIdAvailable, + sharedSubscriptionAvailable); + } + + public MqttServerConnectionConfig withSharedSubscriptionAvailable(boolean sharedSubscriptionAvailable) { + return new MqttServerConnectionConfig( + maxQos, + maxMessageSize, + maxStringLength, + maxBinarySize, + maxTopicLevels, + minKeepAliveTime, + receiveMaxPublishes, + topicAliasMaxValue, + defaultSessionExpiryInterval, + keepAliveEnabled, + sessionsEnabled, + retainAvailable, + wildcardSubscriptionAvailable, + subscriptionIdAvailable, + sharedSubscriptionAvailable); + } } diff --git a/model/src/main/java/javasabr/mqtt/model/MqttUser.java b/model/src/main/java/javasabr/mqtt/model/MqttUser.java deleted file mode 100644 index f7f686ed..00000000 --- a/model/src/main/java/javasabr/mqtt/model/MqttUser.java +++ /dev/null @@ -1,3 +0,0 @@ -package javasabr.mqtt.model; - -public interface MqttUser {} diff --git a/model/src/main/java/javasabr/mqtt/model/PacketProperty.java b/model/src/main/java/javasabr/mqtt/model/PacketProperty.java deleted file mode 100644 index 0c57f9ce..00000000 --- a/model/src/main/java/javasabr/mqtt/model/PacketProperty.java +++ /dev/null @@ -1,92 +0,0 @@ -package javasabr.mqtt.model; - -import java.util.stream.Stream; -import javasabr.mqtt.model.data.type.PacketDataType; -import javasabr.rlib.common.util.ClassUtils; -import javasabr.rlib.common.util.ObjectUtils; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.experimental.Accessors; -import lombok.experimental.FieldDefaults; -import org.jspecify.annotations.Nullable; - -@Accessors(fluent = true, chain = false) -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public enum PacketProperty { - PAYLOAD_FORMAT_INDICATOR(0x01, PacketDataType.BYTE), - MESSAGE_EXPIRY_INTERVAL(0x02, PacketDataType.INTEGER), - CONTENT_TYPE(0x03, PacketDataType.UTF_8_STRING), - RESPONSE_TOPIC(0x08, PacketDataType.UTF_8_STRING), - CORRELATION_DATA(0x09, PacketDataType.BINARY), - SUBSCRIPTION_IDENTIFIER(0x0B, PacketDataType.MULTI_BYTE_INTEGER), - SESSION_EXPIRY_INTERVAL(0x11, PacketDataType.INTEGER), - ASSIGNED_CLIENT_IDENTIFIER(0x12, PacketDataType.UTF_8_STRING), - SERVER_KEEP_ALIVE(0x13, PacketDataType.SHORT), - AUTHENTICATION_METHOD(0x15, PacketDataType.UTF_8_STRING), - AUTHENTICATION_DATA(0x16, PacketDataType.BINARY), - REQUEST_PROBLEM_INFORMATION(0x17, PacketDataType.BYTE), - WILL_DELAY_INTERVAL(0x18, PacketDataType.INTEGER), - REQUEST_RESPONSE_INFORMATION(0x19, PacketDataType.BYTE), - RESPONSE_INFORMATION(0x1A, PacketDataType.UTF_8_STRING), - SERVER_REFERENCE(0x1C, PacketDataType.UTF_8_STRING), - REASON_STRING(0x1F, PacketDataType.UTF_8_STRING), - RECEIVE_MAXIMUM_PUBLISH(0x21, PacketDataType.SHORT), - TOPIC_ALIAS_MAXIMUM(0x22, PacketDataType.SHORT), - TOPIC_ALIAS(0x23, PacketDataType.SHORT), - MAXIMUM_QOS(0x24, PacketDataType.BYTE), - RETAIN_AVAILABLE(0x25, PacketDataType.BYTE), - USER_PROPERTY(0x26, PacketDataType.UTF_8_STRING_PAIR), - MAXIMUM_PACKET_SIZE(0x27, PacketDataType.INTEGER), - WILDCARD_SUBSCRIPTION_AVAILABLE(0x28, PacketDataType.BYTE), - SUBSCRIPTION_IDENTIFIER_AVAILABLE(0x29, PacketDataType.BYTE), - SHARED_SUBSCRIPTION_AVAILABLE(0x2A, PacketDataType.BYTE); - - private static final PacketProperty[] PROPERTIES; - - static { - - int maxId = Stream - .of(values()) - .mapToInt(PacketProperty::id) - .max() - .orElse(0); - - var result = new PacketProperty[maxId + 1]; - - Stream - .of(values()) - .forEach(prop -> result[prop.id] = prop); - - PROPERTIES = result; - } - - public static PacketProperty byId(int id) { - if (id < 0 || id >= PROPERTIES.length) { - throw new IllegalArgumentException("Unknown property with id: " + id); - } else { - return PROPERTIES[id]; - } - } - - @Getter - byte id; - @Getter - PacketDataType dataType; - - @Nullable - Object defaultValue; - - PacketProperty(int id, PacketDataType dataType) { - this(id, dataType, null); - } - - PacketProperty(int id, PacketDataType dataType, @Nullable Object defaultValue) { - this.id = (byte) id; - this.dataType = dataType; - this.defaultValue = defaultValue; - } - - public T defaultValue() { - return ClassUtils.unsafeNNCast(ObjectUtils.notNull(defaultValue)); - } -} diff --git a/model/src/main/java/javasabr/mqtt/model/PayloadFormat.java b/model/src/main/java/javasabr/mqtt/model/PayloadFormat.java new file mode 100644 index 00000000..70fd8361 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/PayloadFormat.java @@ -0,0 +1,29 @@ +package javasabr.mqtt.model; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; + +@Getter +@RequiredArgsConstructor +@Accessors(fluent = true) +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public enum PayloadFormat { + BINARY(0), + UTF8_STRING(1), + INVALID(2), + UNDEFINED(3); + + public static PayloadFormat fromCode(long code) { + if (BINARY.code == code) { + return BINARY; + } else if (UTF8_STRING.code == code) { + return UTF8_STRING; + } + return UNDEFINED; + } + + int code; +} diff --git a/model/src/main/java/javasabr/mqtt/model/QoS.java b/model/src/main/java/javasabr/mqtt/model/QoS.java index e02e9858..20be1150 100644 --- a/model/src/main/java/javasabr/mqtt/model/QoS.java +++ b/model/src/main/java/javasabr/mqtt/model/QoS.java @@ -19,7 +19,7 @@ public enum QoS { private static final QoS[] VALUES = values(); - public static QoS of(int level) { + public static QoS ofCode(int level) { if (level < 0 || level > EXACTLY_ONCE.ordinal()) { return INVALID; } else { @@ -27,6 +27,6 @@ public static QoS of(int level) { } } - int index; + int level; SubscribeAckReasonCode subscribeAckReasonCode; } diff --git a/model/src/main/java/javasabr/mqtt/model/TrackableMessage.java b/model/src/main/java/javasabr/mqtt/model/TrackableMessage.java new file mode 100644 index 00000000..d406861e --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/TrackableMessage.java @@ -0,0 +1,6 @@ +package javasabr.mqtt.model; + +public interface TrackableMessage { + + int messageId(); +} diff --git a/model/src/main/java/javasabr/mqtt/model/data/type/PacketDataType.java b/model/src/main/java/javasabr/mqtt/model/data/type/MqttDataType.java similarity index 82% rename from model/src/main/java/javasabr/mqtt/model/data/type/PacketDataType.java rename to model/src/main/java/javasabr/mqtt/model/data/type/MqttDataType.java index 488924bf..227c7b47 100644 --- a/model/src/main/java/javasabr/mqtt/model/data/type/PacketDataType.java +++ b/model/src/main/java/javasabr/mqtt/model/data/type/MqttDataType.java @@ -1,6 +1,6 @@ package javasabr.mqtt.model.data.type; -public enum PacketDataType { +public enum MqttDataType { BYTE, SHORT, INTEGER, diff --git a/model/src/main/java/javasabr/mqtt/model/exception/ConnectionRejectException.java b/model/src/main/java/javasabr/mqtt/model/exception/ConnectionRejectException.java index aee4be5c..d84e5ca1 100644 --- a/model/src/main/java/javasabr/mqtt/model/exception/ConnectionRejectException.java +++ b/model/src/main/java/javasabr/mqtt/model/exception/ConnectionRejectException.java @@ -1,11 +1,15 @@ package javasabr.mqtt.model.exception; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import lombok.AccessLevel; import lombok.Getter; +import lombok.experimental.FieldDefaults; +@Getter +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class ConnectionRejectException extends MqttException { - private final @Getter ConnectAckReasonCode reasonCode; + ConnectAckReasonCode reasonCode; public ConnectionRejectException(ConnectAckReasonCode reasonCode) { this.reasonCode = reasonCode; diff --git a/model/src/main/java/javasabr/mqtt/model/exception/MalformedPacketMqttException.java b/model/src/main/java/javasabr/mqtt/model/exception/MalformedPacketMqttException.java deleted file mode 100644 index 1d70d79a..00000000 --- a/model/src/main/java/javasabr/mqtt/model/exception/MalformedPacketMqttException.java +++ /dev/null @@ -1,6 +0,0 @@ -package javasabr.mqtt.model.exception; - -import lombok.NoArgsConstructor; - -@NoArgsConstructor -public class MalformedPacketMqttException extends MqttException {} diff --git a/model/src/main/java/javasabr/mqtt/model/exception/MalformedProtocolMqttException.java b/model/src/main/java/javasabr/mqtt/model/exception/MalformedProtocolMqttException.java new file mode 100644 index 00000000..0218e991 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/exception/MalformedProtocolMqttException.java @@ -0,0 +1,10 @@ +package javasabr.mqtt.model.exception; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class MalformedProtocolMqttException extends MqttException { + public MalformedProtocolMqttException(String message) { + super(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 new file mode 100644 index 00000000..ab5cc8ff --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/publishing/Publish.java @@ -0,0 +1,76 @@ +package javasabr.mqtt.model.publishing; + +import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.PayloadFormat; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.TrackableMessage; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.IntArray; +import org.jspecify.annotations.Nullable; + +public record Publish( + int messageId, + QoS qos, + TopicName topicName, + @Nullable TopicName responseTopicName, + byte[] payload, + boolean duplicated, + boolean retained, + @Nullable String contentType, + IntArray subscriptionIds, + byte @Nullable [] correlationData, + long messageExpiryInterval, + int topicAlias, + PayloadFormat payloadFormat, + Array userProperties) implements TrackableMessage { + + static { + DebugUtils.registerIncludedFields("topicName", "messageId", "qos", "topicAlias", "payloadFormat"); + } + + public Publish withDuplicated() { + if (duplicated) { + return this; + } + return new Publish( + messageId, + qos, + topicName, + responseTopicName, + payload, + true, + retained, + contentType, + subscriptionIds, + correlationData, + messageExpiryInterval, + topicAlias, + payloadFormat, + userProperties); + } + + public Publish with(int messageId, QoS qos, boolean duplicated, int topicAlias) { + return new Publish( + messageId, + qos, + topicName, + responseTopicName, + payload, + duplicated, + retained, + contentType, + subscriptionIds, + correlationData, + messageExpiryInterval, + topicAlias, + payloadFormat, + userProperties); + } + + @Override + public String toString() { + return DebugUtils.toJsonString(this); + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/util/package-info.java b/model/src/main/java/javasabr/mqtt/model/publishing/package-info.java similarity index 58% rename from model/src/main/java/javasabr/mqtt/model/util/package-info.java rename to model/src/main/java/javasabr/mqtt/model/publishing/package-info.java index c4bb5fa9..508e1789 100644 --- a/model/src/main/java/javasabr/mqtt/model/util/package-info.java +++ b/model/src/main/java/javasabr/mqtt/model/publishing/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package javasabr.mqtt.model.util; +package javasabr.mqtt.model.publishing; import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/DisconnectReasonCode.java b/model/src/main/java/javasabr/mqtt/model/reason/code/DisconnectReasonCode.java index 5849c17c..a8bb2a31 100644 --- a/model/src/main/java/javasabr/mqtt/model/reason/code/DisconnectReasonCode.java +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/DisconnectReasonCode.java @@ -1,20 +1,26 @@ package javasabr.mqtt.model.reason.code; -import java.util.stream.Stream; -import javasabr.rlib.common.util.ObjectUtils; +import javasabr.rlib.common.util.NumberedEnum; +import javasabr.rlib.common.util.NumberedEnumMap; +import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +@Getter @RequiredArgsConstructor -public enum DisconnectReasonCode { +@Accessors(fluent = true, chain = false) +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public enum DisconnectReasonCode implements NumberedEnum { /** * Close the connection normally. Do not send the Will Message. Client or Server. */ - NORMAL_DISCONNECTION((byte) 0x00), + NORMAL_DISCONNECTION(0x00), /** * The Client wishes to disconnect but requires that the Server also publishes its Will Message. Client. */ - DISCONNECT_WITH_WILL_MESSAGE((byte) 0x04), + DISCONNECT_WITH_WILL_MESSAGE(0x04), // ERRORS @@ -22,141 +28,125 @@ public enum DisconnectReasonCode { * The Connection is closed but the sender either does not wish to reveal the reason, or none of the other Reason * Codes apply. Client or Server. */ - UNSPECIFIED_ERROR((byte) 0x80), + UNSPECIFIED_ERROR(0x80), /** * The received packet does not conform to this specification. Client or Server. */ - MALFORMED_PACKET((byte) 0x81), + MALFORMED_PACKET(0x81), /** * An unexpected or out of order packet was received. Client or Server. */ - PROTOCOL_ERROR((byte) 0x82), + PROTOCOL_ERROR(0x82), /** * The packet received is valid but cannot be processed by this implementation. Client or Server. */ - IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), + IMPLEMENTATION_SPECIFIC_ERROR(0x83), /** * The request is not authorized. Server. */ - NOT_AUTHORIZED((byte) 0x87), + NOT_AUTHORIZED(0x87), /** * The Server is busy and cannot continue processing requests from this Client. Server. */ - SERVER_BUSY((byte) 0x89), + SERVER_BUSY(0x89), /** * The Server is shutting down. Server. */ - SERVER_SHUTTING_DOWN((byte) 0x8B), + SERVER_SHUTTING_DOWN(0x8B), /** * The Connection is closed because no packet has been received for 1.5 times the Keepalive time. Server. */ - KEEP_ALIVE_TIMEOUT((byte) 0x8D), + KEEP_ALIVE_TIMEOUT(0x8D), /** * Another Connection using the same ClientID has connected causing this Connection to be closed. Server. */ - SESSION_TAKEN_OVER((byte) 0x8E), + SESSION_TAKEN_OVER(0x8E), /** * The Topic Filter is correctly formed, but is not accepted by this Sever. Server. */ - TOPIC_FILTER_INVALID((byte) 0x8F), + TOPIC_FILTER_INVALID(0x8F), /** * The Topic Name is correctly formed, but is not accepted by this Client or Server. Client or Server. */ - TOPIC_NAME_INVALID((byte) 0x90), + TOPIC_NAME_INVALID(0x90), /** * The Client or Server has received more than Receive Maximum publication for which it has not sent PUBACK or * PUBCOMP. Client or Server. */ - RECEIVE_MAXIMUM_EXCEEDED((byte) 0x93), + RECEIVE_MAXIMUM_EXCEEDED(0x93), /** * The Client or Server has received a PUBLISH packet containing a Topic Alias which is greater than the Maximum Topic * Alias it sent in the CONNECT or CONNACK packet. Client or Server. */ - TOPIC_ALIAS_INVALID((byte) 0x94), + TOPIC_ALIAS_INVALID(0x94), /** * The packet size is greater than Maximum Packet Size for this Client or Server. Client or Server. */ - PACKET_TOO_LARGE((byte) 0x95), + PACKET_TOO_LARGE(0x95), /** * The received data rate is too high. Client or Server. */ - MESSAGE_RATE_TOO_HIGH((byte) 0x96), + MESSAGE_RATE_TOO_HIGH(0x96), /** * An implementation or administrative imposed limit has been exceeded. Client or Server. */ - QUOTA_EXCEEDED((byte) 0x97), + QUOTA_EXCEEDED(0x97), /** * The Connection is closed due to an administrative action. Client or Server. */ - ADMINISTRATIVE_ACTION((byte) 0x98), + ADMINISTRATIVE_ACTION(0x98), /** * The payload format does not match the one specified by the Payload Format Indicator. Client or Server. */ - PAYLOAD_FORMAT_INVALID((byte) 0x99), + PAYLOAD_FORMAT_INVALID(0x99), /** * The Server has does not support retained messages. Server. */ - RETAIN_NOT_SUPPORTED((byte) 0x9A), + RETAIN_NOT_SUPPORTED(0x9A), /** * The Client specified a QoS greater than the QoS specified in a Maximum QoS in the CONNACK. Server. */ - QOS_NOT_SUPPORTED((byte) 0x9B), + QOS_NOT_SUPPORTED(0x9B), /** * The Client should temporarily change its Server. Server. */ - USE_ANOTHER_SERVER((byte) 0x9C), + USE_ANOTHER_SERVER(0x9C), /** * The Server is moved and the Client should permanently change its server location. Server. */ - SERVER_MOVED((byte) 0x9D), + SERVER_MOVED(0x9D), /** * The Server does not support Shared Subscriptions. Server. */ - SHARED_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0x9E), + SHARED_SUBSCRIPTIONS_NOT_SUPPORTED(0x9E), /** * This connection is closed because the connection rate is too high. Server. */ - CONNECTION_RATE_EXCEEDED((byte) 0x9F), + CONNECTION_RATE_EXCEEDED(0x9F), /** * The maximum connection time authorized for this connection has been exceeded. Server. */ - MAXIMUM_CONNECT_TIME((byte) 0xA0), + MAXIMUM_CONNECT_TIME(0xA0), /** * The Server does not support Subscription Identifiers; the subscription is not accepted. Server. */ - SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED((byte) 0xA1), + SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED(0xA1), /** * The Server does not support Wildcard Subscriptions; the subscription is not accepted. Server. */ - WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0xA2); + WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED(0xA2); - private static final DisconnectReasonCode[] VALUES; + private static final NumberedEnumMap NUMBERED_MAP = + new NumberedEnumMap<>(DisconnectReasonCode.class); - static { - - var maxId = Stream - .of(values()) - .mapToInt(DisconnectReasonCode::getValue) - .map(value -> Byte.toUnsignedInt((byte) value)) - .max() - .orElse(0); - - var values = new DisconnectReasonCode[maxId + 1]; - - for (var value : values()) { - values[Byte.toUnsignedInt(value.value)] = value; - } - - VALUES = values; + public static DisconnectReasonCode ofCode(int code) { + return NUMBERED_MAP.require(code); } - public static DisconnectReasonCode of(int index) { - return ObjectUtils.notNull( - VALUES[index], - index, - arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg)); - } + int code; - private @Getter - final byte value; + @Override + public int number() { + return code; + } } diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/SubscribeAckReasonCode.java b/model/src/main/java/javasabr/mqtt/model/reason/code/SubscribeAckReasonCode.java index 7541f76c..4c7d7f03 100644 --- a/model/src/main/java/javasabr/mqtt/model/reason/code/SubscribeAckReasonCode.java +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/SubscribeAckReasonCode.java @@ -1,24 +1,30 @@ package javasabr.mqtt.model.reason.code; -import java.util.stream.Stream; -import javasabr.rlib.common.util.ObjectUtils; +import javasabr.rlib.common.util.NumberedEnum; +import javasabr.rlib.common.util.NumberedEnumMap; +import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +@Getter @RequiredArgsConstructor -public enum SubscribeAckReasonCode { +@Accessors(fluent = true, chain = false) +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public enum SubscribeAckReasonCode implements NumberedEnum { /** * The subscription is accepted and the maximum QoS sent will be QoS 0. This might be a lower QoS than was requested. */ - GRANTED_QOS_0((byte) 0x00), + GRANTED_QOS_0(0x00), /** * The subscription is accepted and the maximum QoS sent will be QoS 1. This might be a lower QoS than was requested. */ - GRANTED_QOS_1((byte) 0x01), + GRANTED_QOS_1(0x01), /** * The subscription is accepted and any received QoS will be sent to this subscription. */ - GRANTED_QOS_2((byte) 0x02), + GRANTED_QOS_2(0x02), // ERRORS @@ -26,67 +32,51 @@ public enum SubscribeAckReasonCode { * The subscription is not accepted and the Server either does not wish to reveal the reason or none of the other * Reason Codes apply. */ - UNSPECIFIED_ERROR((byte) 0x80), + UNSPECIFIED_ERROR(0x80), /** * The SUBSCRIBE is valid but the Server does not accept it. */ - IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), + IMPLEMENTATION_SPECIFIC_ERROR(0x83), /** * The Client is not authorized to make this subscription. */ - NOT_AUTHORIZED((byte) 0x87), + NOT_AUTHORIZED(0x87), /** * The Topic Filter is correctly formed but is not allowed for this Client. */ - TOPIC_FILTER_INVALID((byte) 0x8F), + TOPIC_FILTER_INVALID(0x8F), /** * The specified Packet Identifier is already in use. */ - PACKET_IDENTIFIER_IN_USE((byte) 0x91), + PACKET_IDENTIFIER_IN_USE(0x91), /** * An implementation or administrative imposed limit has been exceeded. */ - QUOTA_EXCEEDED((byte) 0x97), + QUOTA_EXCEEDED(0x97), /** * The Server does not support Shared Subscriptions for this Client. */ - SHARED_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0x9E), + SHARED_SUBSCRIPTIONS_NOT_SUPPORTED(0x9E), /** * The Server does not support Subscription Identifiers; the subscription is not accepted. */ - SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED((byte) 0xA1), + SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED(0xA1), /** * The Server does not support Wildcard Subscriptions; the subscription is not accepted. */ - WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0xA2); + WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED(0xA2); - private static final SubscribeAckReasonCode[] VALUES; + private static final NumberedEnumMap NUMBERED_MAP = + new NumberedEnumMap<>(SubscribeAckReasonCode.class); - static { - - var maxId = Stream - .of(values()) - .mapToInt(SubscribeAckReasonCode::getValue) - .map(value -> Byte.toUnsignedInt((byte) value)) - .max() - .orElse(0); - - var values = new SubscribeAckReasonCode[maxId + 1]; - - for (var value : values()) { - values[Byte.toUnsignedInt(value.value)] = value; - } - - VALUES = values; + public static SubscribeAckReasonCode ofCode(int code) { + return NUMBERED_MAP.require(code); } - public static SubscribeAckReasonCode of(int index) { - return ObjectUtils.notNull( - VALUES[index], - index, - arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg)); - } + int code; - @Getter - private final byte value; + @Override + public int number() { + return code; + } } diff --git a/model/src/main/java/javasabr/mqtt/model/subscriber/SharedSubscriber.java b/model/src/main/java/javasabr/mqtt/model/subscriber/SharedSubscriber.java index 45cd46fd..810becbc 100644 --- a/model/src/main/java/javasabr/mqtt/model/subscriber/SharedSubscriber.java +++ b/model/src/main/java/javasabr/mqtt/model/subscriber/SharedSubscriber.java @@ -2,53 +2,60 @@ import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; -import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.subscribtion.SubscriptionOwner; import javasabr.mqtt.model.topic.SharedTopicFilter; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.LockableArray; +import lombok.AccessLevel; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +@Accessors(fluent = true, makeFinal = true) +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public final class SharedSubscriber implements Subscriber { - private static SingleSubscriber next(Array subscribers, int current) { - return subscribers.get(current % subscribers.size()); - } + SharedTopicFilter topicFilter; + LockableArray subscribers; + AtomicInteger current; - private final SharedTopicFilter topicFilter; - private final LockableArray subscribers; - private final AtomicInteger current; - - public SharedSubscriber(SubscribeTopicFilter topic) { + public SharedSubscriber(SharedTopicFilter topicFilter) { this.subscribers = ArrayFactory.stampedLockBasedArray(Subscriber.class); this.current = new AtomicInteger(0); - this.topicFilter = (SharedTopicFilter) topic.getTopicFilter(); + this.topicFilter = topicFilter; } - public SingleSubscriber getSubscriber() { - //noinspection ConstantConditions - return subscribers - .operations() - .getInReadLock(current.getAndIncrement(), SharedSubscriber::next); + @Override + public SingleSubscriber resolveSingle() { + int nextIndex = current.incrementAndGet(); + long stamp = subscribers.readLock(); + try { + return next(subscribers, nextIndex); + } finally { + subscribers.readUnlock(stamp); + } } - public void addSubscriber(SingleSubscriber client) { - subscribers - .operations() - .inWriteLock(client, Collection::add); + public void addSubscriber(SingleSubscriber subscriber) { + subscribers.operations() + .inWriteLock(subscriber, Collection::add); } - public boolean removeSubscriber(MqttUser user) { - return subscribers - .operations() - .getInWriteLock( - user, (singleSubscribers, mqttClient) -> { - int index = singleSubscribers.indexOf(SingleSubscriber::getUser, mqttClient); - if (index >= 0) { - singleSubscribers.remove(index); - return true; - } - return false; - }); + public boolean removeSubscriberWithOwner(SubscriptionOwner owner) { + if (subscribers.isEmpty()) { + return false; + } + long stamp = subscribers.writeLock(); + try { + int index = subscribers.indexOf(SingleSubscriber::owner, owner); + if (index >= 0) { + subscribers.remove(index); + return true; + } + } finally { + subscribers.writeUnlock(stamp); + } + return false; } public int size() { @@ -56,7 +63,15 @@ public int size() { return subscribers.size(); } - public String getGroup() { - return topicFilter.getGroup(); + public boolean isEmpty() { + return subscribers.isEmpty(); + } + + public String group() { + return topicFilter.shareName(); + } + + private static SingleSubscriber next(Array subscribers, int current) { + return subscribers.get(current % subscribers.size()); } } diff --git a/model/src/main/java/javasabr/mqtt/model/subscriber/SingleSubscriber.java b/model/src/main/java/javasabr/mqtt/model/subscriber/SingleSubscriber.java index 10f4b9bd..16d5fc4a 100644 --- a/model/src/main/java/javasabr/mqtt/model/subscriber/SingleSubscriber.java +++ b/model/src/main/java/javasabr/mqtt/model/subscriber/SingleSubscriber.java @@ -1,22 +1,24 @@ package javasabr.mqtt.model.subscriber; -import javasabr.mqtt.model.MqttUser; +import com.fasterxml.jackson.annotation.JsonValue; import javasabr.mqtt.model.QoS; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; +import javasabr.mqtt.model.subscribtion.Subscription; +import javasabr.mqtt.model.subscribtion.SubscriptionOwner; -@ToString -@EqualsAndHashCode(of = "user") -@RequiredArgsConstructor -public final class SingleSubscriber implements Subscriber { +public record SingleSubscriber(SubscriptionOwner owner, Subscription subscription) implements Subscriber { - @Getter - private final MqttUser user; - private final SubscribeTopicFilter subscribe; + @Override + public SingleSubscriber resolveSingle() { + return this; + } + + public QoS qos() { + return subscription.qos(); + } - public QoS getQos() { - return subscribe.getQos(); + @JsonValue + @Override + public String toString() { + return "[" + owner + "]->[" + subscription.topicFilter().rawTopic() + "|" + subscription.qos().level() + "]"; } } diff --git a/model/src/main/java/javasabr/mqtt/model/subscriber/SubscribeTopicFilter.java b/model/src/main/java/javasabr/mqtt/model/subscriber/SubscribeTopicFilter.java deleted file mode 100644 index 6a647bd7..00000000 --- a/model/src/main/java/javasabr/mqtt/model/subscriber/SubscribeTopicFilter.java +++ /dev/null @@ -1,61 +0,0 @@ -package javasabr.mqtt.model.subscriber; - -import static javasabr.mqtt.model.util.TopicUtils.buildTopicFilter; - -import javasabr.mqtt.model.QoS; -import javasabr.mqtt.model.SubscribeRetainHandling; -import javasabr.mqtt.model.topic.TopicFilter; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@EqualsAndHashCode -@RequiredArgsConstructor -public class SubscribeTopicFilter { - - /** - * The subscriber's topic filter. - */ - private final TopicFilter topicFilter; - - /** - * Maximum QoS field. This gives the maximum QoS level at which the Server can send Application Messages to the - * Client. - */ - private final QoS qos; - - /** - * This option specifies whether retained messages are sent when the subscription is established. This does not affect - * the sending of retained messages at any point after the subscribe. If there are no retained messages matching the - * Topic Filter, all of these values act the same. - */ - private final SubscribeRetainHandling retainHandling; - - /** - * If the value is true, Application Messages MUST NOT be forwarded to a connection with a ClientID equal to the - * ClientID of the publishing connection. - */ - private final boolean noLocal; - - /** - * If true, Application Messages forwarded using this subscription keep the RETAIN flag they were published with. If - * false, Application Messages forwarded using this subscription have the RETAIN flag set to 0. Retained messages sent - * when the subscription is established have the RETAIN flag set to 1. - */ - private final boolean retainAsPublished; - - public SubscribeTopicFilter(String topicFilter, QoS qos) { - this(buildTopicFilter(topicFilter), qos, SubscribeRetainHandling.SEND, true, true); - } - - public SubscribeTopicFilter(TopicFilter topicFilter, QoS qos) { - this(topicFilter, qos, SubscribeRetainHandling.SEND, true, true); - } - - @Override - public String toString() { - return "SubscribeTopicFilter(" + "topicFilter=" + topicFilter.getRawTopic() + ", qos=" + qos + ", retainHandling=" - + retainHandling + ", noLocal=" + noLocal + ", retainAsPublished=" + retainAsPublished + ')'; - } -} diff --git a/model/src/main/java/javasabr/mqtt/model/subscriber/Subscriber.java b/model/src/main/java/javasabr/mqtt/model/subscriber/Subscriber.java index 4953644c..fd162ecb 100644 --- a/model/src/main/java/javasabr/mqtt/model/subscriber/Subscriber.java +++ b/model/src/main/java/javasabr/mqtt/model/subscriber/Subscriber.java @@ -1,3 +1,18 @@ package javasabr.mqtt.model.subscriber; -public sealed interface Subscriber permits SingleSubscriber, SharedSubscriber {} +import javasabr.mqtt.model.subscribtion.SubscriptionOwner; + +public sealed interface Subscriber permits SingleSubscriber, SharedSubscriber { + + /** + * Resolves the owner of a subscription to send a publishing. + */ + default SubscriptionOwner resolveOwner() { + return resolveSingle().owner(); + } + + /** + * Resolves the owner of a subscription to send a publishing. + */ + SingleSubscriber resolveSingle(); +} diff --git a/model/src/main/java/javasabr/mqtt/model/subscribtion/RequestedSubscription.java b/model/src/main/java/javasabr/mqtt/model/subscribtion/RequestedSubscription.java new file mode 100644 index 00000000..d5cdfc20 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/subscribtion/RequestedSubscription.java @@ -0,0 +1,42 @@ +package javasabr.mqtt.model.subscribtion; + +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.SubscribeRetainHandling; + +public record RequestedSubscription( + /* + The subscriber's topic filter. + */ + String rawTopicFilter, + /* + Maximum QoS field. This gives the maximum QoS level at which the Server can send Application Messages to the + Client. + */ + QoS qos, + /* + This option specifies whether retained messages are sent when the subscription is established. This does not affect + the sending of retained messages at any point after the subscribe. If there are no retained messages matching the + Topic Filter, all of these values act the same. + */ + SubscribeRetainHandling retainHandling, + /* + If the value is true, Application Messages MUST NOT be forwarded to a connection with a ClientID equal to the + ClientID of the publishing connection. + */ + boolean noLocal, + /* + If true, Application Messages forwarded using this subscription keep the RETAIN flag they were published with. If + false, Application Messages forwarded using this subscription have the RETAIN flag set to 0. Retained messages sent + when the subscription is established have the RETAIN flag set to 1. + */ + boolean retainAsPublished) { + + public static RequestedSubscription minimal(String rawTopicFilter, QoS qos) { + return new RequestedSubscription( + rawTopicFilter, + qos, + SubscribeRetainHandling.SEND, + true, + true); + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/subscribtion/Subscription.java b/model/src/main/java/javasabr/mqtt/model/subscribtion/Subscription.java new file mode 100644 index 00000000..f58f83d1 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/subscribtion/Subscription.java @@ -0,0 +1,56 @@ +package javasabr.mqtt.model.subscribtion; + +import com.fasterxml.jackson.annotation.JsonValue; +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.SubscribeRetainHandling; +import javasabr.mqtt.model.topic.TopicFilter; + +public record Subscription( + /* + The subscriber's topic filter. + */ + TopicFilter topicFilter, + /* + * The associated ID for the subscription + */ + int subscriptionId, + /* + Maximum QoS field. This gives the maximum QoS level at which the Server can send Application Messages to the + Client. + */ + QoS qos, + /* + This option specifies whether retained messages are sent when the subscription is established. This does not affect + the sending of retained messages at any point after the subscribe. If there are no retained messages matching the + Topic Filter, all of these values act the same. + */ + SubscribeRetainHandling retainHandling, + /* + If the value is true, Application Messages MUST NOT be forwarded to a connection with a ClientID equal to the + ClientID of the publishing connection. + */ + boolean noLocal, + /* + If true, Application Messages forwarded using this subscription keep the RETAIN flag they were published with. If + false, Application Messages forwarded using this subscription have the RETAIN flag set to 0. Retained messages sent + when the subscription is established have the RETAIN flag set to 1. + */ + boolean retainAsPublished) { + + public static Subscription minimal(TopicFilter topicFilter, QoS qos) { + return new Subscription( + topicFilter, + MqttProperties.SUBSCRIPTION_ID_UNDEFINED, + qos, + SubscribeRetainHandling.SEND, + true, + true); + } + + @JsonValue + @Override + public String toString() { + return "[" + topicFilter.rawTopic() + "|" + qos.level() + "|" + retainHandling + "|" + noLocal + "|" + retainAsPublished + "]"; + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/subscribtion/SubscriptionOwner.java b/model/src/main/java/javasabr/mqtt/model/subscribtion/SubscriptionOwner.java new file mode 100644 index 00000000..111f037b --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/subscribtion/SubscriptionOwner.java @@ -0,0 +1,3 @@ +package javasabr.mqtt.model.subscribtion; + +public interface SubscriptionOwner {} diff --git a/model/src/main/java/javasabr/mqtt/model/subscribtion/package-info.java b/model/src/main/java/javasabr/mqtt/model/subscribtion/package-info.java new file mode 100644 index 00000000..e7e099a5 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/subscribtion/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.model.subscribtion; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file 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 8356f7e7..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,41 +1,42 @@ package javasabr.mqtt.model.topic; import javasabr.mqtt.base.util.DebugUtils; -import javasabr.mqtt.model.util.TopicUtils; +import javasabr.rlib.common.util.StringUtils; +import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; @Getter @EqualsAndHashCode(of = "rawTopic") +@Accessors(fluent = true, chain = false) +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) public abstract class AbstractTopic { + public static final String DELIMITER = "/"; + public static final char DELIMITER_CHAR = '/'; + + static { DebugUtils.registerIncludedFields("rawTopic"); } - private static final String[] EMPTY_ARRAY = new String[0]; - private static final String EMPTY = ""; - private final String[] segments; - private final String rawTopic; - private final int length; + String[] segments; + String rawTopic; + int length; - protected AbstractTopic() { - length = 0; - segments = EMPTY_ARRAY; - rawTopic = EMPTY; + protected AbstractTopic(String rawTopicName) { + length = rawTopicName.length(); + segments = splitTopic(rawTopicName); + rawTopic = rawTopicName; } - protected AbstractTopic(String topicName) { - length = topicName.length(); - segments = TopicUtils.splitTopic(topicName); - rawTopic = topicName; - } - - String getSegment(int level) { + public String segment(int level) { return segments[level]; } - int levelsCount() { + public int levelsCount() { return segments.length; } @@ -43,8 +44,38 @@ String lastSegment() { return segments[segments.length - 1]; } + public boolean isInvalid() { + return false; + } + @Override public String toString() { return rawTopic; } + + protected static String[] splitTopic(String topic) { + int segmentCount = countOccurrencesOf(topic, AbstractTopic.DELIMITER) + 1; + var segments = new String[segmentCount]; + int i = 0, pos = 0, end; + while ((end = topic.indexOf(AbstractTopic.DELIMITER, pos)) >= 0) { + segments[i++] = topic.substring(pos, end); + pos = end + 1; + } + segments[i] = topic.substring(pos); + return segments; + } + + protected static int countOccurrencesOf(String str, String sub) { + if (StringUtils.isEmpty(str)) { + return 0; + } + int count = 0; + int pos = 0; + int idx; + while ((idx = str.indexOf(sub, pos)) != -1) { + ++count; + pos = idx + sub.length(); + } + return count; + } } diff --git a/model/src/main/java/javasabr/mqtt/model/topic/SharedTopicFilter.java b/model/src/main/java/javasabr/mqtt/model/topic/SharedTopicFilter.java index f0881965..e9e3ba7d 100644 --- a/model/src/main/java/javasabr/mqtt/model/topic/SharedTopicFilter.java +++ b/model/src/main/java/javasabr/mqtt/model/topic/SharedTopicFilter.java @@ -1,15 +1,35 @@ package javasabr.mqtt.model.topic; +import lombok.AccessLevel; import lombok.Getter; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +@Getter +@Accessors(fluent = true, chain = false) +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class SharedTopicFilter extends TopicFilter { - @Getter - private final String group; + public static final String SHARE_KEYWORD = "$share"; - public SharedTopicFilter(String topicFilter, String group) { - super(topicFilter); - this.group = group; + String shareName; + + public SharedTopicFilter(String rawTopicFilter, String shareName) { + super(rawTopicFilter); + this.shareName = shareName; + } + + public static SharedTopicFilter valueOf(String rawSharedTopicFilter) { + // $share/{ShareName}/{filter} + int firstSlash = rawSharedTopicFilter.indexOf(DELIMITER) + 1; + int secondSlash = rawSharedTopicFilter.indexOf(DELIMITER, firstSlash); + String shareName = rawSharedTopicFilter.substring(firstSlash, secondSlash); + String rawTopicFilter = rawSharedTopicFilter.substring(secondSlash + 1); + return new SharedTopicFilter(rawTopicFilter, shareName); + } + + public static boolean isShared(String rawTopicFilter) { + return rawTopicFilter.startsWith(SharedTopicFilter.SHARE_KEYWORD); } } diff --git a/model/src/main/java/javasabr/mqtt/model/topic/TopicFilter.java b/model/src/main/java/javasabr/mqtt/model/topic/TopicFilter.java index d047aff8..fa9c1b6f 100644 --- a/model/src/main/java/javasabr/mqtt/model/topic/TopicFilter.java +++ b/model/src/main/java/javasabr/mqtt/model/topic/TopicFilter.java @@ -1,12 +1,37 @@ package javasabr.mqtt.model.topic; -import lombok.NoArgsConstructor; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; -@NoArgsConstructor +@Getter +@Accessors(fluent = true) +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) public class TopicFilter extends AbstractTopic { - public TopicFilter(String topicFilter) { - super(topicFilter); + public static final String MULTI_LEVEL_WILDCARD = "#"; + public static final char MULTI_LEVEL_WILDCARD_CHAR = '#'; + public static final String SINGLE_LEVEL_WILDCARD = "+"; + public static final char SINGLE_LEVEL_WILDCARD_CHAR = '+'; + public static final String SPECIAL = "$"; + + public static final TopicFilter INVALID_TOPIC_FILTER = new TopicFilter("$invalid$") { + @Override + public boolean isInvalid() { + return true; + } + }; + + boolean wildcard; + + public TopicFilter(String rawTopicFilter) { + super(rawTopicFilter); + this.wildcard = rawTopicFilter.contains(SINGLE_LEVEL_WILDCARD) || rawTopicFilter.contains(MULTI_LEVEL_WILDCARD); + } + + public static TopicFilter valueOf(String rawTopicFilter) { + return new TopicFilter(rawTopicFilter); } } diff --git a/model/src/main/java/javasabr/mqtt/model/topic/TopicName.java b/model/src/main/java/javasabr/mqtt/model/topic/TopicName.java index caee440c..93f58105 100644 --- a/model/src/main/java/javasabr/mqtt/model/topic/TopicName.java +++ b/model/src/main/java/javasabr/mqtt/model/topic/TopicName.java @@ -1,12 +1,32 @@ package javasabr.mqtt.model.topic; -import lombok.NoArgsConstructor; - -@NoArgsConstructor public class TopicName extends AbstractTopic { + public static final TopicName INVALID_TOPIC_NAME = new TopicName("$invalid$") { + @Override + public boolean isInvalid() { + return true; + } + + }; + + public static final TopicName EMPTY_TOPIC_NAME = new TopicName("") { + @Override + public boolean isEmpty() { + return true; + } + }; + public TopicName(String topicName) { super(topicName); } + + public boolean isEmpty() { + return false; + } + + public static TopicName valueOf(String rawTopicName) { + return new TopicName(rawTopicName); + } } diff --git a/model/src/main/java/javasabr/mqtt/model/topic/TopicSubscribers.java b/model/src/main/java/javasabr/mqtt/model/topic/TopicSubscribers.java deleted file mode 100644 index 9e6383fa..00000000 --- a/model/src/main/java/javasabr/mqtt/model/topic/TopicSubscribers.java +++ /dev/null @@ -1,297 +0,0 @@ -package javasabr.mqtt.model.topic; - -import java.util.Objects; -import java.util.function.Supplier; -import javasabr.mqtt.model.MqttUser; -import javasabr.mqtt.model.QoS; -import javasabr.mqtt.model.subscriber.SharedSubscriber; -import javasabr.mqtt.model.subscriber.SingleSubscriber; -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; -import javasabr.mqtt.model.subscriber.Subscriber; -import javasabr.mqtt.model.util.SubscriberUtils; -import javasabr.mqtt.model.util.TopicUtils; -import javasabr.rlib.collections.array.Array; -import javasabr.rlib.collections.array.ArrayFactory; -import javasabr.rlib.collections.array.LockableArray; -import javasabr.rlib.collections.array.MutableArray; -import javasabr.rlib.collections.dictionary.Dictionary; -import javasabr.rlib.collections.dictionary.DictionaryFactory; -import javasabr.rlib.collections.dictionary.LockableRefToRefDictionary; -import javasabr.rlib.collections.dictionary.MutableRefToRefDictionary; -import javasabr.rlib.collections.dictionary.RefToRefDictionary; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.experimental.FieldDefaults; -import org.jspecify.annotations.Nullable; - -@FieldDefaults(level = AccessLevel.PRIVATE) -public class TopicSubscribers { - - private final static Supplier TOPIC_SUBSCRIBER_SUPPLIER = TopicSubscribers::new; - - @Getter - @Nullable - volatile LockableRefToRefDictionary topicSubscribers; - - @Getter - @Nullable - volatile LockableArray subscribers; - - public void addSubscriber(MqttUser user, SubscribeTopicFilter subscribe) { - searchPlaceForSubscriber(0, subscribe.getTopicFilter(), user, subscribe); - } - - private void searchPlaceForSubscriber( - int level, - TopicFilter topicFilter, - MqttUser user, - SubscribeTopicFilter subscribe) { - if (level == topicFilter.levelsCount()) { - LockableArray subscribers = getOrCreateSubscribers(); - subscribers - .operations() - .inWriteLock(user, subscribe, TopicSubscribers::addSubscriber); - } else { - LockableRefToRefDictionary topicSubscribers = getOrCreateTopicSubscribers(); - TopicSubscribers topicSubscriber = topicSubscribers - .operations() - .getInWriteLock( - topicFilter.getSegment(level), - TOPIC_SUBSCRIBER_SUPPLIER, - MutableRefToRefDictionary::getOrCompute); - - //noinspection ConstantConditions - topicSubscriber.searchPlaceForSubscriber(level + 1, topicFilter, user, subscribe); - } - } - - public void removeSubscriber(MqttUser user, SubscribeTopicFilter subscribe) { - removeSubscriber(user, subscribe.getTopicFilter()); - } - - public boolean removeSubscriber(MqttUser user, TopicFilter topicFilter) { - return searchSubscriberToRemove(0, topicFilter, user); - } - - private boolean searchSubscriberToRemove(int level, TopicFilter topicFilter, MqttUser user) { - var removed = false; - - LockableRefToRefDictionary topicSubscribers = getTopicSubscribers(); - if (level == topicFilter.levelsCount()) { - removed = tryToRemoveSubscriber(topicFilter, user); - } else if (topicSubscribers != null) { - TopicSubscribers topicSubscriber = topicSubscribers - .operations() - .getInReadLock(topicFilter.getSegment(level), Dictionary::get); - if (topicSubscriber != null) { - removed = topicSubscriber.searchSubscriberToRemove(level + 1, topicFilter, user); - } - } - - return removed; - } - - private boolean tryToRemoveSubscriber(TopicFilter topicFilter, MqttUser user) { - LockableArray subscribers = getSubscribers(); - if (subscribers == null) { - return false; - } - return subscribers - .operations() - .getInWriteLock(topicFilter, user, TopicSubscribers::removeSubscriber); - } - - public Array matches(TopicName topicName) { - var resultArray = MutableArray.ofType(SingleSubscriber.class); - processLevel(0, topicName.getSegment(0), topicName, resultArray); - return resultArray; - } - - private void processLevel(int level, String segment, TopicName topicName, MutableArray result) { - var nextLevel = level + 1; - processSegment(nextLevel, segment, topicName, result); - processSegment(nextLevel, TopicUtils.SINGLE_LEVEL_WILDCARD, topicName, result); - processSegment(nextLevel, TopicUtils.MULTI_LEVEL_WILDCARD, topicName, result); - } - - private void processSegment( - int nextLevel, - String segment, - TopicName topicName, - MutableArray result) { - - LockableRefToRefDictionary subscribersMap = getTopicSubscribers(); - if (subscribersMap == null) { - return; - } - - TopicSubscribers topicSubscribers = subscribersMap - .operations() - .getInReadLock(segment, result, TopicSubscribers::collectSubscribers); - - if (topicSubscribers != null && nextLevel < topicName.levelsCount()) { - String nextSegment = topicName.getSegment(nextLevel); - topicSubscribers.processLevel(nextLevel, nextSegment, topicName, result); - } - } - - private LockableRefToRefDictionary getOrCreateTopicSubscribers() { - if (topicSubscribers == null) { - synchronized (this) { - if (topicSubscribers == null) { - topicSubscribers = DictionaryFactory.stampedLockBasedRefToRefDictionary(); - } - } - } - //noinspection ConstantConditions - return topicSubscribers; - } - - private LockableArray getOrCreateSubscribers() { - if (subscribers == null) { - synchronized (this) { - if (subscribers == null) { - subscribers = ArrayFactory.stampedLockBasedArray(Subscriber.class); - } - } - } - //noinspection ConstantConditions - return subscribers; - } - - @Override - public String toString() { - return "TopicSubscribers{" + "topicSubscribers=" + topicSubscribers + ", subscribers=" + subscribers + '}'; - } - - private static void addSubscriber( - LockableArray subscribers, - MqttUser user, - SubscribeTopicFilter subscribe) { - if (TopicUtils.isShared(subscribe.getTopicFilter())) { - addSharedSubscriber(subscribers, user, subscribe); - } else { - addSingleSubscriber(subscribers, user, subscribe); - } - } - - private static void addSingleSubscriber( - LockableArray subscribers, - MqttUser user, - SubscribeTopicFilter subscribe) { - subscribers.add(new SingleSubscriber(user, subscribe)); - } - - private static void addSharedSubscriber( - LockableArray subscribers, - MqttUser user, - SubscribeTopicFilter subscribe) { - - String group = ((SharedTopicFilter) subscribe.getTopicFilter()).getGroup(); - SharedSubscriber sharedSubscriber = (SharedSubscriber) subscribers - .reversedIterations() - .findAny(group, SubscriberUtils::isSharedSubscriberWithGroup); - - if (sharedSubscriber == null) { - sharedSubscriber = new SharedSubscriber(subscribe); - subscribers.add(sharedSubscriber); - } - - var singleSubscriber = new SingleSubscriber(user, subscribe); - sharedSubscriber.addSubscriber(singleSubscriber); - } - - private static boolean removeSubscriber(LockableArray subscribers, TopicFilter topic, MqttUser user) { - return TopicUtils.isShared(topic) - ? removeSharedSubscriber(subscribers, ((SharedTopicFilter) topic).getGroup(), user) - : removeSingleSubscriber(subscribers, user); - } - - private static boolean removeSingleSubscriber(LockableArray subscribers, MqttUser user) { - for (int i = 0, length = subscribers.size(); i < length; i++) { - Subscriber subscriber = subscribers.get(i); - MqttUser mqttClient = SubscriberUtils.singleSubscriberToMqttUser(subscriber); - if (Objects.equals(user, mqttClient)) { - subscribers.remove(i); - return true; - } - } - return false; - } - - private static boolean removeSharedSubscriber( - LockableArray subscribers, - String group, - MqttUser client) { - - boolean removed = false; - SharedSubscriber sharedSubscriber = (SharedSubscriber) subscribers - .reversedIterations() - .findAny(group, SubscriberUtils::isSharedSubscriberWithGroup); - - if (sharedSubscriber != null) { - removed = sharedSubscriber.removeSubscriber(client); - if (removed && sharedSubscriber.size() == 0) { - subscribers.remove(sharedSubscriber); - } - } - - return removed; - } - - private static boolean removeDuplicateWithLowerQoS(MutableArray result, Subscriber candidate) { - if (candidate instanceof SharedSubscriber) { - return true; - } - int found = result.indexOf(candidate); - if (found == -1) { - return true; - } - SingleSubscriber singleSubscriber = (SingleSubscriber) candidate; - QoS qos = singleSubscriber.getQos(); - - SingleSubscriber existed = result.get(found); - QoS existeQos = existed.getQos(); - - if (existeQos.ordinal() < qos.ordinal()) { - result.remove(found); - return true; - } else { - return false; - } - } - - private static void addToResultArray(MutableArray result, Subscriber subscriber) { - if (subscriber instanceof SharedSubscriber) { - result.add(((SharedSubscriber) subscriber).getSubscriber()); - } else { - result.add((SingleSubscriber) subscriber); - } - } - - @Nullable - private static TopicSubscribers collectSubscribers( - RefToRefDictionary subscribersMap, - String segment, - MutableArray result) { - - var topicSubscribers = subscribersMap.get(segment); - if (topicSubscribers == null) { - return null; - } - var subscribers = topicSubscribers.getSubscribers(); - if (subscribers != null) { - long stamp = subscribers.readLock(); - try { - for (Subscriber subscriber : subscribers) { - if (TopicSubscribers.removeDuplicateWithLowerQoS(result, subscriber)) { - TopicSubscribers.addToResultArray(result, subscriber); - } - } - } finally { - subscribers.readUnlock(stamp); - } - } - return topicSubscribers; - } -} diff --git a/model/src/main/java/javasabr/mqtt/model/topic/TopicValidator.java b/model/src/main/java/javasabr/mqtt/model/topic/TopicValidator.java new file mode 100644 index 00000000..3ef98d10 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/topic/TopicValidator.java @@ -0,0 +1,159 @@ +package javasabr.mqtt.model.topic; + +public class TopicValidator { + + private static final String DOUBLE_DELIMITER = TopicName.DELIMITER.repeat(2); + private static final String DOUBLE_SINGLE_WILDCARD = TopicFilter.SINGLE_LEVEL_WILDCARD.repeat(2); + private static final String NULL_CHAR = "\u0000"; + + /** + * Checks that shared topic filters contains all valid parts. + *
{@code
+   *    $share/{ShareName}/{filter}
+   * }
+ */ + public static boolean validateSharedTopicFilter(String rawSharedTopicFilter) { + // $share/{ShareName}/{filter} + String[] parts = rawSharedTopicFilter.split(AbstractTopic.DELIMITER, 3); + if (parts.length != 3) { + return false; + } else if (!SharedTopicFilter.SHARE_KEYWORD.equals(parts[0])) { + return false; + } else if (!baseShareNameValidation(parts[1])) { + return false; + } + String rawTopicFilter = parts[2]; + return baseTopicValidation(rawTopicFilter) + && baseMultiWildcardTopicFilterValidation(rawTopicFilter) + && baseSingleWildcardTopicFilterValidation(rawTopicFilter); + } + + public static boolean validateTopicFilter(String rawTopicFilter) { + return baseTopicValidation(rawTopicFilter) + && baseMultiWildcardTopicFilterValidation(rawTopicFilter) + && baseSingleWildcardTopicFilterValidation(rawTopicFilter); + } + + public static boolean validateTopicName(String rawTopicName) { + return baseTopicValidation(rawTopicName) && baseTopicNameValidation(rawTopicName); + } + + private static boolean baseTopicValidation(String rawTopic) { + return !rawTopic.isEmpty() + && !rawTopic.contains(NULL_CHAR) + && !rawTopic.contains(DOUBLE_DELIMITER) + && !rawTopic.startsWith(TopicFilter.SPECIAL); + } + + /** + * Checks that topic name doesn't any special topic filter chars. + */ + private static boolean baseTopicNameValidation(String rawTopicName) { + return !rawTopicName.contains(TopicFilter.MULTI_LEVEL_WILDCARD) + && !rawTopicName.contains(TopicFilter.SINGLE_LEVEL_WILDCARD); + } + + /** + * Checks that share name doesn't any special chars. + */ + private static boolean baseShareNameValidation(String rawTopicName) { + return !rawTopicName.contains(TopicFilter.MULTI_LEVEL_WILDCARD) + && !rawTopicName.contains(TopicFilter.SINGLE_LEVEL_WILDCARD) + && !rawTopicName.contains(AbstractTopic.DELIMITER); + } + + /** + * Checks that if topic filter contains multi level wildcard that it's in valid way: + *
{@code
+   *    '#'
+   *    '/#'
+   *    '/segment1/#'
+   * }
+ */ + private static boolean baseMultiWildcardTopicFilterValidation(String rawTopicFilter) { + int index = rawTopicFilter.indexOf(TopicFilter.MULTI_LEVEL_WILDCARD_CHAR); + if (index < 0) { + return true; + } + // for the case '#' + int length = rawTopicFilter.length(); + if (length == 1) { + return true; + } + if (index > 0) { + char leftChar = rawTopicFilter.charAt(index - 1); + if (leftChar != AbstractTopic.DELIMITER_CHAR) { + // before '#' always should be delimiter + return false; + } + } + // '/segment1/segment2/#' should be always in the end of topic filter + return index == length - 1; + } + + /** + * Checks that if topic filter contains multi level wildcard that it's in valid way: + *
{@code
+   *    '+'
+   *    '+/+'
+   *    '/+'
+   *    '+/segment1/#'
+   *    'segment1/+/segment3'
+   *    '+/segment2/+/segment4'
+   *    '+/segment2/+/segment4/+'
+   * }
+ */ + private static boolean baseSingleWildcardTopicFilterValidation(String rawTopicFilter) { + if (!rawTopicFilter.contains(TopicFilter.SINGLE_LEVEL_WILDCARD)) { + return true; + } + // we don't allow '++' combination + else if (rawTopicFilter.contains(DOUBLE_SINGLE_WILDCARD)) { + return false; + } + // just '+' + else if (rawTopicFilter.equals(TopicFilter.SINGLE_LEVEL_WILDCARD)) { + return true; + } + + int lastIndex = rawTopicFilter.length() - 1; + for (int i = 0, length = lastIndex + 1; i < length; i++) { + char ch = rawTopicFilter.charAt(i); + if (ch != TopicFilter.SINGLE_LEVEL_WILDCARD_CHAR) { + continue; + } + + // for the first char we should check only the right char + if (i == 0) { + char rightChar = rawTopicFilter.charAt(i + 1); + if (rightChar != AbstractTopic.DELIMITER_CHAR) { + return false; + } + // we already checked the right char + i++; + } + // for the last char we should check only the left char + else if (i == lastIndex) { + char leftChar = rawTopicFilter.charAt(i - 1); + if (leftChar != AbstractTopic.DELIMITER_CHAR) { + return false; + } + } + // check that the left and the right chars are '/' + else { + char leftChar = rawTopicFilter.charAt(i - 1); + if (leftChar != AbstractTopic.DELIMITER_CHAR) { + return false; + } + char rightChar = rawTopicFilter.charAt(i + 1); + if (rightChar != AbstractTopic.DELIMITER_CHAR) { + return false; + } + // we already checked the right char + i++; + } + } + + return true; + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/topic/tree/ConcurrentTopicTree.java b/model/src/main/java/javasabr/mqtt/model/topic/tree/ConcurrentTopicTree.java new file mode 100644 index 00000000..9628485c --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/topic/tree/ConcurrentTopicTree.java @@ -0,0 +1,42 @@ +package javasabr.mqtt.model.topic.tree; + +import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.model.subscribtion.Subscription; +import javasabr.mqtt.model.subscribtion.SubscriptionOwner; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.MutableArray; +import javasabr.rlib.common.ThreadSafe; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; + +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class ConcurrentTopicTree implements ThreadSafe { + + TopicNode rootNode; + + public ConcurrentTopicTree() { + this.rootNode = new TopicNode(); + } + + public void subscribe(SubscriptionOwner owner, Subscription subscription) { + rootNode.subscribe(0, owner, subscription, subscription.topicFilter()); + } + + public boolean unsubscribe(SubscriptionOwner owner, TopicFilter topicFilter) { + return rootNode.unsubscribe(0, owner, topicFilter); + } + + public Array matches(TopicName topicName) { + var resultArray = MutableArray.ofType(SingleSubscriber.class); + matchesTo(resultArray, topicName); + return resultArray; + } + + public MutableArray matchesTo(MutableArray container, TopicName topicName) { + var resultArray = MutableArray.ofType(SingleSubscriber.class); + rootNode.matchesTo(0, topicName, topicName.levelsCount() - 1, container); + return resultArray; + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicNode.java b/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicNode.java new file mode 100644 index 00000000..57ba1192 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicNode.java @@ -0,0 +1,163 @@ +package javasabr.mqtt.model.topic.tree; + +import java.util.function.Supplier; +import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.model.subscriber.Subscriber; +import javasabr.mqtt.model.subscribtion.Subscription; +import javasabr.mqtt.model.subscribtion.SubscriptionOwner; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.rlib.collections.array.ArrayFactory; +import javasabr.rlib.collections.array.LockableArray; +import javasabr.rlib.collections.array.MutableArray; +import javasabr.rlib.collections.dictionary.DictionaryFactory; +import javasabr.rlib.collections.dictionary.LockableRefToRefDictionary; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; + +@Getter(AccessLevel.PACKAGE) +@Accessors(fluent = true, chain = false) +@FieldDefaults(level = AccessLevel.PRIVATE) +class TopicNode extends TopicTreeBase { + + private final static Supplier TOPIC_NODE_FACTORY = TopicNode::new; + + static { + DebugUtils.registerIncludedFields("childNodes", "subscribers"); + } + + @Nullable + volatile LockableRefToRefDictionary childNodes; + @Nullable + volatile LockableArray subscribers; + + public void subscribe(int level, SubscriptionOwner owner, Subscription subscription, TopicFilter topicFilter) { + if (level == topicFilter.levelsCount()) { + addSubscriber(getOrCreateSubscribers(), owner, subscription, topicFilter); + return; + } + TopicNode childNode = getOrCreateChildNode(topicFilter.segment(level)); + childNode.subscribe(level + 1, owner, subscription, topicFilter); + } + + public boolean unsubscribe(int level, SubscriptionOwner owner, TopicFilter topicFilter) { + if (level == topicFilter.levelsCount()) { + return removeSubscriber(subscribers(), owner, topicFilter); + } + TopicNode childNode = getOrCreateChildNode(topicFilter.segment(level)); + return childNode.unsubscribe(level + 1, owner, topicFilter); + } + + protected void matchesTo(int level, TopicName topicName, int lastLevel, MutableArray container) { + exactlyTopicMatch(level, topicName, lastLevel, container); + singleWildcardTopicMatch(level, topicName, lastLevel, container); + multiWildcardTopicMatch(container); + } + + private void exactlyTopicMatch( + int level, + TopicName topicName, + int lastLevel, + MutableArray result) { + String segment = topicName.segment(level); + TopicNode topicNode = childNode(segment); + if (topicNode == null) { + return; + } + if (level == lastLevel) { + appendSubscribersTo(result, topicNode); + } else if (level < lastLevel) { + topicNode.matchesTo(level + 1, topicName, lastLevel, result); + } + } + + private void singleWildcardTopicMatch( + int level, + TopicName topicName, + int lastLevel, + MutableArray result) { + TopicNode topicNode = childNode(TopicFilter.SINGLE_LEVEL_WILDCARD); + if (topicNode == null) { + return; + } + if (level == lastLevel) { + appendSubscribersTo(result, topicNode); + } else if (level < lastLevel) { + topicNode.matchesTo(level + 1, topicName, lastLevel, result); + } + } + + private void multiWildcardTopicMatch(MutableArray result) { + TopicNode topicNode = childNode(TopicFilter.MULTI_LEVEL_WILDCARD); + if (topicNode != null) { + appendSubscribersTo(result, topicNode); + } + } + + private TopicNode getOrCreateChildNode(String segment) { + LockableRefToRefDictionary childNodes = getOrCreateChildNodes(); + long stamp = childNodes.readLock(); + try { + TopicNode topicNode = childNodes.get(segment); + if (topicNode != null) { + return topicNode; + } + } finally { + childNodes.readUnlock(stamp); + } + stamp = childNodes.writeLock(); + try { + //noinspection DataFlowIssue + return childNodes.getOrCompute(segment, TOPIC_NODE_FACTORY); + } finally { + childNodes.writeUnlock(stamp); + } + } + + @Nullable + private TopicNode childNode(String segment) { + LockableRefToRefDictionary childNodes = childNodes(); + if (childNodes == null) { + return null; + } + long stamp = childNodes.readLock(); + try { + return childNodes.get(segment); + } finally { + childNodes.readUnlock(stamp); + } + } + + private LockableRefToRefDictionary getOrCreateChildNodes() { + if (childNodes == null) { + synchronized (this) { + if (childNodes == null) { + childNodes = DictionaryFactory.stampedLockBasedRefToRefDictionary(); + } + } + } + //noinspection ConstantConditions + return childNodes; + } + + private LockableArray getOrCreateSubscribers() { + if (subscribers == null) { + synchronized (this) { + if (subscribers == null) { + subscribers = ArrayFactory.stampedLockBasedArray(Subscriber.class); + } + } + } + //noinspection ConstantConditions + return subscribers; + } + + @Override + public String toString() { + return DebugUtils.toJsonString(this); + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicTreeBase.java b/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicTreeBase.java new file mode 100644 index 00000000..6caba8a9 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/topic/tree/TopicTreeBase.java @@ -0,0 +1,143 @@ +package javasabr.mqtt.model.topic.tree; + +import java.util.Objects; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.subscriber.SharedSubscriber; +import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.model.subscriber.Subscriber; +import javasabr.mqtt.model.subscribtion.Subscription; +import javasabr.mqtt.model.subscribtion.SubscriptionOwner; +import javasabr.mqtt.model.topic.SharedTopicFilter; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.rlib.collections.array.LockableArray; +import javasabr.rlib.collections.array.MutableArray; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; + +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +abstract class TopicTreeBase { + + protected static void addSubscriber( + LockableArray subscribers, + SubscriptionOwner owner, + Subscription subscription, + TopicFilter topicFilter) { + long stamp = subscribers.writeLock(); + try { + if (topicFilter instanceof SharedTopicFilter stf) { + addSharedSubscriber(subscribers, owner, subscription, stf); + } else { + subscribers.add(new SingleSubscriber(owner, subscription)); + } + } finally { + subscribers.writeUnlock(stamp); + } + } + + private static void addSharedSubscriber( + LockableArray subscribers, + SubscriptionOwner owner, + Subscription subscription, + SharedTopicFilter sharedTopicFilter) { + + String group = sharedTopicFilter.shareName(); + SharedSubscriber sharedSubscriber = (SharedSubscriber) subscribers + .iterations() + .findAny(group, TopicTreeBase::isSharedSubscriberWithGroup); + + if (sharedSubscriber == null) { + sharedSubscriber = new SharedSubscriber(sharedTopicFilter); + subscribers.add(sharedSubscriber); + } + + sharedSubscriber.addSubscriber(new SingleSubscriber(owner, subscription)); + } + + protected static void appendSubscribersTo(MutableArray result, TopicNode topicNode) { + LockableArray subscribers = topicNode.subscribers(); + if (subscribers == null) { + return; + } + long stamp = subscribers.readLock(); + try { + for (Subscriber subscriber : subscribers) { + SingleSubscriber singleSubscriber = subscriber.resolveSingle(); + if (removeDuplicateWithLowerQoS(result, singleSubscriber)) { + result.add(singleSubscriber); + } + } + } finally { + subscribers.readUnlock(stamp); + } + } + + protected static boolean removeSubscriber( + @Nullable LockableArray subscribers, + SubscriptionOwner owner, + TopicFilter topicFilter) { + if (subscribers == null) { + return false; + } + long stamp = subscribers.writeLock(); + try { + if (topicFilter instanceof SharedTopicFilter stf) { + return removeSharedSubscriber(subscribers, owner, stf); + } else { + int index = subscribers.indexOf(Subscriber::resolveOwner, owner); + if (index >= 0) { + subscribers.remove(index); + return true; + } + } + } finally { + subscribers.writeUnlock(stamp); + } + return false; + } + + private static boolean removeSharedSubscriber( + LockableArray subscribers, + SubscriptionOwner owner, + SharedTopicFilter sharedTopicFilter) { + String group = sharedTopicFilter.shareName(); + SharedSubscriber sharedSubscriber = (SharedSubscriber) subscribers + .iterations() + .findAny(group, TopicTreeBase::isSharedSubscriberWithGroup); + if (sharedSubscriber != null) { + boolean removed = sharedSubscriber.removeSubscriberWithOwner(owner); + if (sharedSubscriber.isEmpty()) { + // if it was last member + subscribers.remove(sharedSubscriber); + } + return removed; + } + return false; + } + + private static boolean isSharedSubscriberWithGroup(Subscriber subscriber, String group) { + return subscriber instanceof SharedSubscriber shared && Objects.equals(group, shared.group()); + } + + private static boolean removeDuplicateWithLowerQoS( + MutableArray result, SingleSubscriber candidate) { + + int found = result.indexOf(SingleSubscriber::owner, candidate.owner()); + if (found == -1) { + return true; + } + + QoS candidateQos = candidate.qos(); + SingleSubscriber exist = result.get(found); + QoS existeQos = exist.qos(); + + if (existeQos.ordinal() < candidateQos.ordinal()) { + result.remove(found); + return true; + } + + return false; + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/topic/tree/package-info.java b/model/src/main/java/javasabr/mqtt/model/topic/tree/package-info.java new file mode 100644 index 00000000..95d9e9e6 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/topic/tree/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.model.topic.tree; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/model/src/main/java/javasabr/mqtt/model/util/SubscriberUtils.java b/model/src/main/java/javasabr/mqtt/model/util/SubscriberUtils.java deleted file mode 100644 index d1960c49..00000000 --- a/model/src/main/java/javasabr/mqtt/model/util/SubscriberUtils.java +++ /dev/null @@ -1,27 +0,0 @@ -package javasabr.mqtt.model.util; - -import javasabr.mqtt.model.MqttUser; -import javasabr.mqtt.model.subscriber.SharedSubscriber; -import javasabr.mqtt.model.subscriber.SingleSubscriber; -import javasabr.mqtt.model.subscriber.Subscriber; -import org.jspecify.annotations.Nullable; - -public class SubscriberUtils { - - private static boolean isSharedSubscriber(Subscriber subscriber) { - return subscriber instanceof SharedSubscriber; - } - - private static boolean isSingleSubscriber(Subscriber subscriber) { - return subscriber instanceof SingleSubscriber; - } - - public static boolean isSharedSubscriberWithGroup(String group, Subscriber subscriber) { - return isSharedSubscriber(subscriber) && group.equals(((SharedSubscriber) subscriber).getGroup()); - } - - @Nullable - public static MqttUser singleSubscriberToMqttUser(Subscriber subscriber) { - return isSingleSubscriber(subscriber) ? ((SingleSubscriber) subscriber).getUser() : null; - } -} diff --git a/model/src/main/java/javasabr/mqtt/model/util/TopicUtils.java b/model/src/main/java/javasabr/mqtt/model/util/TopicUtils.java deleted file mode 100644 index ae052030..00000000 --- a/model/src/main/java/javasabr/mqtt/model/util/TopicUtils.java +++ /dev/null @@ -1,112 +0,0 @@ -package javasabr.mqtt.model.util; - -import javasabr.mqtt.model.topic.SharedTopicFilter; -import javasabr.mqtt.model.topic.TopicFilter; -import javasabr.mqtt.model.topic.TopicName; -import javasabr.rlib.common.util.StringUtils; - -public class TopicUtils { - - private static final TopicFilter INVALID_TOPIC_FILTER = new TopicFilter(); - private static final TopicName INVALID_TOPIC_NAME = new TopicName(); - public static final TopicName EMPTY_TOPIC_NAME = new TopicName(); - - private static final String SHARE_KEYWORD = "$share"; - private static final String DELIMITER = "/"; - public static final String MULTI_LEVEL_WILDCARD = "#"; - public static final String SINGLE_LEVEL_WILDCARD = "+"; - - public static boolean isInvalid(TopicFilter topicFilter) { - return topicFilter == INVALID_TOPIC_FILTER; - } - - public static boolean isInvalid(TopicName topicName) { - return topicName == INVALID_TOPIC_NAME; - } - - public static boolean isShared(TopicFilter topicFilter) { - return topicFilter instanceof SharedTopicFilter; - } - - public static boolean hasWildcard(TopicFilter topicFilter) { - var topic = topicFilter.getRawTopic(); - return topic.contains(SINGLE_LEVEL_WILDCARD) || topic.contains(MULTI_LEVEL_WILDCARD); - } - - public static TopicName buildTopicName(String topicName) { - if (isInvalidTopicName(topicName)) { - return INVALID_TOPIC_NAME; - } else { - return new TopicName(topicName); - } - } - - public static TopicFilter buildTopicFilter(String topicFilter) { - if (isInvalidTopicFilter(topicFilter)) { - return INVALID_TOPIC_FILTER; - } else if (isShared(topicFilter)) { - return buildSharedTopicFilter(topicFilter); - } else { - return new TopicFilter(topicFilter); - } - } - - public static String[] splitTopic(String topic) { - int segmentCount = countOccurrencesOf(topic, DELIMITER) + 1; - var segments = new String[segmentCount]; - int i = 0, pos = 0, end; - while ((end = topic.indexOf(DELIMITER, pos)) >= 0) { - segments[i++] = topic.substring(pos, end); - pos = end + 1; - } - segments[i] = topic.substring(pos); - return segments; - } - - private static TopicFilter buildSharedTopicFilter(String topicFilter) { - int firstSlash = topicFilter.indexOf(DELIMITER) + 1; - int secondSlash = topicFilter.indexOf(DELIMITER, firstSlash); - String group = topicFilter.substring(firstSlash, secondSlash); - if (group.isEmpty()) { - return INVALID_TOPIC_FILTER; - } - var realTopicFilter = topicFilter.substring(secondSlash + 1); - return new SharedTopicFilter(realTopicFilter, group); - } - - private static boolean isInvalidTopicName(String topic) { - return invalid(topic) || topic.contains(MULTI_LEVEL_WILDCARD) || topic.contains(SINGLE_LEVEL_WILDCARD); - } - - private static boolean isInvalidTopicFilter(String topic) { - if (TopicUtils.invalid(topic) || topic.contains("++")) { - return true; - } - int multiPos = topic.indexOf(MULTI_LEVEL_WILDCARD); - return multiPos != -1 && multiPos != topic.length() - 1; - } - - private static boolean invalid(String topic) { - return topic.isEmpty() || topic.contains("//") || topic.startsWith("/") || topic.endsWith("/"); - } - - private static boolean isShared(String topicFilter) { - return topicFilter.startsWith(SHARE_KEYWORD); - } - - public static int countOccurrencesOf(String str, String sub) { - if (StringUtils.isEmpty(str)) { - return 0; - } - - int count = 0; - int pos = 0; - int idx; - while ((idx = str.indexOf(sub, pos)) != -1) { - ++count; - pos = idx + sub.length(); - } - return count; - } - -} diff --git a/model/src/test/groovy/javasabr/mqtt/model/ActionResultTest.groovy b/model/src/test/groovy/javasabr/mqtt/model/ActionResultTest.groovy deleted file mode 100644 index 38f0cac8..00000000 --- a/model/src/test/groovy/javasabr/mqtt/model/ActionResultTest.groovy +++ /dev/null @@ -1,30 +0,0 @@ -package javasabr.mqtt.model - -import javasabr.mqtt.test.support.UnitSpecification -import spock.lang.Unroll - -import static javasabr.mqtt.model.ActionResult.* - -class ActionResultTest extends UnitSpecification { - - @Unroll - def "#first and #second == #result"( - ActionResult first, - ActionResult second, - ActionResult result) { - expect: - first.and(second) == result - where: - first | second | result - SUCCESS | SUCCESS | SUCCESS - SUCCESS | FAILED | FAILED - FAILED | SUCCESS | FAILED - FAILED | FAILED | FAILED - EMPTY | EMPTY | EMPTY - EMPTY | FAILED | FAILED - FAILED | EMPTY | FAILED - SUCCESS | EMPTY | SUCCESS - EMPTY | SUCCESS | SUCCESS - } - -} diff --git a/model/src/test/groovy/javasabr/mqtt/model/TopicSubscriberTest.groovy b/model/src/test/groovy/javasabr/mqtt/model/TopicSubscriberTest.groovy deleted file mode 100644 index ccb7e160..00000000 --- a/model/src/test/groovy/javasabr/mqtt/model/TopicSubscriberTest.groovy +++ /dev/null @@ -1,81 +0,0 @@ -package javasabr.mqtt.model - -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter -import javasabr.mqtt.model.topic.TopicFilter -import javasabr.mqtt.model.topic.TopicName -import javasabr.mqtt.model.topic.TopicSubscribers -import javasabr.mqtt.test.support.UnitSpecification -import spock.lang.Shared -import spock.lang.Unroll - -import static javasabr.mqtt.model.QoS.* -import static javasabr.mqtt.model.util.TopicUtils.buildTopicFilter -import static javasabr.mqtt.model.util.TopicUtils.buildTopicName - -class TopicSubscriberTest extends UnitSpecification { - - @Shared - MqttUser defaultUser = Mock(MqttUser) - @Shared - MqttUser newUser1 = Mock(MqttUser) - @Shared - MqttUser newUser2 = Mock(MqttUser) - @Shared - MqttUser newUser3 = Mock(MqttUser) - - @Unroll - def "should choose #matchedQos from #subscriberQos"( - TopicFilter[] topicFilters, - TopicName topicNames, - QoS[] subscriberQos, - QoS[] matchedQos, - MqttUser[] users) { - given: - SubscribeTopicFilter[] subscribeFilters = new SubscribeTopicFilter[users.length] - users.eachWithIndex { MqttUser entry, int i -> - subscribeFilters[i] = new SubscribeTopicFilter(topicFilters[i], subscriberQos[i]) - } - def topicSubscriber = new TopicSubscribers() - when: - topicSubscriber.addSubscriber(users[0], subscribeFilters[0]) - topicSubscriber.addSubscriber(users[1], subscribeFilters[1]) - topicSubscriber.addSubscriber(users[2], subscribeFilters[2]) - then: - def subscribers = topicSubscriber.matches(topicNames) - subscribers.size() == matchedQos.size() - for (int i = 0; i < subscribers.size(); i++) { - subscribers[i].qos == matchedQos[i] - } - where: - topicFilters << [ - [buildTopicFilter("topic/second/in"), buildTopicFilter("topic/+/in"), buildTopicFilter("topic/#")], - [buildTopicFilter("topic/+/in"), buildTopicFilter("topic/first/in"), buildTopicFilter("topic/out")], - [buildTopicFilter("topic/second/in"), buildTopicFilter("topic/first/in"), buildTopicFilter("topic/out")], - [buildTopicFilter("topic/second/in"), buildTopicFilter("topic/+/in"), buildTopicFilter("topic/#")] - ] - topicNames << [ - buildTopicName("topic/second/in"), - buildTopicName("topic/first/in"), - buildTopicName("topic/second/in"), - buildTopicName("topic/second/in") - ] - subscriberQos << [ - [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE], - [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE], - [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE], - [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE] - ] - matchedQos << [ - [EXACTLY_ONCE], - [AT_MOST_ONCE], - [AT_LEAST_ONCE], - [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE] - ] - users << [ - [defaultUser, defaultUser, defaultUser], - [defaultUser, defaultUser, defaultUser], - [defaultUser, defaultUser, defaultUser], - [newUser1, newUser2, newUser3] - ] - } -} diff --git a/model/src/test/groovy/javasabr/mqtt/model/TopicTest.groovy b/model/src/test/groovy/javasabr/mqtt/model/TopicTest.groovy deleted file mode 100644 index f6a285df..00000000 --- a/model/src/test/groovy/javasabr/mqtt/model/TopicTest.groovy +++ /dev/null @@ -1,105 +0,0 @@ -package javasabr.mqtt.model - - -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter -import javasabr.mqtt.model.topic.TopicSubscribers -import spock.lang.Specification -import spock.lang.Unroll - -import static javasabr.mqtt.model.util.TopicUtils.* - -class TopicTest extends Specification { - - @Unroll - def "should create topic name: [#stringTopicName]"() { - when: - def topicName = buildTopicName(stringTopicName) - then: - topicName.segments.size() == levelsCount - topicName.rawTopic == stringTopicName - topicName.length == stringTopicName.length() - where: - stringTopicName | levelsCount - "topic/second/in" | 3 - "topic/second" | 2 - } - - @Unroll - def "should fail create topic name: [#stringTopicName]"() { - expect: - isInvalid(buildTopicName(stringTopicName)) - where: - stringTopicName << [ - "", - "topic/+", - "topic/#" - ] - } - - @Unroll - def "should create topic filter: [#stringTopicFilter]"() { - when: - def topicFilter = buildTopicFilter(stringTopicFilter) - then: - topicFilter.segments.size() == levelsCount - topicFilter.rawTopic == stringTopicFilter - topicFilter.length == stringTopicFilter.length() - where: - stringTopicFilter | levelsCount - "topic/in" | 2 - "topic/+" | 2 - "topic/#" | 2 - "topic/+/in" | 3 - } - - @Unroll - def "should fail create topic filter: [#stringTopicFilter]"() { - expect: - isInvalid(buildTopicFilter(stringTopicFilter)) - where: - stringTopicFilter << [ - "", - "topic/in/", - "/topic/in", - "topic//in", - "topic/++/in", - "topic/#/in", - "topic/##" - ] - } - - @Unroll - def "should match topic filter: [#topicFilter] with topic name: [#topicName]"() { - expect: - def builtTopicName = buildTopicName(topicName) - def builtTopicFilter = buildTopicFilter(topicFilter) - def subscribers = new TopicSubscribers() - subscribers.addSubscriber(Mock(MqttUser), new SubscribeTopicFilter(builtTopicFilter, QoS.AT_LEAST_ONCE)) - subscribers.matches(builtTopicName) - where: - topicFilter | topicName - "topic/in" | "topic/in" - "topic/+" | "topic/in" - "topic/#" | "topic/in" - "topic/+/in" | "topic/m/in" - } - - @Unroll - def "should not match topic filter: [#topicFilter] with topic name: [#topicName]"() { - expect: - def builtTopicName = buildTopicName(topicName) - def builtTopicFilter = buildTopicFilter(topicFilter) - def subscribers = new TopicSubscribers() - subscribers.addSubscriber(Mock(MqttUser), new SubscribeTopicFilter(builtTopicFilter, QoS.AT_LEAST_ONCE)) - !subscribers.matches(builtTopicName) - where: - topicFilter | topicName - "topic/in" | "topic/m/in" - // "topic/in" | "topic/in/m" - // "topic/+" | "topic/m/in" - // "topic/+" | "topic/in/m" - "topic/#" | "topic" - "topic/+/in" | "topic/m/n" - "topic/+/in" | "topic/in" - } -} diff --git a/model/src/test/groovy/javasabr/mqtt/model/topic/TopicCreationTest.groovy b/model/src/test/groovy/javasabr/mqtt/model/topic/TopicCreationTest.groovy new file mode 100644 index 00000000..9455305f --- /dev/null +++ b/model/src/test/groovy/javasabr/mqtt/model/topic/TopicCreationTest.groovy @@ -0,0 +1,82 @@ +package javasabr.mqtt.model.topic + +import javasabr.mqtt.test.support.UnitSpecification +import spock.lang.Unroll + +class TopicCreationTest extends UnitSpecification { + + @Unroll + def "should create topic name:[#topicName] with levels [#levels] and segments #segments"() { + given: + def created = TopicName.valueOf(topicName) + expect: + created.levelsCount() == levels + List.of(created.segments()) == segments + where: + topicName | levels | segments + "t" | 1 | ["t"] + "topic" | 1 | ["topic"] + "/" | 2 | ["", ""] + "/topic" | 2 | ["", "topic"] + "topic/" | 2 | ["topic", ""] + "/topic/name/" | 4 | ["", "topic", "name", ""] + "topic/na me" | 2 | ["topic", "na me"] + } + + @Unroll + def "should create topic filter:[#topicFilter] with levels [#levels] and segments #segments"() { + given: + def created = TopicFilter.valueOf(topicFilter) + expect: + created.levelsCount() == levels + List.of(created.segments()) == segments + where: + topicFilter | levels | segments + "t" | 1 | ["t"] + "topic" | 1 | ["topic"] + "/" | 2 | ["", ""] + "/topic" | 2 | ["", "topic"] + "topic/" | 2 | ["topic", ""] + "/topic/name/" | 4 | ["", "topic", "name", ""] + "topic/na me" | 2 | ["topic", "na me"] + "#" | 1 | ["#"] + "/#" | 2 | ["", "#"] + "/topic/#" | 3 | ["", "topic", "#"] + "+" | 1 | ["+"] + "/+" | 2 | ["", "+"] + "+/+" | 2 | ["+", "+"] + "/topic/+" | 3 | ["", "topic", "+"] + "/topic/+/filter" | 4 | ["", "topic", "+", "filter"] + "+/topic/+/filter" | 4 | ["+", "topic", "+", "filter"] + "topic/+/filter/+/test" | 5 | ["topic", "+", "filter", "+", "test"] + } + + @Unroll + def "should create shared topic filter:[#sharedTopicFilter] with levels [#levels], [#shareName] and segments #segments"() { + given: + def created = SharedTopicFilter.valueOf(sharedTopicFilter) + expect: + created.levelsCount() == levels + created.shareName() == shareName + List.of(created.segments()) == segments + where: + sharedTopicFilter | levels | shareName | segments + '$share/name1/t' | 1 | "name1" | ["t"] + '$share/name2/topic' | 1 | "name2" | ["topic"] + '$share/name3/' | 1 | "name3" | [""] + '$share/name4/topic' | 1 | "name4" | ["topic"] + '$share/name5/topic/' | 2 | "name5" | ["topic", ""] + '$share/name6/topic/name/' | 3 | "name6" | ["topic", "name", ""] + '$share/name7/topic/na me' | 2 | "name7" | ["topic", "na me"] + '$share/name8/#' | 1 | "name8" | ["#"] + '$share/name9/#' | 1 | "name9" | ["#"] + '$share/name10/topic/#' | 2 | "name10" | ["topic", "#"] + '$share/name11/+' | 1 | "name11" | ["+"] + '$share/name12/+' | 1 | "name12" | ["+"] + '$share/name13/+/+' | 2 | "name13" | ["+", "+"] + '$share/name14/topic/+' | 2 | "name14" | ["topic", "+"] + '$share/name15/topic/+/filter' | 3 | "name15" | ["topic", "+", "filter"] + '$share/name16/+/topic/+/filter' | 4 | "name16" | ["+", "topic", "+", "filter"] + '$share/name17/topic/+/filter/+/test' | 5 | "name17" | ["topic", "+", "filter", "+", "test"] + } +} diff --git a/model/src/test/groovy/javasabr/mqtt/model/topic/TopicValidatorTest.groovy b/model/src/test/groovy/javasabr/mqtt/model/topic/TopicValidatorTest.groovy new file mode 100644 index 00000000..1546d7aa --- /dev/null +++ b/model/src/test/groovy/javasabr/mqtt/model/topic/TopicValidatorTest.groovy @@ -0,0 +1,109 @@ +package javasabr.mqtt.model.topic + +import javasabr.mqtt.test.support.UnitSpecification +import spock.lang.Unroll + +class TopicValidatorTest extends UnitSpecification { + + private static final String NULL_CHAR = "\u0000" + + @Unroll + def "should validate topic name:[#topicName]->[#valid]"() { + expect: + TopicValidator.validateTopicName(topicName) == valid + where: + topicName | valid + "t" | true + "/" | true + "topic" | true + "topic/name" | true + "topic/name/" | true + "/topic/name/" | true + "/to pic/nam e/" | true + "/topic" | true + "" | false + "//" | false + "+" | false + "+/+" | false + "#" | false + "/to$NULL_CHAR/ne/" | false + '$sys/topic' | false + } + + @Unroll + def 'should validate topic filter:[#topicFilter]->[#valid]'() { + expect: + TopicValidator.validateTopicFilter(topicFilter) == valid + where: + topicFilter | valid + "t" | true + "/" | true + "topic" | true + "topic/filter" | true + "topic/filter/" | true + "/topic/filter/" | true + "/topic" | true + "+" | true + "+/+" | true + "/+/" | true + "+/filter" | true + "+/filter/+" | true + "+/ fil ter/+" | true + "+/filter/+/segment" | true + "#" | true + "+/filter/#" | true + "/topic/#" | true + "" | false + "//" | false + "/to$NULL_CHAR/ne/" | false + "#/filter" | false + "++" | false + "##" | false + "topic/filter+" | false + "+/+topic/filter" | false + "+/#/filter" | false + "#/filter" | false + '$sys/topic/filter/' | false + '$sys/#' | false + '$sys/topic/+' | false + } + + @Unroll + def 'should validate shared topic filter:[#sharedTopicFilter]->[#valid]'() { + expect: + TopicValidator.validateSharedTopicFilter(sharedTopicFilter) == valid + where: + sharedTopicFilter | valid + '$share/group1/t' | true + '$share/group1/topic' | true + '$share/group1/topic/filter' | true + '$share/group1/topic/filter/' | true + '$share/group1/topic/filter/' | true + '$share/group1/topic' | true + '$share/group1/+' | true + '$share/group1/+/+' | true + '$share/group1/+/' | true + '$share/group1/+/filter' | true + '$share/group1/+/filter/+' | true + '$share/group1/+/fi lt er/+' | true + '$share/group1/+/filter/+/segment' | true + '$share/group1/#' | true + '$share/group1/+/filter/#' | true + '$share/group1/topic/#' | true + '$share/group1//' | true + '$share/group1' | false + '$share/group1/#/filter' | false + '$share/group1/++' | false + '$share/group1/##' | false + '$share/group1/topic/filter+' | false + '$share/group1/+/+topic/filter' | false + '$share/group1/+/#/filter' | false + '$share/group1/#/filter' | false + '$share/group1/' | false + '$share' | false + '$share//' | false + '$share/group1/$sys/topic/filter/' | false + '$share/group1/$sys/#' | false + '$share/group1/$sys/topic/+' | false + } +} diff --git a/model/src/test/groovy/javasabr/mqtt/model/topic/tree/TopicTreeTest.groovy b/model/src/test/groovy/javasabr/mqtt/model/topic/tree/TopicTreeTest.groovy new file mode 100644 index 00000000..0176e230 --- /dev/null +++ b/model/src/test/groovy/javasabr/mqtt/model/topic/tree/TopicTreeTest.groovy @@ -0,0 +1,575 @@ +package javasabr.mqtt.model.topic.tree + +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.SubscribeRetainHandling +import javasabr.mqtt.model.subscriber.SingleSubscriber +import javasabr.mqtt.model.subscribtion.Subscription +import javasabr.mqtt.model.subscribtion.SubscriptionOwner +import javasabr.mqtt.model.subscription.TestSubscriptionOwner +import javasabr.mqtt.model.topic.SharedTopicFilter +import javasabr.mqtt.model.topic.TopicFilter +import javasabr.mqtt.model.topic.TopicName +import javasabr.mqtt.test.support.UnitSpecification + +class TopicTreeTest extends UnitSpecification { + + def "should match simple topic correctly"( + List subscriptions, + List owners, + String topicName, + List expectedOwners) { + given: + ConcurrentTopicTree topicTree = new ConcurrentTopicTree() + subscriptions.eachWithIndex { Subscription subscription, int i -> + topicTree.subscribe(owners.get(i), subscription) + } + when: + def found = topicTree.matches(TopicName.valueOf(topicName)) + .collect { it.resolveOwner() } + then: + found ==~ expectedOwners + where: + topicName << [ + "/topic/segment1", + "/topic/segment2", + "/topic/segment3" + ] + subscriptions << [ + [ + makeSubscription("/topic/segment1"), + makeSubscription("/topic/segment2"), + makeSubscription("/topic/segment1/segment2"), + makeSubscription("/topic/"), + makeSubscription("/topic") + ], + [ + makeSubscription("/topic/segment1"), + makeSubscription("/topic/segment2"), + makeSubscription("/topic/segment1/segment2"), + makeSubscription("/topic/"), + makeSubscription("/topic/segment2"), + makeSubscription("/"), + makeSubscription("/topic/segment2/segment1") + ], + [ + makeSubscription("/topic/segment1"), + makeSubscription("/topic/segment2"), + makeSubscription("/topic/segment3"), + makeSubscription("/topic/segment3"), + makeSubscription("/topic/segment3"), + makeSubscription("/topic/segment3") + ] + ] + owners << [ + [ + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id4"), + makeOwner("id5") + ], + [ + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id4"), + makeOwner("id5"), + makeOwner("id6"), + makeOwner("id7") + ], + [ + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id3"), + makeOwner("id3"), + makeOwner("id4") + ] + ] + expectedOwners << [ + [ + makeOwner("id1") + ], + [ + makeOwner("id2"), + makeOwner("id5") + ], + [ + makeOwner("id3"), + makeOwner("id4") + ] + ] + } + + def "should match single wildcard topic correctly"( + List subscriptions, + List owners, + String topicName, + List expectedOwners) { + given: + ConcurrentTopicTree topicTree = new ConcurrentTopicTree() + subscriptions.eachWithIndex { Subscription subscription, int i -> + topicTree.subscribe(owners.get(i), subscription) + } + when: + def found = topicTree.matches(TopicName.valueOf(topicName)) + .collect { it.resolveOwner() } + then: + found ==~ expectedOwners + where: + topicName << [ + "/topic/segment1", + "/topic/segment2", + "/topic/segment3" + ] + subscriptions << [ + [ + makeSubscription("/topic/segment1"), + makeSubscription("/topic/+"), + makeSubscription("/+/segment1"), + makeSubscription("/+/+"), + makeSubscription("/topic/segment2"), + makeSubscription("/topic2/segment1"), + makeSubscription("/+/segment2"), + makeSubscription("/topic2/+") + ], + [ + makeSubscription("/topic/segment1"), + makeSubscription("/topic/+"), + makeSubscription("/+/segment1"), + makeSubscription("/+/+"), + makeSubscription("/topic/segment2"), + makeSubscription("/topic2/segment1"), + makeSubscription("/+/segment2"), + makeSubscription("/topic2/+") + ], + [ + makeSubscription("/topic/segment1"), + makeSubscription("/topic/+"), + makeSubscription("/+/segment1"), + makeSubscription("/+/+"), + makeSubscription("/topic/segment2"), + makeSubscription("/topic2/segment1"), + makeSubscription("/+/segment2"), + makeSubscription("/topic2/+") + ] + ] + owners << [ + [ + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id4"), + makeOwner("id5"), + makeOwner("id6"), + makeOwner("id7"), + makeOwner("id8") + ], + [ + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id4"), + makeOwner("id5"), + makeOwner("id6"), + makeOwner("id7"), + makeOwner("id8") + ], + [ + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id4"), + makeOwner("id5"), + makeOwner("id6"), + makeOwner("id7"), + makeOwner("id8") + ] + ] + expectedOwners << [ + [ + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id4") + ], + [ + makeOwner("id2"), + makeOwner("id4"), + makeOwner("id5"), + makeOwner("id7") + ], + [ + makeOwner("id2"), + makeOwner("id4") + ] + ] + } + + def "should match multi wildcard topic correctly"( + List subscriptions, + List owners, + String topicName, + List expectedOwners) { + given: + ConcurrentTopicTree topicTree = new ConcurrentTopicTree() + subscriptions.eachWithIndex { Subscription subscription, int i -> + topicTree.subscribe(owners.get(i), subscription) + } + when: + def found = topicTree.matches(TopicName.valueOf(topicName)) + .collect { it.resolveOwner() } + then: + found ==~ expectedOwners + where: + topicName << [ + "/topic/segment1/segment2", + "/topic/segment3/segment4", + "/topic/segment2" + ] + subscriptions << [ + [ + makeSubscription("/topic/segment1/segment2"), + makeSubscription("/topic/segment1/#"), + makeSubscription("/topic/#"), + makeSubscription("/#"), + makeSubscription("#"), + makeSubscription("/topic/segment2/segment3"), + makeSubscription("/topic/segment2/#"), + makeSubscription("/topic/segment3/segment4"), + makeSubscription("/topic/segment3/#") + ], + [ + makeSubscription("/topic/segment1/segment2"), + makeSubscription("/topic/segment1/#"), + makeSubscription("/topic/#"), + makeSubscription("/#"), + makeSubscription("#"), + makeSubscription("/topic/segment2/segment3"), + makeSubscription("/topic/segment2/#"), + makeSubscription("/topic/segment3/segment4"), + makeSubscription("/topic/segment3/#") + ], + [ + makeSubscription("/topic/segment1/segment2"), + makeSubscription("/topic/segment1/#"), + makeSubscription("/topic/#"), + makeSubscription("/#"), + makeSubscription("#"), + makeSubscription("/topic/segment2/segment3"), + makeSubscription("/topic/segment2/#"), + makeSubscription("/topic/segment3/segment4"), + makeSubscription("/topic/segment3/#") + ] + ] + owners << [ + [ + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id4"), + makeOwner("id5"), + makeOwner("id6"), + makeOwner("id7"), + makeOwner("id8"), + makeOwner("id9") + ], + [ + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id4"), + makeOwner("id5"), + makeOwner("id6"), + makeOwner("id7"), + makeOwner("id8"), + makeOwner("id9") + ], + [ + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id4"), + makeOwner("id5"), + makeOwner("id6"), + makeOwner("id7"), + makeOwner("id8"), + makeOwner("id9") + ] + ] + expectedOwners << [ + [ + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id4"), + makeOwner("id5") + ], + [ + makeOwner("id8"), + makeOwner("id9"), + makeOwner("id3"), + makeOwner("id4"), + makeOwner("id5") + ], + [ + makeOwner("id3"), + makeOwner("id4"), + makeOwner("id5") + ] + ] + } + + def "should choose strongest QoS when the same subscriber has several matches"( + List subscriptions, + List owners, + String topicName, + List expectedSubscribers) { + given: + ConcurrentTopicTree topicTree = new ConcurrentTopicTree() + subscriptions.eachWithIndex { Subscription subscription, int i -> + topicTree.subscribe(owners.get(i), subscription) + } + when: + def found = topicTree.matches(TopicName.valueOf(topicName)) + then: + found ==~ expectedSubscribers + where: + topicName << [ + "/topic/segment1/segment2", + "/topic/segment3", + "/topic/segment2/" + ] + subscriptions << [ + [ + makeSubscription("/topic/segment1/segment2", 2), + makeSubscription("/topic/segment1/#", 1), + makeSubscription("/topic/#", 0), + makeSubscription("/topic/segment1/segment3", 2), + makeSubscription("/topic/segment1/#", 1), + makeSubscription("/topic/#", 0), + makeSubscription("/topic/segment2/segment3", 2), + makeSubscription("/topic/segment2/#", 1), + makeSubscription("/topic/#", 0) + ], + [ + makeSubscription("/topic/segment1/segment2", 2), + makeSubscription("/topic/segment1/#", 1), + makeSubscription("/topic/#", 0), + makeSubscription("/topic/segment1/segment3", 2), + makeSubscription("/topic/segment1/#", 1), + makeSubscription("/topic/#", 0), + makeSubscription("/topic/segment2/segment3", 2), + makeSubscription("/topic/segment2/#", 1), + makeSubscription("/topic/#", 0) + ], + [ + makeSubscription("/topic/segment1/segment2", 2), + makeSubscription("/topic/segment1/#", 1), + makeSubscription("/topic/#", 0), + makeSubscription("/topic/segment1/segment3", 2), + makeSubscription("/topic/segment1/#", 1), + makeSubscription("/topic/#", 0), + makeSubscription("/topic/segment2/segment3", 2), + makeSubscription("/topic/segment2/#", 1), + makeSubscription("/topic/#", 0) + ] + ] + owners << [ + [ + makeOwner("id1"), + makeOwner("id1"), + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id2"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id3"), + makeOwner("id3") + ], + [ + makeOwner("id1"), + makeOwner("id1"), + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id2"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id3"), + makeOwner("id3") + ], + [ + makeOwner("id1"), + makeOwner("id1"), + makeOwner("id1"), + makeOwner("id2"), + makeOwner("id2"), + makeOwner("id2"), + makeOwner("id3"), + makeOwner("id3"), + makeOwner("id3") + ] + ] + expectedSubscribers << [ + [ + new SingleSubscriber(makeOwner("id1"), makeSubscription("/topic/segment1/segment2", 2)), + new SingleSubscriber(makeOwner("id2"), makeSubscription("/topic/segment1/#", 1)), + new SingleSubscriber(makeOwner("id3"), makeSubscription("/topic/#", 0)), + ], + [ + new SingleSubscriber(makeOwner("id1"), makeSubscription("/topic/#", 0)), + new SingleSubscriber(makeOwner("id2"), makeSubscription("/topic/#", 0)), + new SingleSubscriber(makeOwner("id3"), makeSubscription("/topic/#", 0)), + ], + [ + new SingleSubscriber(makeOwner("id1"), makeSubscription("/topic/#", 0)), + new SingleSubscriber(makeOwner("id2"), makeSubscription("/topic/#", 0)), + new SingleSubscriber(makeOwner("id3"), makeSubscription("/topic/segment2/#", 1)), + ] + ] + } + + def "should provide different owners when math shared topic"() { + given: + def group1 = ["id1", "id2", "id3", "id4", "id5"] + def group2 = ["id6", "id7", "id8", "id9", "id10"] + ConcurrentTopicTree topicTree = new ConcurrentTopicTree() + topicTree.subscribe(makeOwner("id1"), makeSharedSubscription('$share/group1/topic/name1')) + topicTree.subscribe(makeOwner("id2"), makeSharedSubscription('$share/group1/topic/name1')) + topicTree.subscribe(makeOwner("id3"), makeSharedSubscription('$share/group1/topic/name1')) + topicTree.subscribe(makeOwner("id4"), makeSharedSubscription('$share/group1/topic/name1')) + topicTree.subscribe(makeOwner("id5"), makeSharedSubscription('$share/group1/topic/name1')) + topicTree.subscribe(makeOwner("id6"), makeSharedSubscription('$share/group2/topic/name1')) + topicTree.subscribe(makeOwner("id7"), makeSharedSubscription('$share/group2/topic/name1')) + topicTree.subscribe(makeOwner("id8"), makeSharedSubscription('$share/group2/topic/name1')) + topicTree.subscribe(makeOwner("id9"), makeSharedSubscription('$share/group2/topic/name1')) + topicTree.subscribe(makeOwner("id10"), makeSharedSubscription('$share/group2/topic/name1')) + when: + def matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .collect { it.owner().toString() } + then: + matched.size() == 2 + when: + def matched2 = topicTree + .matches(TopicName.valueOf("topic/name1")) + .collect { it.owner().toString() } + then: + matched2.size() == 2 + matched2 != matched + then: "should contains by one owner from different groups" + (group1.contains(matched[0]) && group2.contains(matched[1])) || + (group1.contains(matched[1]) && group2.contains(matched[0])) + (group1.contains(matched2[0]) && group2.contains(matched2[1])) || + (group1.contains(matched2[1]) && group2.contains(matched2[0])) + } + + def "should subscribe and unsubscribe simple topic correctly correctly"() { + given: + ConcurrentTopicTree topicTree = new ConcurrentTopicTree() + topicTree.subscribe(makeOwner("id1"), makeSubscription('topic/name1')) + topicTree.subscribe(makeOwner("id2"), makeSubscription('topic/name1')) + topicTree.subscribe(makeOwner("id3"), makeSubscription('topic/name1')) + when: + def matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .collect { it.owner().toString() } + .toSet() + then: + matched.size() == 3 + when: + def id2WasUnsubscribed = topicTree.unsubscribe(makeOwner("id2"), TopicFilter.valueOf('topic/name1')) + def id3WasUnsubscribed = topicTree.unsubscribe(makeOwner("id3"), TopicFilter.valueOf('topic/name1')) + matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .collect { it.owner().toString() } + .toSet() + then: + matched.size() == 1 + id2WasUnsubscribed + id3WasUnsubscribed + when: + def id1WasUnsubscribed = topicTree.unsubscribe(makeOwner("id1"), TopicFilter.valueOf('topic/name1')) + id3WasUnsubscribed = topicTree.unsubscribe(makeOwner("id3"), TopicFilter.valueOf('topic/name1')) + matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .collect { it.owner().toString() } + .toSet() + then: + matched.size() == 0 + id1WasUnsubscribed + !id3WasUnsubscribed + } + + def "should subscribe and unsubscribe shared topic correctly correctly"() { + given: + ConcurrentTopicTree topicTree = new ConcurrentTopicTree() + topicTree.subscribe(makeOwner("id1"), makeSharedSubscription('$share/group1/topic/name1')) + topicTree.subscribe(makeOwner("id2"), makeSharedSubscription('$share/group1/topic/name1')) + topicTree.subscribe(makeOwner("id3"), makeSharedSubscription('$share/group1/topic/name1')) + when: + def matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .collect { it.owner().toString() } + .toSet() + then: + matched.size() == 1 + when: + def id2WasUnsubscribed = topicTree.unsubscribe(makeOwner("id2"), SharedTopicFilter.valueOf('$share/group1/topic/name1')) + def id3WasUnsubscribed = topicTree.unsubscribe(makeOwner("id3"), SharedTopicFilter.valueOf('$share/group1/topic/name1')) + matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .collect { it.owner().toString() } + .toSet() + then: + matched.size() == 1 + id2WasUnsubscribed + id3WasUnsubscribed + when: + def id1WasUnsubscribed = topicTree.unsubscribe(makeOwner("id1"), SharedTopicFilter.valueOf('$share/group1/topic/name1')) + id3WasUnsubscribed = topicTree.unsubscribe(makeOwner("id3"), SharedTopicFilter.valueOf('$share/group1/topic/name1')) + matched = topicTree + .matches(TopicName.valueOf("topic/name1")) + .collect { it.owner().toString() } + .toSet() + then: + matched.size() == 0 + id1WasUnsubscribed + !id3WasUnsubscribed + } + + static def makeOwner(String id) { + return new TestSubscriptionOwner(id) + } + + static def makeSubscription(String topicFilter) { + return new Subscription( + TopicFilter.valueOf(topicFilter), + MqttProperties.SUBSCRIPTION_ID_UNDEFINED, + QoS.AT_LEAST_ONCE, + SubscribeRetainHandling.SEND, + true, + true) + } + + static def makeSharedSubscription(String topicFilter) { + return new Subscription( + SharedTopicFilter.valueOf(topicFilter), + MqttProperties.SUBSCRIPTION_ID_UNDEFINED, + QoS.AT_LEAST_ONCE, + SubscribeRetainHandling.SEND, + true, + true) + } + + static def makeSubscription(String topicFilter, int qos) { + return new Subscription( + TopicFilter.valueOf(topicFilter), + MqttProperties.SUBSCRIPTION_ID_UNDEFINED, + QoS.ofCode(qos), + SubscribeRetainHandling.SEND, + true, + true) + } +} diff --git a/model/src/test/groovy/javasabr/mqtt/model/util/TopicUtilsTest.groovy b/model/src/test/groovy/javasabr/mqtt/model/util/TopicUtilsTest.groovy deleted file mode 100644 index c377ec64..00000000 --- a/model/src/test/groovy/javasabr/mqtt/model/util/TopicUtilsTest.groovy +++ /dev/null @@ -1,56 +0,0 @@ -package javasabr.mqtt.model.util - -import javasabr.mqtt.test.support.UnitSpecification -import spock.lang.Unroll - -import static javasabr.mqtt.model.util.TopicUtils.* - -class TopicUtilsTest extends UnitSpecification { - - @Unroll - def "should create valid topic name: [#topicName]"() { - expect: - !isInvalid(buildTopicName(topicName)) - where: - topicName | _ - "topic/Name" | _ - "topic" | _ - } - - @Unroll - def "should create valid topic filter: [#topicFilter]"() { - expect: - !isInvalid(buildTopicFilter(topicFilter)) - where: - topicFilter | _ - "topic/Filter" | _ - "topic/+" | _ - "topic/+/Filter" | _ - "topic/#" | _ - } - - @Unroll - def "should detect invalid topic name: [#topicName]"() { - expect: - isInvalid(buildTopicName(topicName)) - where: - topicName | _ - "topic/+" | _ - "topic/" | _ - "topic//Name" | _ - "topic/#" | _ - } - - @Unroll - def "should detect invalid topic filter: [#topicFilter]"() { - expect: - isInvalid(buildTopicFilter(topicFilter)) - where: - topicFilter | _ - "topic/" | _ - "/topic" | _ - "topic//Name" | _ - "topic/##" | _ - "#/Filter" | _ - } -} diff --git a/model/src/testFixtures/groovy/javasabr/mqtt/model/subscription/TestSubscriptionOwner.groovy b/model/src/testFixtures/groovy/javasabr/mqtt/model/subscription/TestSubscriptionOwner.groovy new file mode 100644 index 00000000..fb7d57f9 --- /dev/null +++ b/model/src/testFixtures/groovy/javasabr/mqtt/model/subscription/TestSubscriptionOwner.groovy @@ -0,0 +1,13 @@ +package javasabr.mqtt.model.subscription + +import com.fasterxml.jackson.annotation.JsonValue +import javasabr.mqtt.model.subscribtion.SubscriptionOwner + +record TestSubscriptionOwner(String id) implements SubscriptionOwner { + + @JsonValue + @Override + String toString() { + return id + } +} \ No newline at end of file diff --git a/network/src/main/java/javasabr/mqtt/network/MqttClient.java b/network/src/main/java/javasabr/mqtt/network/MqttClient.java index dfcafaf7..4cc3813e 100644 --- a/network/src/main/java/javasabr/mqtt/network/MqttClient.java +++ b/network/src/main/java/javasabr/mqtt/network/MqttClient.java @@ -2,13 +2,13 @@ import java.util.concurrent.CompletableFuture; import javasabr.mqtt.model.MqttClientConnectionConfig; -import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.subscribtion.SubscriptionOwner; import javasabr.mqtt.network.message.out.ConnectAckMqtt311OutMessage; import javasabr.mqtt.network.message.out.MqttOutMessage; import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; -public interface MqttClient extends MqttUser { +public interface MqttClient extends SubscriptionOwner { interface UnsafeMqttClient extends MqttClient { @@ -30,7 +30,9 @@ interface UnsafeMqttClient extends MqttClient { MqttClientConnectionConfig connectionConfig(); - void send(MqttOutMessage packet); + void send(MqttOutMessage message); - CompletableFuture sendWithFeedback(MqttOutMessage packet); + CompletableFuture sendWithFeedback(MqttOutMessage message); + + void closeWithReason(MqttOutMessage message); } diff --git a/network/src/main/java/javasabr/mqtt/network/MqttSession.java b/network/src/main/java/javasabr/mqtt/network/MqttSession.java index 187ca659..0cfb6faf 100644 --- a/network/src/main/java/javasabr/mqtt/network/MqttSession.java +++ b/network/src/main/java/javasabr/mqtt/network/MqttSession.java @@ -1,10 +1,10 @@ package javasabr.mqtt.network; -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.model.TrackableMessage; +import javasabr.mqtt.model.publishing.Publish; +import javasabr.mqtt.model.subscribtion.Subscription; import javasabr.mqtt.model.topic.TopicFilter; -import javasabr.mqtt.network.message.HasMessageId; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; -import javasabr.rlib.functions.TriConsumer; +import javasabr.rlib.collections.array.Array; public interface MqttSession { @@ -24,14 +24,14 @@ interface PendingMessageHandler { /** * @return true if pending packet can be removed. */ - boolean handleResponse(MqttClient client, HasMessageId response); + boolean handleResponse(MqttClient client, TrackableMessage response); - default void resend(MqttClient client, PublishMqttInMessage packet, int packetId) {} + default void resend(MqttClient client, Publish publish) {} } String clientId(); - int nextPacketId(); + int nextMessageId(); /** * @return the expiration time in ms or -1 if it should not be expired now. @@ -44,24 +44,23 @@ default void resend(MqttClient client, PublishMqttInMessage packet, int packetId boolean hasInPending(); - boolean hasInPending(int packetId); + boolean hasInPending(int messageId); - boolean hasOutPending(int packetId); + boolean hasOutPending(int messageId); - void registerOutPublish(PublishMqttInMessage publish, PendingMessageHandler handler, int packetId); + void registerOutPublish(Publish publish, PendingMessageHandler handler); - void registerInPublish(PublishMqttInMessage publish, PendingMessageHandler handler, int packetId); + void registerInPublish(Publish publish, PendingMessageHandler handler); - void updateOutPendingPacket(MqttClient client, HasMessageId response); + void updateOutPendingPacket(MqttClient client, TrackableMessage response); - void updateInPendingPacket(MqttClient client, HasMessageId response); + void updateInPendingPacket(MqttClient client, TrackableMessage response); - void forEachTopicFilter( - A arg1, - B arg2, - TriConsumer consumer); + void storeSubscription(Subscription subscription); - void addSubscriber(SubscribeTopicFilter subscribe); + void removeSubscription(TopicFilter subscribe); - void removeSubscriber(TopicFilter subscribe); + Array storedSubscriptions(); + + Array findStoredSubscriptionWithId(int subscriptionId); } diff --git a/network/src/main/java/javasabr/mqtt/network/impl/AbstractMqttClient.java b/network/src/main/java/javasabr/mqtt/network/impl/AbstractMqttClient.java index 55fe238c..2e9f01c7 100644 --- a/network/src/main/java/javasabr/mqtt/network/impl/AbstractMqttClient.java +++ b/network/src/main/java/javasabr/mqtt/network/impl/AbstractMqttClient.java @@ -49,15 +49,21 @@ public AbstractMqttClient(MqttConnection connection, MqttClientReleaseHandler re } @Override - public void send(MqttOutMessage packet) { - log.debug(clientId, packet.name(), packet, "[%s] Send to client packet:[%s] %s"::formatted); - connection.send(packet); + public void send(MqttOutMessage message) { + log.debug(clientId, message.name(), message, "[%s] Send to client packet:[%s] %s"::formatted); + connection.send(message); } @Override - public CompletableFuture sendWithFeedback(MqttOutMessage packet) { - log.debug(clientId, packet.name(), packet, "[%s] Send to client packet:[%s] %s"::formatted); - return connection.sendWithFeedback(packet); + public CompletableFuture sendWithFeedback(MqttOutMessage message) { + log.debug(clientId, message.name(), message, "[%s] Send to client packet:[%s] %s"::formatted); + return connection.sendWithFeedback(message); + } + + @Override + public void closeWithReason(MqttOutMessage message) { + sendWithFeedback(message) + .thenAccept(_ -> connection.close()); } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/HasMessageId.java b/network/src/main/java/javasabr/mqtt/network/message/HasMessageId.java deleted file mode 100644 index 5b1d1353..00000000 --- a/network/src/main/java/javasabr/mqtt/network/message/HasMessageId.java +++ /dev/null @@ -1,9 +0,0 @@ -package javasabr.mqtt.network.message; - -import javasabr.mqtt.network.MqttConnection; -import javasabr.rlib.network.packet.NetworkPacket; - -public interface HasMessageId extends NetworkPacket { - - int messageId(); -} diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/AuthenticationMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/AuthenticationMqttInMessage.java index c53008ef..36d59b54 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/AuthenticationMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/AuthenticationMqttInMessage.java @@ -3,7 +3,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.reason.code.AuthenticateReasonCode; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; @@ -24,19 +24,19 @@ public class AuthenticationMqttInMessage extends MqttInMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.AUTHENTICATE.ordinal(); - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by a UTF-8 Encoded String containing the name of the authentication method. It is a Protocol Error to omit the Authentication Method or to include it more than once. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_METHOD, + MqttMessageProperty.AUTHENTICATION_METHOD, /* Followed by Binary Data containing authentication data. It is a Protocol Error to include Authentication Data more than once. The contents of this data are defined by the authentication method. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_DATA, + MqttMessageProperty.AUTHENTICATION_DATA, /* Followed by the UTF-8 Encoded String representing the reason for the disconnect. This Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the receiver. @@ -45,7 +45,7 @@ public class AuthenticationMqttInMessage extends MqttInMessage { Maximum Packet Size specified by the receiver [MQTT-3.15.2-2]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other information. The sender MUST NOT send this property if it would increase the size of the AUTH packet @@ -53,7 +53,7 @@ public class AuthenticationMqttInMessage extends MqttInMessage { allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); AuthenticateReasonCode reasonCode; @@ -83,12 +83,12 @@ protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } @Override - protected void applyProperty(PacketProperty property, byte[] value) { + protected void applyProperty(MqttMessageProperty property, byte[] value) { switch (property) { case AUTHENTICATION_DATA -> authenticationData = value; default -> unexpectedProperty(property); @@ -96,7 +96,7 @@ protected void applyProperty(PacketProperty property, byte[] value) { } @Override - protected void applyProperty(PacketProperty property, String value) { + protected void applyProperty(MqttMessageProperty property, String value) { switch (property) { case REASON_STRING -> reason = value; case AUTHENTICATION_METHOD -> authenticationMethod = value; diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectAckMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectAckMqttInMessage.java index 4c7610df..f736e697 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectAckMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectAckMqttInMessage.java @@ -3,9 +3,9 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttVersion; -import javasabr.mqtt.model.PacketProperty; import javasabr.mqtt.model.QoS; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; @@ -30,7 +30,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.CONNECT_ACK.ordinal(); - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the Four Byte Integer representing the Session Expiry Interval in seconds. It is a Protocol Error to include the Session Expiry Interval more than once. @@ -38,7 +38,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { If the Session Expiry Interval is absent the value in the CONNECT Packet used. The server uses this property to inform the Client that it is using a value other than that sent by the Client in the CONNACK. */ - PacketProperty.SESSION_EXPIRY_INTERVAL, + MqttMessageProperty.SESSION_EXPIRY_INTERVAL, /* Followed by the Two Byte Integer representing the Receive Maximum value. It is a Protocol Error to include the Receive Maximum value more than once or for it to have the value 0. @@ -49,7 +49,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { If the Receive Maximum value is absent, then its value defaults to 65,535. */ - PacketProperty.RECEIVE_MAXIMUM_PUBLISH, + MqttMessageProperty.RECEIVE_MAXIMUM_PUBLISHES, /* Followed by a Byte with a value of either 0 or 1. It is a Protocol Error to include Maximum QoS more than once, or to have a value other than 0 or 1. If the Maximum QoS is absent, the Client uses a Maximum @@ -70,7 +70,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { reject the connection. It SHOULD use a CONNACK packet with Reason Code 0x9B (QoS not supported) as described in section 4.13 Handling errors, and MUST close the Network Connection */ - PacketProperty.MAXIMUM_QOS, + MqttMessageProperty.MAXIMUM_QOS, /* Followed by a Byte field. If present, this byte declares whether the Server supports retained messages. A value of 0 means that retained messages are not supported. A value of 1 means retained messages are @@ -87,7 +87,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { Server SHOULD send a DISCONNECT with Reason Code of 0x9A (Retain not supported) as described in section 4.13. */ - PacketProperty.RETAIN_AVAILABLE, + MqttMessageProperty.RETAIN_AVAILABLE, /* Followed by a Four Byte Integer representing the Maximum Packet Size the Server is willing to accept. If the Maximum Packet Size is not present, there is no limit on the packet size imposed beyond the @@ -104,7 +104,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { a Server receives a packet whose size exceeds this limit, this is a Protocol Error, the Server uses DISCONNECT with Reason Code 0x95 (Packet too large), as described in section 4.13. */ - PacketProperty.MAXIMUM_PACKET_SIZE, + MqttMessageProperty.MAXIMUM_MESSAGE_SIZE, /* Followed by the UTF-8 string which is the Assigned Client Identifier. It is a Protocol Error to include the Assigned Client Identifier more than once. @@ -116,7 +116,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { containing an Assigned Client Identifier. The Assigned Client Identifier MUST be a new Client Identifier not used by any other Session currently in the Server [ */ - PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, + MqttMessageProperty.ASSIGNED_CLIENT_IDENTIFIER, /* Followed by the Two Byte Integer representing the Topic Alias Maximum value. It is a Protocol Error to include the Topic Alias Maximum value more than once. If the Topic Alias Maximum property is absent, @@ -129,7 +129,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { on this connection. If Topic Alias Maximum is absent or 0, the Client MUST NOT send any Topic Aliases on to the Server */ - PacketProperty.TOPIC_ALIAS_MAXIMUM, + MqttMessageProperty.TOPIC_ALIAS_MAXIMUM, /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the @@ -139,7 +139,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { property if it would increase the size of the CONNACK packet beyond the Maximum Packet Size specified by the Client [MQTT-3.2.2-19]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by a UTF-8 String Pair. This property can be used to provide additional information to the Client including diagnostic information. The Server MUST NOT send this property if it would increase the size of @@ -150,7 +150,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { The content and meaning of this property is not defined by this specification. The receiver of a CONNACK containing this property MAY ignore it. */ - PacketProperty.USER_PROPERTY, + MqttMessageProperty.USER_PROPERTY, /* Followed by a Byte field. If present, this byte declares whether the Server supports Wildcard Subscriptions. A value is 0 means that Wildcard Subscriptions are not supported. A value of 1 means @@ -168,7 +168,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { Wildcard Subscription. In this case the Server MAY send a SUBACK Control Packet with a Reason Code 0xA2 (Wildcard Subscriptions not supported). */ - PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, + MqttMessageProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, /* Followed by a Byte field. If present, this byte declares whether the Server supports Subscription Identifiers. A value is 0 means that Subscription Identifiers are not supported. A value of 1 means @@ -180,7 +180,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { Subscription Identifiers, this is a Protocol Error. The Server uses DISCONNECT with Reason Code of 0xA1 (Subscription Identifiers not supported) as described in section 4.13. */ - PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, + MqttMessageProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, /* Followed by a Byte field. If present, this byte declares whether the Server supports Shared Subscriptions. A value is 0 means that Shared Subscriptions are not supported. A value of 1 means Shared @@ -191,7 +191,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { Shared Subscriptions, this is a Protocol Error. The Server uses DISCONNECT with Reason Code 0x9E (Shared Subscriptions not supported) as described in section 4.13. */ - PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, + MqttMessageProperty.SHARED_SUBSCRIPTION_AVAILABLE, /* Followed by a Two Byte Integer with the Keep Alive time assigned by the Server. If the Server sends a Server Keep Alive on the CONNACK packet, the Client MUST use this value instead of the Keep Alive @@ -199,7 +199,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { the Server MUST use the Keep Alive value set by the Client on CONNECT [MQTT-3.2.2-22]. It is a Protocol Error to include the Server Keep Alive more than once. */ - PacketProperty.SERVER_KEEP_ALIVE, + MqttMessageProperty.SERVER_KEEP_ALIVE, /* Followed by a UTF-8 Encoded String which is used as the basis for creating a Response Topic. The way in which the Client creates a Response Topic from the Response Information is not defined by this @@ -208,7 +208,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { If the Client sends a Request Response Information with a value 1, it is OPTIONAL for the Server to send the Response Information in the CONNACK. */ - PacketProperty.RESPONSE_INFORMATION, + MqttMessageProperty.RESPONSE_INFORMATION, /* Followed by a UTF-8 Encoded String which can be used by the Client to identify another Server to use. It is a Protocol Error to include the Server Reference more than once. @@ -218,20 +218,20 @@ public class ConnectAckMqttInMessage extends MqttInMessage { Refer to section 4.11 Server redirection for information about how Server Reference is used */ - PacketProperty.SERVER_REFERENCE, + MqttMessageProperty.SERVER_REFERENCE, /* Followed by a UTF-8 Encoded String containing the name of the authentication method. It is a Protocol Error to include the Authentication Method more than once. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_METHOD, + MqttMessageProperty.AUTHENTICATION_METHOD, /* Followed by Binary Data containing authentication data. The contents of this data are defined by the authentication method and the state of already exchanged authentication data. It is a Protocol Error to include the Authentication Data more than once. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_DATA); + MqttMessageProperty.AUTHENTICATION_DATA); /** * The values the Connect Reason Code are shown below. If a well formed CONNECT packet is received by the Server, but @@ -261,7 +261,7 @@ public class ConnectAckMqttInMessage extends MqttInMessage { long sessionExpiryInterval; int receiveMaxPublishes; - int maxPacketSize; + int maxMessageSize; int topicAliasMaxValue; int serverKeepAlive; @@ -286,10 +286,10 @@ public ConnectAckMqttInMessage(byte info) { this.authenticationMethod = StringUtils.EMPTY; this.authenticationData = ArrayUtils.EMPTY_BYTE_ARRAY; this.serverKeepAlive = MqttProperties.SERVER_KEEP_ALIVE_UNDEFINED; - this.maxPacketSize = MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED; + this.maxMessageSize = MqttProperties.MAXIMUM_MESSAGE_SIZE_UNDEFINED; this.sessionExpiryInterval = MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED; this.topicAliasMaxValue = MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED; - this.receiveMaxPublishes = MqttProperties.RECEIVE_MAXIMUM_UNDEFINED; + this.receiveMaxPublishes = MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_UNDEFINED; } @Override @@ -305,12 +305,12 @@ protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } @Override - protected void applyProperty(PacketProperty property, byte[] value) { + protected void applyProperty(MqttMessageProperty property, byte[] value) { switch (property) { case AUTHENTICATION_DATA -> authenticationData = value; default -> unexpectedProperty(property); @@ -318,7 +318,7 @@ protected void applyProperty(PacketProperty property, byte[] value) { } @Override - protected void applyProperty(PacketProperty property, String value) { + protected void applyProperty(MqttMessageProperty property, String value) { switch (property) { case REASON_STRING -> reason = value; case ASSIGNED_CLIENT_IDENTIFIER -> assignedClientId = value; @@ -330,17 +330,17 @@ protected void applyProperty(PacketProperty property, String value) { } @Override - protected void applyProperty(PacketProperty property, long value) { + protected void applyProperty(MqttMessageProperty property, long value) { switch (property) { case WILDCARD_SUBSCRIPTION_AVAILABLE -> wildcardSubscriptionAvailable = NumberUtils.toBoolean(value); case SHARED_SUBSCRIPTION_AVAILABLE -> sharedSubscriptionAvailable = NumberUtils.toBoolean(value); case SUBSCRIPTION_IDENTIFIER_AVAILABLE -> subscriptionIdAvailable = NumberUtils.toBoolean(value); case RETAIN_AVAILABLE -> retainAvailable = NumberUtils.toBoolean(value); - case RECEIVE_MAXIMUM_PUBLISH -> receiveMaxPublishes = (int) NumberUtils.validate( + case RECEIVE_MAXIMUM_PUBLISHES -> receiveMaxPublishes = (int) NumberUtils.validate( value, - MqttProperties.RECEIVE_MAXIMUM_MIN, - MqttProperties.RECEIVE_MAXIMUM_MAX); - case MAXIMUM_QOS -> maximumQos = QoS.of((int) value); + MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_MIN, + MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_MAX); + case MAXIMUM_QOS -> maximumQos = QoS.ofCode((int) value); case SERVER_KEEP_ALIVE -> serverKeepAlive = NumberUtils.validate( (int) value, MqttProperties.SERVER_KEEP_ALIVE_MIN, @@ -353,10 +353,10 @@ protected void applyProperty(PacketProperty property, long value) { value, MqttProperties.SESSION_EXPIRY_INTERVAL_MIN, MqttProperties.SESSION_EXPIRY_INTERVAL_INFINITY); - case MAXIMUM_PACKET_SIZE -> maxPacketSize = NumberUtils.validate( + case MAXIMUM_MESSAGE_SIZE -> maxMessageSize = NumberUtils.validate( (int) value, - MqttProperties.MAXIMUM_PACKET_SIZE_MIN, - MqttProperties.MAXIMUM_PACKET_SIZE_MAX); + MqttProperties.MAXIMUM_MESSAGE_SIZE_MIN, + MqttProperties.MAXIMUM_MESSAGE_SIZE_MAX); default -> unexpectedProperty(property); } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java index 33d1c62e..f1f6a318 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/ConnectMqttInMessage.java @@ -4,9 +4,9 @@ import java.util.EnumSet; import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttVersion; -import javasabr.mqtt.model.PacketProperty; import javasabr.mqtt.model.exception.ConnectionRejectException; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -33,7 +33,7 @@ public class ConnectMqttInMessage extends MqttInMessage { DebugUtils.registerIncludedFields("clientId", "keepAlive", "cleanStart", "mqttVersion"); } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* If the Session Expiry Interval is absent the value 0 is used. If it is set to 0, or is absent, the Session ends when the Network Connection is closed. @@ -42,7 +42,7 @@ public class ConnectMqttInMessage extends MqttInMessage { The Client and Server MUST store the Session State after the Network Connection is closed if the Session Expiry Interval is greater than 0 */ - PacketProperty.SESSION_EXPIRY_INTERVAL, + MqttMessageProperty.SESSION_EXPIRY_INTERVAL, /* Followed by the Two Byte Integer representing the Receive Maximum value. It is a Protocol Error to include the Receive Maximum value more than once or for it to have the value 0. @@ -53,7 +53,7 @@ public class ConnectMqttInMessage extends MqttInMessage { The value of Receive Maximum applies only to the current Network Connection. If the Receive Maximum value is absent then its value defaults to 65,535. */ - PacketProperty.RECEIVE_MAXIMUM_PUBLISH, + MqttMessageProperty.RECEIVE_MAXIMUM_PUBLISHES, /* Followed by a Four Byte Integer representing the Maximum Packet Size the Client is willing to accept. If the Maximum Packet Size is not present, no limit on the packet size is imposed beyond the limitations in @@ -62,7 +62,7 @@ public class ConnectMqttInMessage extends MqttInMessage { It is a Protocol Error to include the Maximum Packet Size more than once, or for the value to be set to zero. */ - PacketProperty.MAXIMUM_PACKET_SIZE, + MqttMessageProperty.MAXIMUM_MESSAGE_SIZE, /* Followed by the Two Byte Integer representing the Topic Alias Maximum value. It is a Protocol Error to include the Topic Alias Maximum value more than once. If the Topic Alias Maximum property is absent, @@ -75,7 +75,7 @@ public class ConnectMqttInMessage extends MqttInMessage { this connection. If Topic Alias Maximum is absent or zero, the Server MUST NOT send any Topic Aliases to the Client [MQTT-3.1.2-27]. */ - PacketProperty.TOPIC_ALIAS_MAXIMUM, + MqttMessageProperty.TOPIC_ALIAS_MAXIMUM, /* Followed by a Byte with a value of either 0 or 1. It is Protocol Error to include the Request Response Information more than once, or to have a value other than 0 or 1. If the Request Response Information is @@ -85,7 +85,7 @@ public class ConnectMqttInMessage extends MqttInMessage { value of 0 indicates that the Server MUST NOT return Response Information [MQTT-3.1.2-28]. If the value is 1 the Server MAY return Response Information in the CONNACK packet. */ - PacketProperty.REQUEST_RESPONSE_INFORMATION, + MqttMessageProperty.REQUEST_RESPONSE_INFORMATION, /* Followed by a Byte with a value of either 0 or 1. It is a Protocol Error to include Request Problem Information more than once, or to have a value other than 0 or 1. If the Request Problem Information is @@ -104,12 +104,12 @@ public class ConnectMqttInMessage extends MqttInMessage { If this value is 1, the Server MAY return a Reason String or User Properties on any packet where it is allowed. */ - PacketProperty.REQUEST_PROBLEM_INFORMATION, + MqttMessageProperty.REQUEST_PROBLEM_INFORMATION, /* The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once */ - PacketProperty.USER_PROPERTY, + MqttMessageProperty.USER_PROPERTY, /* Followed by a UTF-8 Encoded String containing the name of the authentication method used for extended authentication .It is a Protocol Error to include Authentication Method more than once. @@ -118,7 +118,7 @@ public class ConnectMqttInMessage extends MqttInMessage { If a Client sets an Authentication Method in the CONNECT, the Client MUST NOT send any packets other than AUTH or DISCONNECT packets until it has received a CONNACK packet */ - PacketProperty.AUTHENTICATION_METHOD, + MqttMessageProperty.AUTHENTICATION_METHOD, /* Followed by Binary Data containing authentication data. It is a Protocol Error to include Authentication Data if there is no Authentication Method. It is a Protocol Error to include Authentication Data more than @@ -127,9 +127,9 @@ public class ConnectMqttInMessage extends MqttInMessage { The contents of this data are defined by the authentication method. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_DATA); + MqttMessageProperty.AUTHENTICATION_DATA); - private static final Set WILL_PROPERTIES = EnumSet.of( + private static final Set WILL_PROPERTIES = EnumSet.of( /* Followed by the Four Byte Integer representing the Will Delay Interval in seconds. It is a Protocol Error to include the Will Delay Interval more than once. If the Will Delay Interval is absent, the default value is 0 @@ -139,7 +139,7 @@ public class ConnectMqttInMessage extends MqttInMessage { Session ends, whichever happens first. If a new Network Connection to this Session is made before the Will Delay Interval has passed, the Server MUST NOT send the Will Message */ - PacketProperty.WILL_DELAY_INTERVAL, + MqttMessageProperty.WILL_DELAY_INTERVAL, /* Followed by the value of the Payload Format Indicator, either of: • 0 (0x00) Byte Indicates that the Will Message is unspecified bytes, which is equivalent to not @@ -151,7 +151,7 @@ public class ConnectMqttInMessage extends MqttInMessage { It is a Protocol Error to include the Payload Format Indicator more than once. The Server MAY validate that the Will Message is of the format indicated, and if it is not se */ - PacketProperty.PAYLOAD_FORMAT_INDICATOR, + MqttMessageProperty.PAYLOAD_FORMAT_INDICATOR, /* Followed by the Four Byte Integer representing the Message Expiry Interval. It is a Protocol Error to include the Message Expiry Interval more than once. @@ -161,19 +161,19 @@ public class ConnectMqttInMessage extends MqttInMessage { If absent, no Message Expiry Interval is sent when the Server publishes the Will Message. */ - PacketProperty.MESSAGE_EXPIRY_INTERVAL, + MqttMessageProperty.MESSAGE_EXPIRY_INTERVAL, /* Followed by a UTF-8 Encoded String describing the content of the Will Message. It is a Protocol Error to include the Content Type more than once. The value of the Content Type is defined by the sending and receiving application. */ - PacketProperty.CONTENT_TYPE, + MqttMessageProperty.CONTENT_TYPE, /* Followed by a UTF-8 Encoded String which is used as the Topic Name for a response message. It is a Protocol Error to include the Response Topic more than once. The presence of a Response Topic identifies the Will Message as a Request. */ - PacketProperty.RESPONSE_TOPIC, + MqttMessageProperty.RESPONSE_TOPIC, /* Followed by Binary Data. The Correlation Data is used by the sender of the Request Message to identify which request the Response Message is for when it is received. It is a Protocol Error to include @@ -183,14 +183,14 @@ public class ConnectMqttInMessage extends MqttInMessage { The value of the Correlation Data only has meaning to the sender of the Request Message and receiver of the Response Message. */ - PacketProperty.CORRELATION_DATA, + MqttMessageProperty.CORRELATION_DATA, /* Followed by a UTF-8 String Pair. The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. The Server MUST maintain the order of User Properties when publishing the Will Message */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); MqttVersion mqttVersion = MqttVersion.MQTT_3_1_1; @@ -215,8 +215,8 @@ public class ConnectMqttInMessage extends MqttInMessage { byte[] authenticationData = ArrayUtils.EMPTY_BYTE_ARRAY; long sessionExpiryInterval = MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED; - int receiveMaxPublishes = MqttProperties.RECEIVE_MAXIMUM_UNDEFINED; - int maxPacketSize = MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED; + int receiveMaxPublishes = MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_UNDEFINED; + int maxPacketSize = MqttProperties.MAXIMUM_MESSAGE_SIZE_UNDEFINED; int topicAliasMaxValue = MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED; boolean requestResponseInformation = false; boolean requestProblemInformation = false; @@ -343,12 +343,12 @@ protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer bu } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } @Override - protected void applyProperty(PacketProperty property, byte[] value) { + protected void applyProperty(MqttMessageProperty property, byte[] value) { switch (property) { case AUTHENTICATION_DATA -> authenticationData = value; default -> unexpectedProperty(property); @@ -356,7 +356,7 @@ protected void applyProperty(PacketProperty property, byte[] value) { } @Override - protected void applyProperty(PacketProperty property, String value) { + protected void applyProperty(MqttMessageProperty property, String value) { switch (property) { case AUTHENTICATION_METHOD -> authenticationMethod = value; default -> unexpectedProperty(property); @@ -364,14 +364,14 @@ protected void applyProperty(PacketProperty property, String value) { } @Override - protected void applyProperty(PacketProperty property, long value) { + protected void applyProperty(MqttMessageProperty property, long value) { switch (property) { case REQUEST_RESPONSE_INFORMATION -> requestResponseInformation = NumberUtils.toBoolean(value); case REQUEST_PROBLEM_INFORMATION -> requestProblemInformation = NumberUtils.toBoolean(value); - case RECEIVE_MAXIMUM_PUBLISH -> receiveMaxPublishes = NumberUtils.validate( + case RECEIVE_MAXIMUM_PUBLISHES -> receiveMaxPublishes = NumberUtils.validate( (int) value, - MqttProperties.RECEIVE_MAXIMUM_MIN, - MqttProperties.RECEIVE_MAXIMUM_MAX); + MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_MIN, + MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_MAX); case TOPIC_ALIAS_MAXIMUM -> topicAliasMaxValue = NumberUtils.validate( (int) value, MqttProperties.TOPIC_ALIAS_MIN, @@ -380,10 +380,10 @@ protected void applyProperty(PacketProperty property, long value) { value, MqttProperties.SESSION_EXPIRY_INTERVAL_MIN, MqttProperties.SESSION_EXPIRY_INTERVAL_INFINITY); - case MAXIMUM_PACKET_SIZE -> maxPacketSize = NumberUtils.validate( + case MAXIMUM_MESSAGE_SIZE -> maxPacketSize = NumberUtils.validate( (int) value, - MqttProperties.MAXIMUM_PACKET_SIZE_MIN, - MqttProperties.MAXIMUM_PACKET_SIZE_MAX); + MqttProperties.MAXIMUM_MESSAGE_SIZE_MIN, + MqttProperties.MAXIMUM_MESSAGE_SIZE_MAX); default -> unexpectedProperty(property); } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/DisconnectMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/DisconnectMqttInMessage.java index c30bdcb4..da85bc81 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/DisconnectMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/DisconnectMqttInMessage.java @@ -4,9 +4,9 @@ import java.util.EnumSet; import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttVersion; -import javasabr.mqtt.model.PacketProperty; import javasabr.mqtt.model.reason.code.DisconnectReasonCode; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; @@ -30,7 +30,7 @@ public class DisconnectMqttInMessage extends MqttInMessage { DebugUtils.registerIncludedFields("reasonCode"); } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* If the Session Expiry Interval is absent, the Session Expiry Interval in the CONNECT packet is used. @@ -41,13 +41,13 @@ public class DisconnectMqttInMessage extends MqttInMessage { Expiry Interval is received by the Server, it does not treat it as a valid DISCONNECT packet. The Server uses DISCONNECT with Reason Code 0x82 (Protocol Error) as described in */ - PacketProperty.SESSION_EXPIRY_INTERVAL, + MqttMessageProperty.SESSION_EXPIRY_INTERVAL, /* The sender MUST NOT send this Property if it would increase the size of the DISCONNECT packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.14.2-3]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other information. The sender MUST NOT send this property if it would increase the size of the DISCONNECT @@ -55,12 +55,12 @@ public class DisconnectMqttInMessage extends MqttInMessage { allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY, + MqttMessageProperty.USER_PROPERTY, /* The Server sends DISCONNECT including a Server Reference and Reason Code {0x9C (Use another 2601 server)} or 0x9D (Server moved) as described in section 4.13. */ - PacketProperty.SERVER_REFERENCE); + MqttMessageProperty.SERVER_REFERENCE); DisconnectReasonCode reasonCode; @@ -95,7 +95,7 @@ protected void readImpl(MqttConnection connection, ByteBuffer buffer) { protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901207 if (connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining()) { - reasonCode = DisconnectReasonCode.of(readByteUnsigned(buffer)); + reasonCode = DisconnectReasonCode.ofCode(readByteUnsigned(buffer)); } } @@ -105,12 +105,12 @@ protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer bu } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } @Override - protected void applyProperty(PacketProperty property, long value) { + protected void applyProperty(MqttMessageProperty property, long value) { switch (property) { case SESSION_EXPIRY_INTERVAL: { sessionExpiryInterval = value; @@ -123,7 +123,7 @@ protected void applyProperty(PacketProperty property, long value) { } @Override - protected void applyProperty(PacketProperty property, String value) { + protected void applyProperty(MqttMessageProperty property, String value) { switch (property) { case REASON_STRING: { reason = value; 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 9bd35e59..bb851d99 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 @@ -9,12 +9,12 @@ import java.util.Collections; import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.MqttVersion; -import javasabr.mqtt.model.PacketProperty; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.exception.ConnectionRejectException; -import javasabr.mqtt.model.exception.MalformedPacketMqttException; +import javasabr.mqtt.model.exception.MalformedProtocolMqttException; import javasabr.mqtt.model.exception.MqttException; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -37,7 +37,8 @@ public abstract class MqttInMessage extends AbstractReadableNetworkPacket EMPTY_PROPERTIES = Array.empty(StringPair.class); + protected static final Array EMPTY_PROPERTIES = Array.empty(StringPair.class); + protected static final Array EMPTY_STRINGS = Array.empty(String.class); private record Utf8Decoder(CharsetDecoder decoder, ByteBuffer inBuffer, CharBuffer outBuffer) {} @@ -109,7 +110,7 @@ protected void readProperties(MqttConnection connection, ByteBuffer buffer) { protected void readPayload(MqttConnection connection, ByteBuffer buffer) {} - protected void readProperties(MqttConnection connection, ByteBuffer buffer, Set availableProperties) { + protected void readProperties(MqttConnection connection, ByteBuffer buffer, Set availableProperties) { int propertiesLength = MqttDataUtils.readMbi(buffer); if (propertiesLength == MqttDataUtils.UNKNOWN_LENGTH) { @@ -122,7 +123,7 @@ protected void readProperties(MqttConnection connection, ByteBuffer buffer, Set< MqttServerConnectionConfig serverConnectionConfig = connection.serverConnectionConfig(); while (buffer.position() < lastPositionInBuffer) { - PacketProperty property = PacketProperty.byId(readByteUnsigned(buffer)); + MqttMessageProperty property = MqttMessageProperty.byId(readByteUnsigned(buffer)); if (!availableProperties.contains(property)) { throw new IllegalStateException("Property:[" + property + "] is not available for packet:[" + this + "]"); } @@ -164,17 +165,17 @@ protected void readProperties(MqttConnection connection, ByteBuffer buffer, Set< } } - protected Set availableProperties() { + protected Set availableProperties() { return Collections.emptySet(); } - protected void applyProperty(PacketProperty property, long value) {} + protected void applyProperty(MqttMessageProperty property, long value) {} - protected void applyProperty(PacketProperty property, String value) {} + protected void applyProperty(MqttMessageProperty property, String value) {} - protected void applyProperty(PacketProperty property, byte[] value) {} + protected void applyProperty(MqttMessageProperty property, byte[] value) {} - protected void applyProperty(PacketProperty property, StringPair value) { + protected void applyProperty(MqttMessageProperty property, StringPair value) { switch (property) { case USER_PROPERTY: if (userProperties == null) { @@ -193,7 +194,7 @@ protected String readString(ByteBuffer buffer, int maxLength) { int stringLength = readShortUnsigned(buffer); if (stringLength > inBuffer.capacity() || stringLength > maxLength) { - throw new MalformedPacketMqttException(); + throw new MalformedProtocolMqttException(); } inBuffer.clear(); @@ -211,7 +212,7 @@ protected String readString(ByteBuffer buffer, int maxLength) { CoderResult result = decoder.decode(inBuffer, outBuffer, true); if (result.isError()) { - throw new MalformedPacketMqttException(); + throw new MalformedProtocolMqttException(); } return outBuffer @@ -241,8 +242,8 @@ protected byte[] readPayload(ByteBuffer buffer) { return data; } - protected void unexpectedProperty(PacketProperty property) { - throw new IllegalArgumentException("Unsupported property:[" + property + "]"); + protected void unexpectedProperty(MqttMessageProperty property) { + throw new MalformedProtocolMqttException("Unsupported property:[" + property + "]"); } @Override diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/PublishAckMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/PublishAckMqttInMessage.java index 0222bdb9..a8e59370 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 @@ -4,11 +4,11 @@ import java.util.EnumSet; import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttVersion; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.model.reason.code.PublishAckReasonCode; import javasabr.mqtt.network.MqttConnection; -import javasabr.mqtt.network.message.HasMessageId; import javasabr.mqtt.network.message.MqttMessageType; import lombok.AccessLevel; import lombok.Getter; @@ -21,7 +21,7 @@ @Getter @Accessors(fluent = true, chain = false) @FieldDefaults(level = AccessLevel.PRIVATE) -public class PublishAckMqttInMessage extends MqttInMessage implements HasMessageId { +public class PublishAckMqttInMessage extends MqttInMessage implements TrackableMessage { private static final int MESSAGE_TYPE = MqttMessageType.PUBLISH_ACK.ordinal(); @@ -29,7 +29,7 @@ public class PublishAckMqttInMessage extends MqttInMessage implements HasMessage DebugUtils.registerIncludedFields("reasonCode", "messageId"); } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is a human readable string designed for diagnostics and is not intended to be parsed by @@ -40,7 +40,7 @@ public class PublishAckMqttInMessage extends MqttInMessage implements HasMessage specified by the receiver [MQTT-3.4.2-2]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information. The sender MUST NOT send this property if it would increase the size of the PUBACK @@ -48,7 +48,7 @@ public class PublishAckMqttInMessage extends MqttInMessage implements HasMessage allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); PublishAckReasonCode reasonCode; int messageId; @@ -85,12 +85,12 @@ protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer bu } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } @Override - protected void applyProperty(PacketProperty property, String value) { + protected void applyProperty(MqttMessageProperty property, String value) { switch (property) { case REASON_STRING -> reason = value; default -> unexpectedProperty(property); 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 1e746f29..39c4630d 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 @@ -4,11 +4,11 @@ import java.util.EnumSet; import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttVersion; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode; import javasabr.mqtt.network.MqttConnection; -import javasabr.mqtt.network.message.HasMessageId; import javasabr.mqtt.network.message.MqttMessageType; import lombok.AccessLevel; import lombok.Getter; @@ -21,7 +21,7 @@ @Getter @Accessors(fluent = true, chain = false) @FieldDefaults(level = AccessLevel.PRIVATE) -public class PublishCompleteMqttInMessage extends MqttInMessage implements HasMessageId { +public class PublishCompleteMqttInMessage extends MqttInMessage implements TrackableMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.PUBLISH_COMPLETED.ordinal(); @@ -29,7 +29,7 @@ public class PublishCompleteMqttInMessage extends MqttInMessage implements HasMe DebugUtils.registerIncludedFields("reasonCode", "messageId"); } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the @@ -40,7 +40,7 @@ public class PublishCompleteMqttInMessage extends MqttInMessage implements HasMe specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the @@ -48,7 +48,7 @@ public class PublishCompleteMqttInMessage extends MqttInMessage implements HasMe Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); PublishCompletedReasonCode reasonCode; int messageId; @@ -87,12 +87,12 @@ protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer bu } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } @Override - protected void applyProperty(PacketProperty property, String value) { + protected void applyProperty(MqttMessageProperty property, String value) { switch (property) { case REASON_STRING -> reason = value; default -> unexpectedProperty(property); 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 df2af353..7c344c61 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 @@ -1,28 +1,25 @@ package javasabr.mqtt.network.message.in; -import static javasabr.mqtt.model.util.TopicUtils.EMPTY_TOPIC_NAME; -import static javasabr.mqtt.model.util.TopicUtils.buildTopicName; - import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttProperties; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.PayloadFormat; import javasabr.mqtt.model.QoS; -import javasabr.mqtt.model.topic.TopicName; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.IntArray; import javasabr.rlib.collections.array.MutableIntArray; import javasabr.rlib.common.util.ArrayUtils; -import javasabr.rlib.common.util.NumberUtils; import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; /** * Publish message. @@ -30,15 +27,22 @@ @Getter @Accessors(fluent = true) @FieldDefaults(level = AccessLevel.PRIVATE) -public class PublishMqttInMessage extends MqttInMessage { +public class PublishMqttInMessage extends TrackableMqttInMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.PUBLISH.ordinal(); static { - DebugUtils.registerIncludedFields("topicName", "qos", "duplicate", "messageId"); + DebugUtils.registerIncludedFields( + "rawTopicName", + "topicAlias", + "qos", + "duplicate", + "messageId", + "messageExpiryInterval", + "payloadFormat"); } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the value of the Payload Format Indicator, either of: • 0 (0x00) Byte Indicates that the Payload is unspecified bytes, which is equivalent to not sending a @@ -53,7 +57,7 @@ public class PublishMqttInMessage extends MqttInMessage { as described in section 4.13. Refer to section 5.4.9 for information about security issues in validating the payload format. */ - PacketProperty.PAYLOAD_FORMAT_INDICATOR, + MqttMessageProperty.PAYLOAD_FORMAT_INDICATOR, /* Followed by the Four Byte Integer representing the Message Expiry Interval. @@ -67,7 +71,7 @@ public class PublishMqttInMessage extends MqttInMessage { received value minus the time that the Application Message has been waiting in the Server [MQTT-3.3.2- 6]. Refer to section 4.1 for details and limitations of stored state. */ - PacketProperty.MESSAGE_EXPIRY_INTERVAL, + MqttMessageProperty.MESSAGE_EXPIRY_INTERVAL, /* Followed by the Two Byte integer representing the Topic Alias value. It is a Protocol Error to include the Topic Alias value more than once. @@ -108,7 +112,7 @@ public class PublishMqttInMessage extends MqttInMessage { Client sends a PUBLISH containing a Topic Alias value of 1 to a Server and the Server sends a PUBLISH with a Topic Alias value of 1 to that Client they will in general be referring to different Topics. */ - PacketProperty.TOPIC_ALIAS, + MqttMessageProperty.TOPIC_ALIAS, /* Followed by a UTF-8 Encoded String which is used as the Topic Name for a response message. The Response Topic MUST be a UTF-8 Encoded String as defined in section 1.5.4 [MQTT-3.3.2-13]. The @@ -121,7 +125,7 @@ public class PublishMqttInMessage extends MqttInMessage { The Server MUST send the Response Topic unaltered to all subscribers receiving the Application Message [MQTT-3.3.2-15]. */ - PacketProperty.RESPONSE_TOPIC, + MqttMessageProperty.RESPONSE_TOPIC, /* Followed by Binary Data. The Correlation Data is used by the sender of the Request Message to identify which request the Response Message is for when it is received. It is a Protocol Error to include @@ -132,7 +136,7 @@ public class PublishMqttInMessage extends MqttInMessage { Message [MQTT-3.3.2-16]. The value of the Correlation Data only has meaning to the sender of the Request Message and receiver of the Response Message. */ - PacketProperty.CORRELATION_DATA, + MqttMessageProperty.CORRELATION_DATA, /* Followed by a UTF-8 String Pair. The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. @@ -141,7 +145,7 @@ public class PublishMqttInMessage extends MqttInMessage { Application Message to a Client [MQTT-3.3.2-17]. The Server MUST maintain the order of User Properties when forwarding the Application Message [MQTT-3.3.2-18]. */ - PacketProperty.USER_PROPERTY, + MqttMessageProperty.USER_PROPERTY, /* Followed by a Variable Byte Integer representing the identifier of the subscription. @@ -150,7 +154,7 @@ public class PublishMqttInMessage extends MqttInMessage { publication is the result of a match to more than one subscription, in this case their order is not significant. */ - PacketProperty.SUBSCRIPTION_IDENTIFIER, + MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, /* Followed by a UTF-8 Encoded String describing the content of the Application Message. The Content Type MUST be a UTF-8 Encoded String as defined in section 1.5.4 [MQTT-3.3.2-19]. @@ -160,7 +164,7 @@ public class PublishMqttInMessage extends MqttInMessage { A Server MUST send the Content Type unaltered to all subscribers receiving the Application Message [MQTT-3.3.2-20]. */ - PacketProperty.CONTENT_TYPE); + MqttMessageProperty.CONTENT_TYPE); /** * This field indicates the level of assurance for delivery of an Application Message. The QoS levels are shown @@ -249,39 +253,48 @@ public class PublishMqttInMessage extends MqttInMessage { * To reduce the size of the PUBLISH packet the sender can use a Topic Alias. The Topic Alias is described in section * 3.3.2.3.4. It is a Protocol Error if the Topic Name is zero length and there is no Topic Alias. */ - TopicName topicName; + String rawTopicName; /** * The Packet Identifier field is only present in PUBLISH packets where the QoS level is 1 or 2. Section 2.2.1 * provides more information about Packet Identifiers. + * {@link TrackableMqttInMessage#messageId} */ - int messageId; + // int messageId; + /** + * The Payload contains the Application Message that is being published. + * The content and format of the data is application specific. + * The length of the Payload can be calculated by subtracting the length of the Variable Header + * from the Remaining Length field that is in the Fixed Header. It is valid + * for a PUBLISH packet to contain a zero length Payload. + */ byte[] payload; // properties - String responseTopic; + @Nullable + String rawResponseTopicName; + @Nullable String contentType; - IntArray subscriptionIds; - - byte[] correlationData; + @Nullable + MutableIntArray subscriptionIds; - long messageExpiryInterval = MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED; - int topicAlias = MqttProperties.TOPIC_ALIAS_DEFAULT; - boolean payloadFormatIndicator = MqttProperties.PAYLOAD_FORMAT_INDICATOR_DEFAULT; + byte @Nullable [] correlationData; + long messageExpiryInterval; + int topicAlias; + PayloadFormat payloadFormat; public PublishMqttInMessage(byte info) { super(info); - this.qos = QoS.of((info >> 1) & 0x03); - this.retained = NumberUtils.isSetBit(info, 0); - this.duplicate = NumberUtils.isSetBit(info, 3); - this.topicName = EMPTY_TOPIC_NAME; - this.responseTopic = StringUtils.EMPTY; - this.contentType = StringUtils.EMPTY; - this.correlationData = ArrayUtils.EMPTY_BYTE_ARRAY; + this.qos = QoS.ofCode((info & 0b0110) >> 1); + this.retained = (info & 0b0001) != 0; + this.duplicate = (info & 0b1000) != 0; + this.rawTopicName = StringUtils.EMPTY; this.payload = ArrayUtils.EMPTY_BYTE_ARRAY; - this.subscriptionIds = IntArray.empty(); + this.messageExpiryInterval = MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED; + this.topicAlias = MqttProperties.TOPIC_ALIAS_UNDEFINED; + this.payloadFormat = PayloadFormat.UNDEFINED; } @Override @@ -292,7 +305,7 @@ public byte messageType() { @Override protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718039 - topicName = buildTopicName(readString(buffer, Integer.MAX_VALUE)); + rawTopicName = readString(buffer, Integer.MAX_VALUE); messageId = qos != QoS.AT_MOST_ONCE ? readShortUnsigned(buffer) : 0; } @@ -303,42 +316,41 @@ protected void readPayload(MqttConnection connection, ByteBuffer buffer) { } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } + public IntArray subscriptionIds() { + return subscriptionIds == null ? IntArray.empty() : subscriptionIds; + } + @Override - protected void applyProperty(PacketProperty property, long value) { + protected void applyProperty(MqttMessageProperty property, long value) { switch (property) { - case PAYLOAD_FORMAT_INDICATOR -> payloadFormatIndicator = NumberUtils.toBoolean(value); - case TOPIC_ALIAS -> topicAlias = NumberUtils.validate( - (int) value, - MqttProperties.TOPIC_ALIAS_MIN, - MqttProperties.TOPIC_ALIAS_MAX); + case PAYLOAD_FORMAT_INDICATOR -> payloadFormat = PayloadFormat.fromCode(value); + case TOPIC_ALIAS -> topicAlias = Math.toIntExact(value); case MESSAGE_EXPIRY_INTERVAL -> messageExpiryInterval = value; case SUBSCRIPTION_IDENTIFIER -> { - if (subscriptionIds == IntArray.empty()) { + if (subscriptionIds == null) { subscriptionIds = ArrayFactory.mutableIntArray(); } - if (subscriptionIds instanceof MutableIntArray array) { - array.add((int) value); - } + subscriptionIds.add((int) value); } default -> unexpectedProperty(property); } } @Override - protected void applyProperty(PacketProperty property, String value) { + protected void applyProperty(MqttMessageProperty property, String value) { switch (property) { - case RESPONSE_TOPIC -> responseTopic = value; + case RESPONSE_TOPIC -> rawResponseTopicName = value; case CONTENT_TYPE -> contentType = value; default -> unexpectedProperty(property); } } @Override - protected void applyProperty(PacketProperty property, byte[] value) { + protected void applyProperty(MqttMessageProperty property, byte[] value) { switch (property) { case CORRELATION_DATA -> correlationData = value; default -> unexpectedProperty(property); 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 20d46176..3367ecb2 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 @@ -4,11 +4,11 @@ import java.util.EnumSet; import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttVersion; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; import javasabr.mqtt.network.MqttConnection; -import javasabr.mqtt.network.message.HasMessageId; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; @@ -22,7 +22,7 @@ @Getter @Accessors(fluent = true, chain = false) @FieldDefaults(level = AccessLevel.PRIVATE) -public class PublishReceivedMqttInMessage extends MqttInMessage implements HasMessageId { +public class PublishReceivedMqttInMessage extends MqttInMessage implements TrackableMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.PUBLISH_RECEIVED.ordinal(); @@ -30,7 +30,7 @@ public class PublishReceivedMqttInMessage extends MqttInMessage implements HasMe DebugUtils.registerIncludedFields("reasonCode", "messageId"); } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the @@ -41,7 +41,7 @@ public class PublishReceivedMqttInMessage extends MqttInMessage implements HasMe specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the @@ -49,7 +49,7 @@ public class PublishReceivedMqttInMessage extends MqttInMessage implements HasMe Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); PublishReceivedReasonCode reasonCode; int messageId; @@ -88,12 +88,12 @@ public byte messageType() { } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } @Override - protected void applyProperty(PacketProperty property, String value) { + protected void applyProperty(MqttMessageProperty property, String value) { switch (property) { case REASON_STRING -> reason = value; default -> unexpectedProperty(property); 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 544498bb..081f48ef 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 @@ -4,11 +4,11 @@ import java.util.EnumSet; import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttVersion; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode; import javasabr.mqtt.network.MqttConnection; -import javasabr.mqtt.network.message.HasMessageId; import javasabr.mqtt.network.message.MqttMessageType; import lombok.Getter; import lombok.experimental.Accessors; @@ -18,7 +18,7 @@ */ @Getter @Accessors(fluent = true, chain = false) -public class PublishReleaseMqttInMessage extends MqttInMessage implements HasMessageId { +public class PublishReleaseMqttInMessage extends MqttInMessage implements TrackableMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.PUBLISH_RELEASED.ordinal(); @@ -26,7 +26,7 @@ public class PublishReleaseMqttInMessage extends MqttInMessage implements HasMes DebugUtils.registerIncludedFields("reasonCode", "messageId"); } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the @@ -37,7 +37,7 @@ public class PublishReleaseMqttInMessage extends MqttInMessage implements HasMes specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the @@ -45,7 +45,7 @@ public class PublishReleaseMqttInMessage extends MqttInMessage implements HasMes Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); PublishReleaseReasonCode reasonCode; int messageId; @@ -84,12 +84,12 @@ protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer bu } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } @Override - protected void applyProperty(PacketProperty property, String value) { + protected void applyProperty(MqttMessageProperty property, String value) { switch (property) { case REASON_STRING -> reason = value; default -> unexpectedProperty(property); diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessage.java index 0d55692a..868f2058 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeAckMqttInMessage.java @@ -3,7 +3,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; @@ -27,7 +27,7 @@ public class SubscribeAckMqttInMessage extends MqttInMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.SUBSCRIBE_ACK.ordinal(); - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the @@ -37,7 +37,7 @@ public class SubscribeAckMqttInMessage extends MqttInMessage { Property if it would increase the size of the SUBACK packet beyond the Maximum Packet Size specified by the Client */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information. The Server MUST NOT send this property if it would increase the size of the SUBACK packet @@ -45,7 +45,7 @@ public class SubscribeAckMqttInMessage extends MqttInMessage { appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); @Nullable MutableArray reasonCodes; @@ -79,7 +79,7 @@ protected void readPayload(MqttConnection connection, ByteBuffer buffer) { reasonCodes = ArrayFactory.mutableArray(SubscribeAckReasonCode.class, buffer.remaining()); while (buffer.hasRemaining()) { - reasonCodes.add(SubscribeAckReasonCode.of(readByteUnsigned(buffer))); + reasonCodes.add(SubscribeAckReasonCode.ofCode(readByteUnsigned(buffer))); } } @@ -88,12 +88,12 @@ public Array reasonCodes() { } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } @Override - protected void applyProperty(PacketProperty property, String value) { + protected void applyProperty(MqttMessageProperty property, String value) { switch (property) { case REASON_STRING -> reason = value; default -> unexpectedProperty(property); diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java index d95c1fac..97ed1ca6 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/SubscribeMqttInMessage.java @@ -1,22 +1,22 @@ package javasabr.mqtt.network.message.in; -import static javasabr.mqtt.model.util.TopicUtils.buildTopicFilter; - import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.MqttVersion; -import javasabr.mqtt.model.PacketProperty; import javasabr.mqtt.model.QoS; import javasabr.mqtt.model.SubscribeRetainHandling; -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.model.exception.MalformedProtocolMqttException; +import javasabr.mqtt.model.subscribtion.RequestedSubscription; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; +import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.MutableArray; -import javasabr.rlib.common.util.NumberUtils; import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; @@ -28,15 +28,15 @@ @Getter @Accessors(fluent = true) @FieldDefaults(level = AccessLevel.PRIVATE) -public class SubscribeMqttInMessage extends MqttInMessage { +public class SubscribeMqttInMessage extends TrackableMqttInMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.SUBSCRIBE.ordinal(); static { - DebugUtils.registerIncludedFields("messageId", "topicFilters"); + DebugUtils.registerIncludedFields("subscriptions"); } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by a Variable Byte Integer representing the identifier of the subscription. The Subscription Identifier can have the value of 1 to 268,435,455. It is a Protocol Error if the Subscription Identifier has a @@ -44,25 +44,23 @@ public class SubscribeMqttInMessage extends MqttInMessage { The Subscription Identifier is associated with any subscription created or modified as the result of this SUBSCRIBE packet. If there is a Subscription Identifier, it is stored with the subscription. If this - property is - not specified, then the absence of a Subscription Identifier is stored with the subscription. + property is not specified, then the absence of a Subscription Identifier is stored with the subscription. */ - PacketProperty.SUBSCRIPTION_IDENTIFIER, + MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, /* The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); - MutableArray topicFilters; - int messageId; + final MutableArray subscriptions; // properties int subscriptionId; public SubscribeMqttInMessage(byte info) { super(info); - this.topicFilters = ArrayFactory.mutableArray(SubscribeTopicFilter.class); + this.subscriptions = ArrayFactory.mutableArray(RequestedSubscription.class); this.subscriptionId = MqttProperties.SUBSCRIPTION_ID_UNDEFINED; } @@ -71,52 +69,74 @@ public byte messageType() { return MESSAGE_TYPE; } - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718065 - messageId = readShortUnsigned(buffer); - } - @Override protected void readPayload(MqttConnection connection, ByteBuffer buffer) { - if (buffer.remaining() < 1) { - throw new IllegalStateException("No any topic filters."); + throw new MalformedProtocolMqttException("No any topic filters"); } + MqttServerConnectionConfig severConnConfig = connection.serverConnectionConfig(); boolean isMqtt5 = connection.isSupported(MqttVersion.MQTT_5); // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718066 // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901168 while (buffer.hasRemaining()) { + String topicFilter = readString(buffer, severConnConfig.maxStringLength()); - String topicFilter = readString(buffer, Integer.MAX_VALUE); int options = readByteUnsigned(buffer); + int qosLevel = options & 0b0000_0011; + boolean noLocal = true; + boolean retainAsPublished = true; + SubscribeRetainHandling retainHandling = SubscribeRetainHandling.SEND; + + if (isMqtt5) { + noLocal = (options & 0b0000_0100) != 0; + retainAsPublished = (options & 0b0000_1000) != 0; + int retainLevel = (options & 0b0011_0000) >> 4; + retainHandling = SubscribeRetainHandling.of(retainLevel); + } else { + validateMqtt311Options(options); + } - QoS qos = QoS.of(options & 0x03); - SubscribeRetainHandling retainHandling = isMqtt5 ? SubscribeRetainHandling.of((options >> 4) & 0x03) : SubscribeRetainHandling.SEND; - + QoS qos = QoS.ofCode(qosLevel); if (qos == QoS.INVALID || retainHandling == SubscribeRetainHandling.INVALID) { - throw new IllegalStateException("Unsupported qos or retain handling"); + throw new MalformedProtocolMqttException("Unsupported qos or retain handling"); } - boolean noLocal = !isMqtt5 || NumberUtils.isSetBit(options, 2); - boolean rap = !isMqtt5 || NumberUtils.isSetBit(options, 3); - - topicFilters.add(new SubscribeTopicFilter(buildTopicFilter(topicFilter), qos, retainHandling, noLocal, rap)); + subscriptions.add(new RequestedSubscription( + topicFilter, + qos, + retainHandling, + noLocal, + retainAsPublished)); } } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } @Override - protected void applyProperty(PacketProperty property, long value) { + protected void applyProperty(MqttMessageProperty property, long value) { switch (property) { case SUBSCRIPTION_IDENTIFIER -> subscriptionId = (int) value; default -> unexpectedProperty(property); } } + + public Array subscriptions() { + return subscriptions; + } + + private static void validateMqtt311Options(int options) { + // for MQTT 3.1.1 these bits must be zero + if ((options & 0b0000_0100) != 0) { + throw new MalformedProtocolMqttException("No local option is not available on this protocol level"); + } else if ((options & 0b0000_1000) != 0) { + throw new MalformedProtocolMqttException("Retain as published option is not available on this protocol level"); + } else if (((options & 0b0011_0000) >> 4) != 0) { + throw new MalformedProtocolMqttException("Retain level option is not available on this protocol level"); + } + } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/TrackableMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/TrackableMqttInMessage.java new file mode 100644 index 00000000..a1928db5 --- /dev/null +++ b/network/src/main/java/javasabr/mqtt/network/message/in/TrackableMqttInMessage.java @@ -0,0 +1,32 @@ +package javasabr.mqtt.network.message.in; + +import java.nio.ByteBuffer; +import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.network.MqttConnection; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; + +@Getter +@Accessors(fluent = true, chain = false) +@FieldDefaults(level = AccessLevel.PROTECTED) +public abstract class TrackableMqttInMessage extends MqttInMessage { + + static { + DebugUtils.registerIncludedFields("messageId"); + } + + int messageId; + + public TrackableMqttInMessage(byte info) { + super(info); + this.messageId = MqttProperties.MESSAGE_ID_UNDEFINED; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + messageId = readShortUnsigned(buffer); + } +} diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessage.java index 91b38a87..c84f10ab 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessage.java @@ -3,8 +3,8 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttVersion; -import javasabr.mqtt.model.PacketProperty; import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; @@ -28,7 +28,7 @@ public class UnsubscribeAckMqttInMessage extends MqttInMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.UNSUBSCRIBE_ACK.ordinal(); - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the @@ -39,7 +39,7 @@ public class UnsubscribeAckMqttInMessage extends MqttInMessage { specified by the Client [MQTT-3.11.2-1]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information. The Server MUST NOT send this property if it would increase the size of the UNSUBACK @@ -47,7 +47,7 @@ public class UnsubscribeAckMqttInMessage extends MqttInMessage { allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); private static final Array EMPTY_REASON_CODES = Array.empty(UnsubscribeAckReasonCode.class); @@ -90,7 +90,7 @@ protected void readPayload(MqttConnection connection, ByteBuffer buffer) { } @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } @@ -99,7 +99,7 @@ public Array reasonCodes() { } @Override - protected void applyProperty(PacketProperty property, String value) { + protected void applyProperty(MqttMessageProperty property, String value) { switch (property) { case REASON_STRING -> reason = value; default -> unexpectedProperty(property); diff --git a/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java b/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java index abc3b179..7b7e4ff8 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessage.java @@ -1,20 +1,21 @@ package javasabr.mqtt.network.message.in; -import static javasabr.mqtt.model.util.TopicUtils.buildTopicFilter; - import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; -import javasabr.mqtt.model.PacketProperty; -import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttMessageProperty; +import javasabr.mqtt.model.exception.MalformedProtocolMqttException; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; +import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.MutableArray; import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; /** * Unsubscribe request. @@ -22,19 +23,23 @@ @Getter @Accessors(fluent = true) @FieldDefaults(level = AccessLevel.PRIVATE) -public class UnsubscribeMqttInMessage extends MqttInMessage { +public class UnsubscribeMqttInMessage extends TrackableMqttInMessage { + + static { + DebugUtils.registerIncludedFields("rawTopicFilters"); + } private static final byte MESSAGE_TYPE = (byte) MqttMessageType.UNSUBSCRIBE.ordinal(); - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); - MutableArray topicFilters; - int messageId; + @Nullable + MutableArray rawTopicFilters; public UnsubscribeMqttInMessage(byte info) { super(info); @@ -45,25 +50,23 @@ public byte messageType() { return MESSAGE_TYPE; } - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - messageId = readShortUnsigned(buffer); - } - @Override protected void readPayload(MqttConnection connection, ByteBuffer buffer) { if (buffer.remaining() < 1) { - throw new IllegalStateException("No any topic filters."); + throw new MalformedProtocolMqttException("No any topic filters."); } - - topicFilters = ArrayFactory.mutableArray(TopicFilter.class); + rawTopicFilters = ArrayFactory.mutableArray(String.class); while (buffer.hasRemaining()) { - topicFilters.add(buildTopicFilter(readString(buffer, Integer.MAX_VALUE))); + rawTopicFilters.add(readString(buffer, Integer.MAX_VALUE)); } } + public Array rawTopicFilters() { + return rawTopicFilters == null ? EMPTY_STRINGS : rawTopicFilters; + } + @Override - protected Set availableProperties() { + protected Set availableProperties() { return AVAILABLE_PROPERTIES; } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/AuthenticationMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/AuthenticationMqtt5OutMessage.java index 055cc39e..ff838343 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/AuthenticationMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/AuthenticationMqtt5OutMessage.java @@ -3,7 +3,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.reason.code.AuthenticateReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -22,19 +22,19 @@ public class AuthenticationMqtt5OutMessage extends MqttOutMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.AUTHENTICATE.ordinal(); - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by a UTF-8 Encoded String containing the name of the authentication method. It is a Protocol Error to omit the Authentication Method or to include it more than once. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_METHOD, + MqttMessageProperty.AUTHENTICATION_METHOD, /* Followed by Binary Data containing authentication data. It is a Protocol Error to include Authentication Data more than once. The contents of this data are defined by the authentication method. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_DATA, + MqttMessageProperty.AUTHENTICATION_DATA, /* Followed by the UTF-8 Encoded String representing the reason for the disconnect. This Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the receiver. @@ -43,7 +43,7 @@ public class AuthenticationMqtt5OutMessage extends MqttOutMessage { Maximum Packet Size specified by the receiver [MQTT-3.15.2-2]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other information. The sender MUST NOT send this property if it would increase the size of the AUTH packet @@ -51,7 +51,7 @@ public class AuthenticationMqtt5OutMessage extends MqttOutMessage { allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); Array userProperties; @@ -81,9 +81,9 @@ protected boolean isPropertiesSupported(MqttConnection connection) { @Override protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901221 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); - writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_METHOD, authenticateMethod); - writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_DATA, authenticateData); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, MqttMessageProperty.REASON_STRING, reason); + writeNotEmptyProperty(buffer, MqttMessageProperty.AUTHENTICATION_METHOD, authenticateMethod); + writeNotEmptyProperty(buffer, MqttMessageProperty.AUTHENTICATION_DATA, authenticateData); } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessage.java index 9c295258..3dd52a43 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessage.java @@ -5,8 +5,9 @@ import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; import javasabr.mqtt.model.MqttClientConnectionConfig; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttProperties; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -24,7 +25,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { DebugUtils.registerIncludedFields("reasonCode", "sessionPresent", "clientId"); } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the Four Byte Integer representing the Session Expiry Interval in seconds. It is a Protocol Error to include the Session Expiry Interval more than once. @@ -32,7 +33,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { If the Session Expiry Interval is absent the value in the CONNECT Packet used. The server uses this property to inform the Client that it is using a value other than that sent by the Client in the CONNACK. */ - PacketProperty.SESSION_EXPIRY_INTERVAL, + MqttMessageProperty.SESSION_EXPIRY_INTERVAL, /* Followed by the Two Byte Integer representing the Receive Maximum value. It is a Protocol Error to include the Receive Maximum value more than once or for it to have the value 0. @@ -43,7 +44,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { If the Receive Maximum value is absent, then its value defaults to 65,535. */ - PacketProperty.RECEIVE_MAXIMUM_PUBLISH, + MqttMessageProperty.RECEIVE_MAXIMUM_PUBLISHES, /* Followed by a Byte with a value of either 0 or 1. It is a Protocol Error to include Maximum QoS more than once, or to have a value other than 0 or 1. If the Maximum QoS is absent, the Client uses a Maximum @@ -64,7 +65,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { reject the connection. It SHOULD use a CONNACK packet with Reason Code 0x9B (QoS not supported) as described in section 4.13 Handling errors, and MUST close the Network Connection */ - PacketProperty.MAXIMUM_QOS, + MqttMessageProperty.MAXIMUM_QOS, /* Followed by a Byte field. If present, this byte declares whether the Server supports retained messages. A value of 0 means that retained messages are not supported. A value of 1 means retained messages are @@ -81,7 +82,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { Server SHOULD send a DISCONNECT with Reason Code of 0x9A (Retain not supported) as described in section 4.13. */ - PacketProperty.RETAIN_AVAILABLE, + MqttMessageProperty.RETAIN_AVAILABLE, /* Followed by a Four Byte Integer representing the Maximum Packet Size the Server is willing to accept. If the Maximum Packet Size is not present, there is no limit on the packet size imposed beyond the @@ -98,7 +99,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { a Server receives a packet whose size exceeds this limit, this is a Protocol Error, the Server uses DISCONNECT with Reason Code 0x95 (Packet too large), as described in section 4.13. */ - PacketProperty.MAXIMUM_PACKET_SIZE, + MqttMessageProperty.MAXIMUM_MESSAGE_SIZE, /* Followed by the UTF-8 string which is the Assigned Client Identifier. It is a Protocol Error to include the Assigned Client Identifier more than once. @@ -110,7 +111,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { containing an Assigned Client Identifier. The Assigned Client Identifier MUST be a new Client Identifier not used by any other Session currently in the Server [ */ - PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, + MqttMessageProperty.ASSIGNED_CLIENT_IDENTIFIER, /* Followed by the Two Byte Integer representing the Topic Alias Maximum value. It is a Protocol Error to include the Topic Alias Maximum value more than once. If the Topic Alias Maximum property is absent, @@ -123,7 +124,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { on this connection. If Topic Alias Maximum is absent or 0, the Client MUST NOT send any Topic Aliases on to the Server */ - PacketProperty.TOPIC_ALIAS_MAXIMUM, + MqttMessageProperty.TOPIC_ALIAS_MAXIMUM, /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the @@ -133,7 +134,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { property if it would increase the size of the CONNACK packet beyond the Maximum Packet Size specified by the Client [MQTT-3.2.2-19]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by a UTF-8 String Pair. This property can be used to provide additional information to the Client including diagnostic information. The Server MUST NOT send this property if it would increase the size of @@ -144,7 +145,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { The content and meaning of this property is not defined by this specification. The receiver of a CONNACK containing this property MAY ignore it. */ - PacketProperty.USER_PROPERTY, + MqttMessageProperty.USER_PROPERTY, /* Followed by a Byte field. If present, this byte declares whether the Server supports Wildcard Subscriptions. A value is 0 means that Wildcard Subscriptions are not supported. A value of 1 means @@ -162,7 +163,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { Wildcard Subscription. In this case the Server MAY send a SUBACK Control Packet with a Reason Code 0xA2 (Wildcard Subscriptions not supported). */ - PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, + MqttMessageProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, /* Followed by a Byte field. If present, this byte declares whether the Server supports Subscription Identifiers. A value is 0 means that Subscription Identifiers are not supported. A value of 1 means @@ -174,7 +175,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { Subscription Identifiers, this is a Protocol Error. The Server uses DISCONNECT with Reason Code of 0xA1 (Subscription Identifiers not supported) as described in section 4.13. */ - PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, + MqttMessageProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, /* Followed by a Byte field. If present, this byte declares whether the Server supports Shared Subscriptions. A value is 0 means that Shared Subscriptions are not supported. A value of 1 means Shared @@ -185,7 +186,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { Shared Subscriptions, this is a Protocol Error. The Server uses DISCONNECT with Reason Code 0x9E (Shared Subscriptions not supported) as described in section 4.13. */ - PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, + MqttMessageProperty.SHARED_SUBSCRIPTION_AVAILABLE, /* Followed by a Two Byte Integer with the Keep Alive time assigned by the Server. If the Server sends a Server Keep Alive on the CONNACK packet, the Client MUST use this value instead of the Keep Alive @@ -193,7 +194,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { the Server MUST use the Keep Alive value set by the Client on CONNECT [MQTT-3.2.2-22]. It is a Protocol Error to include the Server Keep Alive more than once. */ - PacketProperty.SERVER_KEEP_ALIVE, + MqttMessageProperty.SERVER_KEEP_ALIVE, /* Followed by a UTF-8 Encoded String which is used as the basis for creating a Response Topic. The way in which the Client creates a Response Topic from the Response Information is not defined by this @@ -202,7 +203,7 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { If the Client sends a Request Response Information with a value 1, it is OPTIONAL for the Server to send the Response Information in the CONNACK. */ - PacketProperty.RESPONSE_INFORMATION, + MqttMessageProperty.RESPONSE_INFORMATION, /* Followed by a UTF-8 Encoded String which can be used by the Client to identify another Server to use. It is a Protocol Error to include the Server Reference more than once. @@ -212,20 +213,20 @@ public class ConnectAckMqtt5OutMessage extends ConnectAckMqtt311OutMessage { Refer to section 4.11 Server redirection for information about how Server Reference is used */ - PacketProperty.SERVER_REFERENCE, + MqttMessageProperty.SERVER_REFERENCE, /* Followed by a UTF-8 Encoded String containing the name of the authentication method. It is a Protocol Error to include the Authentication Method more than once. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_METHOD, + MqttMessageProperty.AUTHENTICATION_METHOD, /* Followed by Binary Data containing authentication data. The contents of this data are defined by the authentication method and the state of already exchanged authentication data. It is a Protocol Error to include the Authentication Data more than once. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_DATA); + MqttMessageProperty.AUTHENTICATION_DATA); String clientId; @@ -294,55 +295,56 @@ protected boolean isPropertiesSupported(MqttConnection connection) { @Override protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { + MqttServerConnectionConfig serverConfig = connectionConfig.server(); // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901080 - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); - writeNotEmptyProperty(buffer, PacketProperty.RESPONSE_INFORMATION, responseInformation); - writeNotEmptyProperty(buffer, PacketProperty.SERVER_REFERENCE, serverReference); - writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_METHOD, authenticationMethod); - writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_DATA, authenticationData); - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, MqttMessageProperty.REASON_STRING, reason); + writeNotEmptyProperty(buffer, MqttMessageProperty.RESPONSE_INFORMATION, responseInformation); + writeNotEmptyProperty(buffer, MqttMessageProperty.SERVER_REFERENCE, serverReference); + writeNotEmptyProperty(buffer, MqttMessageProperty.AUTHENTICATION_METHOD, authenticationMethod); + writeNotEmptyProperty(buffer, MqttMessageProperty.AUTHENTICATION_DATA, authenticationData); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); writeProperty( buffer, - PacketProperty.MAXIMUM_QOS, + MqttMessageProperty.MAXIMUM_QOS, connectionConfig.maxQos().ordinal(), MqttProperties.MAXIMUM_QOS_DEFAULT.ordinal()); writeProperty( buffer, - PacketProperty.RETAIN_AVAILABLE, - connectionConfig.retainAvailable(), + MqttMessageProperty.RETAIN_AVAILABLE, + serverConfig.retainAvailable(), MqttProperties.RETAIN_AVAILABLE_DEFAULT); writeProperty( buffer, - PacketProperty.SESSION_EXPIRY_INTERVAL, + MqttMessageProperty.SESSION_EXPIRY_INTERVAL, connectionConfig.sessionExpiryInterval(), requestedSessionExpiryInterval); - writeProperty(buffer, PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, clientId, requestedClientId); - writeProperty(buffer, PacketProperty.RECEIVE_MAXIMUM_PUBLISH, connectionConfig.receiveMaxPublishes(), requestedReceiveMax); + writeProperty(buffer, MqttMessageProperty.ASSIGNED_CLIENT_IDENTIFIER, clientId, requestedClientId); + writeProperty(buffer, MqttMessageProperty.RECEIVE_MAXIMUM_PUBLISHES, connectionConfig.receiveMaxPublishes(), requestedReceiveMax); writeProperty( buffer, - PacketProperty.MAXIMUM_PACKET_SIZE, - connectionConfig.maxPacketSize(), - MqttProperties.MAXIMUM_PACKET_SIZE_MAX); + MqttMessageProperty.MAXIMUM_MESSAGE_SIZE, + connectionConfig.maxMessageSize(), + MqttProperties.MAXIMUM_MESSAGE_SIZE_MAX); writeProperty( buffer, - PacketProperty.TOPIC_ALIAS_MAXIMUM, + MqttMessageProperty.TOPIC_ALIAS_MAXIMUM, connectionConfig.topicAliasMaxValue(), MqttProperties.TOPIC_ALIAS_MAXIMUM_DISABLED); writeProperty( buffer, - PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, - connectionConfig.wildcardSubscriptionAvailable(), + MqttMessageProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, + serverConfig.wildcardSubscriptionAvailable(), MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT); writeProperty( buffer, - PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, - connectionConfig.subscriptionIdAvailable(), + MqttMessageProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, + serverConfig.subscriptionIdAvailable(), MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT); writeProperty( buffer, - PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, - connectionConfig.sharedSubscriptionAvailable(), + MqttMessageProperty.SHARED_SUBSCRIPTION_AVAILABLE, + serverConfig.sharedSubscriptionAvailable(), MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT); - writeProperty(buffer, PacketProperty.SERVER_KEEP_ALIVE, connectionConfig.keepAlive(), requestedKeepAlive); + writeProperty(buffer, MqttMessageProperty.SERVER_KEEP_ALIVE, connectionConfig.keepAlive(), requestedKeepAlive); } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/ConnectMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/ConnectMqtt5OutMessage.java index 471198ff..1277c38f 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/ConnectMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/ConnectMqtt5OutMessage.java @@ -3,9 +3,9 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttProperties; import javasabr.mqtt.model.MqttVersion; -import javasabr.mqtt.model.PacketProperty; import javasabr.mqtt.model.QoS; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.network.MqttConnection; @@ -22,7 +22,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* If the Session Expiry Interval is absent the value 0 is used. If it is set to 0, or is absent, the Session ends when the Network Connection is closed. @@ -31,7 +31,7 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { The Client and Server MUST store the Session State after the Network Connection is closed if the Session Expiry Interval is greater than 0 */ - PacketProperty.SESSION_EXPIRY_INTERVAL, + MqttMessageProperty.SESSION_EXPIRY_INTERVAL, /* Followed by the Two Byte Integer representing the Receive Maximum value. It is a Protocol Error to include the Receive Maximum value more than once or for it to have the value 0. @@ -42,7 +42,7 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { The value of Receive Maximum applies only to the current Network Connection. If the Receive Maximum value is absent then its value defaults to 65,535. */ - PacketProperty.RECEIVE_MAXIMUM_PUBLISH, + MqttMessageProperty.RECEIVE_MAXIMUM_PUBLISHES, /* Followed by a Four Byte Integer representing the Maximum Packet Size the Client is willing to accept. If the Maximum Packet Size is not present, no limit on the packet size is imposed beyond the limitations in @@ -51,7 +51,7 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { It is a Protocol Error to include the Maximum Packet Size more than once, or for the value to be set to zero. */ - PacketProperty.MAXIMUM_PACKET_SIZE, + MqttMessageProperty.MAXIMUM_MESSAGE_SIZE, /* Followed by the Two Byte Integer representing the Topic Alias Maximum value. It is a Protocol Error to include the Topic Alias Maximum value more than once. If the Topic Alias Maximum property is absent, @@ -64,7 +64,7 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { this connection. If Topic Alias Maximum is absent or zero, the Server MUST NOT send any Topic Aliases to the Client [MQTT-3.1.2-27]. */ - PacketProperty.TOPIC_ALIAS_MAXIMUM, + MqttMessageProperty.TOPIC_ALIAS_MAXIMUM, /* Followed by a Byte with a value of either 0 or 1. It is Protocol Error to include the Request Response Information more than once, or to have a value other than 0 or 1. If the Request Response Information is @@ -74,7 +74,7 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { value of 0 indicates that the Server MUST NOT return Response Information [MQTT-3.1.2-28]. If the value is 1 the Server MAY return Response Information in the CONNACK packet. */ - PacketProperty.REQUEST_RESPONSE_INFORMATION, + MqttMessageProperty.REQUEST_RESPONSE_INFORMATION, /* Followed by a Byte with a value of either 0 or 1. It is a Protocol Error to include Request Problem Information more than once, or to have a value other than 0 or 1. If the Request Problem Information is @@ -93,12 +93,12 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { If this value is 1, the Server MAY return a Reason String or User Properties on any packet where it is allowed. */ - PacketProperty.REQUEST_PROBLEM_INFORMATION, + MqttMessageProperty.REQUEST_PROBLEM_INFORMATION, /* The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once */ - PacketProperty.USER_PROPERTY, + MqttMessageProperty.USER_PROPERTY, /* Followed by a UTF-8 Encoded String containing the name of the authentication method used for extended authentication .It is a Protocol Error to include Authentication Method more than once. @@ -107,7 +107,7 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { If a Client sets an Authentication Method in the CONNECT, the Client MUST NOT send any packets other than AUTH or DISCONNECT packets until it has received a CONNACK packet */ - PacketProperty.AUTHENTICATION_METHOD, + MqttMessageProperty.AUTHENTICATION_METHOD, /* Followed by Binary Data containing authentication data. It is a Protocol Error to include Authentication Data if there is no Authentication Method. It is a Protocol Error to include Authentication Data more than @@ -116,9 +116,9 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { The contents of this data are defined by the authentication method. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_DATA); + MqttMessageProperty.AUTHENTICATION_DATA); - private static final Set WILL_PROPERTIES = EnumSet.of( + private static final Set WILL_PROPERTIES = EnumSet.of( /* Followed by the Four Byte Integer representing the Will Delay Interval in seconds. It is a Protocol Error to include the Will Delay Interval more than once. If the Will Delay Interval is absent, the default value is 0 @@ -128,7 +128,7 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { Session ends, whichever happens first. If a new Network Connection to this Session is made before the Will Delay Interval has passed, the Server MUST NOT send the Will Message */ - PacketProperty.WILL_DELAY_INTERVAL, + MqttMessageProperty.WILL_DELAY_INTERVAL, /* Followed by the value of the Payload Format Indicator, either of: • 0 (0x00) Byte Indicates that the Will Message is unspecified bytes, which is equivalent to not @@ -140,7 +140,7 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { It is a Protocol Error to include the Payload Format Indicator more than once. The Server MAY validate that the Will Message is of the format indicated, and if it is not se */ - PacketProperty.PAYLOAD_FORMAT_INDICATOR, + MqttMessageProperty.PAYLOAD_FORMAT_INDICATOR, /* Followed by the Four Byte Integer representing the Message Expiry Interval. It is a Protocol Error to include the Message Expiry Interval more than once. @@ -150,19 +150,19 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { If absent, no Message Expiry Interval is sent when the Server publishes the Will Message. */ - PacketProperty.MESSAGE_EXPIRY_INTERVAL, + MqttMessageProperty.MESSAGE_EXPIRY_INTERVAL, /* Followed by a UTF-8 Encoded String describing the content of the Will Message. It is a Protocol Error to include the Content Type more than once. The value of the Content Type is defined by the sending and receiving application. */ - PacketProperty.CONTENT_TYPE, + MqttMessageProperty.CONTENT_TYPE, /* Followed by a UTF-8 Encoded String which is used as the Topic Name for a response message. It is a Protocol Error to include the Response Topic more than once. The presence of a Response Topic identifies the Will Message as a Request. */ - PacketProperty.RESPONSE_TOPIC, + MqttMessageProperty.RESPONSE_TOPIC, /* Followed by Binary Data. The Correlation Data is used by the sender of the Request Message to identify which request the Response Message is for when it is received. It is a Protocol Error to include @@ -172,14 +172,14 @@ public class ConnectMqtt5OutMessage extends ConnectMqtt311OutMessage { The value of the Correlation Data only has meaning to the sender of the Request Message and receiver of the Response Message. */ - PacketProperty.CORRELATION_DATA, + MqttMessageProperty.CORRELATION_DATA, /* Followed by a UTF-8 String Pair. The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. The Server MUST maintain the order of User Properties when publishing the Will Message */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); // properties Array userProperties; @@ -208,8 +208,8 @@ public ConnectMqtt5OutMessage(String clientId, int keepAlive) { StringUtils.EMPTY, ArrayUtils.EMPTY_BYTE_ARRAY, MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED, - MqttProperties.RECEIVE_MAXIMUM_UNDEFINED, - MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED, + MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_UNDEFINED, + MqttProperties.MAXIMUM_MESSAGE_SIZE_UNDEFINED, MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED, false, false); @@ -277,27 +277,27 @@ protected void appendWillProperties(ByteBuffer buffer) { @Override protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901046 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_METHOD, authenticationMethod); - writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_DATA, authenticationData); - writeProperty(buffer, PacketProperty.REQUEST_RESPONSE_INFORMATION, requestResponseInformation, false); - writeProperty(buffer, PacketProperty.REQUEST_PROBLEM_INFORMATION, requestProblemInformation, false); - writeProperty(buffer, PacketProperty.RECEIVE_MAXIMUM_PUBLISH, receiveMax, MqttProperties.RECEIVE_MAXIMUM_UNDEFINED); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, MqttMessageProperty.AUTHENTICATION_METHOD, authenticationMethod); + writeNotEmptyProperty(buffer, MqttMessageProperty.AUTHENTICATION_DATA, authenticationData); + writeProperty(buffer, MqttMessageProperty.REQUEST_RESPONSE_INFORMATION, requestResponseInformation, false); + writeProperty(buffer, MqttMessageProperty.REQUEST_PROBLEM_INFORMATION, requestProblemInformation, false); + writeProperty(buffer, MqttMessageProperty.RECEIVE_MAXIMUM_PUBLISHES, receiveMax, MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_UNDEFINED); writeProperty( buffer, - PacketProperty.TOPIC_ALIAS_MAXIMUM, + MqttMessageProperty.TOPIC_ALIAS_MAXIMUM, topicAliasMaximum, MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED); writeProperty( buffer, - PacketProperty.SESSION_EXPIRY_INTERVAL, + MqttMessageProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval, MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED); writeProperty( buffer, - PacketProperty.MAXIMUM_PACKET_SIZE, + MqttMessageProperty.MAXIMUM_MESSAGE_SIZE, maximumPacketSize, - MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED); + MqttProperties.MAXIMUM_MESSAGE_SIZE_UNDEFINED); } protected void writeWillProperties(ByteBuffer buffer) {} diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/DisconnectMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/DisconnectMqtt5OutMessage.java index cecfccb7..9ce7b45e 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/DisconnectMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/DisconnectMqtt5OutMessage.java @@ -3,8 +3,8 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttProperties; -import javasabr.mqtt.model.PacketProperty; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.reason.code.DisconnectReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -20,7 +20,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class DisconnectMqtt5OutMessage extends DisconnectMqtt311OutMessage { - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* If the Session Expiry Interval is absent, the Session Expiry Interval in the CONNECT packet is used. @@ -31,13 +31,13 @@ public class DisconnectMqtt5OutMessage extends DisconnectMqtt311OutMessage { Expiry Interval is received by the Server, it does not treat it as a valid DISCONNECT packet. The Server uses DISCONNECT with Reason Code 0x82 (Protocol Error) as described in */ - PacketProperty.SESSION_EXPIRY_INTERVAL, + MqttMessageProperty.SESSION_EXPIRY_INTERVAL, /* The sender MUST NOT send this Property if it would increase the size of the DISCONNECT packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.14.2-3]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other information. The sender MUST NOT send this property if it would increase the size of the DISCONNECT @@ -45,12 +45,12 @@ public class DisconnectMqtt5OutMessage extends DisconnectMqtt311OutMessage { allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY, + MqttMessageProperty.USER_PROPERTY, /* The Server sends DISCONNECT including a Server Reference and Reason Code {0x9C (Use another 2601 server)} or 0x9D (Server moved) as described in section 4.13. */ - PacketProperty.SERVER_REFERENCE); + MqttMessageProperty.SERVER_REFERENCE); DisconnectReasonCode reasonCode; Array userProperties; @@ -63,7 +63,7 @@ public class DisconnectMqtt5OutMessage extends DisconnectMqtt311OutMessage { @Override protected void writeVariableHeader(MqttConnection connection, ByteBuffer buffer) { // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901207 - writeByte(buffer, reasonCode.getValue()); + writeByte(buffer, reasonCode.code()); } @Override @@ -74,14 +74,14 @@ protected boolean isPropertiesSupported(MqttConnection connection) { @Override protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901209 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); - writeNotEmptyProperty(buffer, PacketProperty.SERVER_REFERENCE, serverReference); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, MqttMessageProperty.REASON_STRING, reason); + writeNotEmptyProperty(buffer, MqttMessageProperty.SERVER_REFERENCE, serverReference); if (sessionExpiryInterval != MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED) { writeProperty( buffer, - PacketProperty.SESSION_EXPIRY_INTERVAL, + MqttMessageProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval, MqttProperties.SESSION_EXPIRY_INTERVAL_DEFAULT); } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java index 0efafad5..66257f87 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/MqttOutMessage.java @@ -3,7 +3,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import javasabr.mqtt.base.util.DebugUtils; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.util.MqttDataUtils; @@ -11,10 +11,13 @@ import javasabr.rlib.common.util.NumberUtils; import javasabr.rlib.network.packet.impl.AbstractWritableNetworkPacket; import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.Nullable; @RequiredArgsConstructor public abstract class MqttOutMessage extends AbstractWritableNetworkPacket { + protected static final Array EMPTY_USER_PROPERTIES = Array.empty(StringPair.class); + private static final ThreadLocal LOCAL_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocate( 1024 * 1024)); @@ -75,23 +78,23 @@ private void appendProperties(MqttConnection connection, ByteBuffer buffer) { .put(propertiesBuffer); } - public void writeProperty(ByteBuffer buffer, PacketProperty property, boolean value) { + public void writeProperty(ByteBuffer buffer, MqttMessageProperty property, boolean value) { writeProperty(buffer, property, value ? 1 : 0); } - public void writeProperty(ByteBuffer buffer, PacketProperty property, boolean value, boolean def) { + public void writeProperty(ByteBuffer buffer, MqttMessageProperty property, boolean value, boolean def) { if (value != def) { writeProperty(buffer, property, value ? 1 : 0); } } - public void writeProperty(ByteBuffer buffer, PacketProperty property, long value, long def) { + public void writeProperty(ByteBuffer buffer, MqttMessageProperty property, long value, long def) { if (value != def) { writeProperty(buffer, property, value); } } - public void writeProperty(ByteBuffer buffer, PacketProperty property, long value) { + public void writeProperty(ByteBuffer buffer, MqttMessageProperty property, long value) { buffer.put(property.id()); switch (property.dataType()) { case BYTE -> writeByte(buffer, (int) value); @@ -104,7 +107,7 @@ public void writeProperty(ByteBuffer buffer, PacketProperty property, long value public void writeProperty( ByteBuffer buffer, - PacketProperty property, + MqttMessageProperty property, String value, String def) { if (!def.equals(value)) { @@ -112,35 +115,36 @@ public void writeProperty( } } - public void writeProperty(ByteBuffer buffer, PacketProperty property, StringPair value) { + public void writeProperty(ByteBuffer buffer, MqttMessageProperty property, StringPair value) { buffer.put(property.id()); writeString(buffer, value.name()); writeString(buffer, value.value()); } - public void writeNotEmptyProperty(ByteBuffer buffer, PacketProperty property, String value) { - if (!value.isEmpty()) { + public void writeNotEmptyProperty(ByteBuffer buffer, MqttMessageProperty property, @Nullable String value) { + if (value != null && !value.isEmpty()) { writeProperty(buffer, property, value); } } - public void writeNotEmptyProperty(ByteBuffer buffer, PacketProperty property, byte[] value) { - if (value.length > 0) { + + public void writeNotEmptyProperty(ByteBuffer buffer, MqttMessageProperty property, byte @Nullable [] value) { + if (value != null && value.length > 0) { writeProperty(buffer, property, value); } } - public void writeProperty(ByteBuffer buffer, PacketProperty property, String value) { + public void writeProperty(ByteBuffer buffer, MqttMessageProperty property, String value) { buffer.put(property.id()); writeString(buffer, value); } - public void writeProperty(ByteBuffer buffer, PacketProperty property, byte[] value) { + public void writeProperty(ByteBuffer buffer, MqttMessageProperty property, byte[] value) { buffer.put(property.id()); writeBytes(buffer, value); } - public void writeStringPairProperties(ByteBuffer buffer, PacketProperty property, Array pairs) { + public void writeStringPairProperties(ByteBuffer buffer, MqttMessageProperty property, Array pairs) { if (pairs.isEmpty()) { return; } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishAckMqtt311OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishAckMqtt311OutMessage.java index 78bf688e..6d47513c 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishAckMqtt311OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishAckMqtt311OutMessage.java @@ -1,25 +1,27 @@ package javasabr.mqtt.network.message.out; -import java.nio.ByteBuffer; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; /** * Publish acknowledgement. */ -@RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public class PublishAckMqtt311OutMessage extends MqttOutMessage { +public class PublishAckMqtt311OutMessage extends TrackableMqttOutMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.PUBLISH_ACK.ordinal(); + public PublishAckMqtt311OutMessage(int messageId) { + super(messageId); + } + /** * Packet Identifier from the PUBLISH packet that is being acknowledged. + * {@link TrackableMqttOutMessage#messageId} */ - int messageId; + //int messageId; @Override public int expectedLength(MqttConnection connection) { @@ -30,10 +32,4 @@ public int expectedLength(MqttConnection connection) { protected byte messageType() { return MESSAGE_TYPE; } - - @Override - protected void writeVariableHeader(MqttConnection connection, ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718045 - buffer.putShort((short) messageId); - } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishAckMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishAckMqtt5OutMessage.java index e378d3d0..de58e1e1 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishAckMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishAckMqtt5OutMessage.java @@ -3,7 +3,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.reason.code.PublishAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -17,7 +17,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class PublishAckMqtt5OutMessage extends PublishAckMqtt311OutMessage { - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is a human readable string designed for diagnostics and is not intended to be parsed by @@ -28,7 +28,7 @@ public class PublishAckMqtt5OutMessage extends PublishAckMqtt311OutMessage { specified by the receiver [MQTT-3.4.2-2]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information. The sender MUST NOT send this property if it would increase the size of the PUBACK @@ -36,7 +36,7 @@ public class PublishAckMqtt5OutMessage extends PublishAckMqtt311OutMessage { allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); Array userProperties; String reason; @@ -68,7 +68,7 @@ protected void writeVariableHeader(MqttConnection connection, ByteBuffer buffer) @Override protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901125 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, MqttMessageProperty.REASON_STRING, reason); } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishCompleteMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishCompleteMqtt5OutMessage.java index 91ca265f..8233242d 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishCompleteMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishCompleteMqtt5OutMessage.java @@ -3,7 +3,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -18,7 +18,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class PublishCompleteMqtt5OutMessage extends PublishCompleteMqtt311OutMessage { - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the @@ -29,7 +29,7 @@ public class PublishCompleteMqtt5OutMessage extends PublishCompleteMqtt311OutMes specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the @@ -37,7 +37,7 @@ public class PublishCompleteMqtt5OutMessage extends PublishCompleteMqtt311OutMes Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); Array userProperties; PublishCompletedReasonCode reasonCode; @@ -79,7 +79,7 @@ protected boolean isPropertiesSupported(MqttConnection connection) { protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { super.writeProperties(connection, buffer); // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901155 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, MqttMessageProperty.REASON_STRING, reason); } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqtt5OutMessage.java index 7fcebcb0..bc057671 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqtt5OutMessage.java @@ -4,14 +4,15 @@ import java.util.EnumSet; import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttProperties; -import javasabr.mqtt.model.PacketProperty; import javasabr.mqtt.model.QoS; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.network.MqttConnection; import javasabr.rlib.collections.array.Array; import lombok.AccessLevel; import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class PublishMqtt5OutMessage extends PublishMqtt311OutMessage { @@ -20,7 +21,7 @@ public class PublishMqtt5OutMessage extends PublishMqtt311OutMessage { DebugUtils.registerIncludedFields("qos", "topicName", "duplicate"); } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the value of the Payload Forma t Indicator, either of: · 0 (0x00) Byte Indicates that the Payload is unspecified bytes, which is equivalent to not sending a @@ -34,7 +35,7 @@ public class PublishMqtt5OutMessage extends PublishMqtt311OutMessage { PUBACK, PUBREC, or DISCONNECT with Reason Code of 0x99 (Payload format invalid) as described in section 4.13. Refer to section 5.4.9 for information about security issues in validating the payload format. */ - PacketProperty.PAYLOAD_FORMAT_INDICATOR, + MqttMessageProperty.PAYLOAD_FORMAT_INDICATOR, /* Followed by the Four Byte Integer representing the Message Expiry Interval. If present, the Four Byte value is the lifetime of the Application Message in seconds. If the Message Expiry @@ -47,7 +48,7 @@ public class PublishMqtt5OutMessage extends PublishMqtt311OutMessage { value minus the time that the Application Message has been waiting in the Server [MQTT-3.3.2-6]. Refer to section 4.1 for details and limitations of stored state. */ - PacketProperty.MESSAGE_EXPIRY_INTERVAL, + MqttMessageProperty.MESSAGE_EXPIRY_INTERVAL, /* Followed by the Two Byte integer representing the Topic Alias value. It is a Protocol Error to include the Topic Alias value more than once. @@ -86,7 +87,7 @@ public class PublishMqtt5OutMessage extends PublishMqtt311OutMessage { sends a PUBLISH containing a Topic Alias value of 1 to a Server and the Server sends a PUBLISH with a Topic Alias value of 1 to that Client they will in general be referring to different Topics. */ - PacketProperty.TOPIC_ALIAS, + MqttMessageProperty.TOPIC_ALIAS, /* Followed by a UTF-8 Encoded String which is used as the Topic Name for a response message. The Response Topic MUST be a UTF-8 Encoded String as defined in section 1.5.4 [MQTT-3.3.2-13]. The Response Topic MUST NOT @@ -103,7 +104,7 @@ public class PublishMqtt5OutMessage extends PublishMqtt311OutMessage { the Topic Name of a PUBLISH. If the Request Message contains a Correlation Data, the receiver of the Request Message should also include this Correlation Data as a property in the PUBLISH packet of the Response Message. */ - PacketProperty.RESPONSE_TOPIC, + MqttMessageProperty.RESPONSE_TOPIC, /* Followed by Binary Data. The Correlation Data is used by the sender of the Request Message to identify which request the Response Message is for when it is received. It is a Protocol Error to include Correlation Data @@ -124,7 +125,7 @@ public class PublishMqtt5OutMessage extends PublishMqtt311OutMessage { Refer to section 4.10 for more information about Request / Response */ - PacketProperty.CORRELATION_DATA, + MqttMessageProperty.CORRELATION_DATA, /* Followed by a UTF-8 String Pair. The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. @@ -137,10 +138,11 @@ public class PublishMqtt5OutMessage extends PublishMqtt311OutMessage { This property is intended to provide a means of transferring application layer name-value tags whose meaning and interpretation are known only by the application programs responsible for sending and receiving them. */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); + @Nullable String responseTopic; - byte[] correlationData; + byte @Nullable [] correlationData; Array userProperties; int topicAlias; @@ -155,8 +157,8 @@ public PublishMqtt5OutMessage( byte[] payload, int topicAlias, boolean stringPayload, - String responseTopic, - byte[] correlationData, + @Nullable String responseTopic, + byte @Nullable [] correlationData, Array userProperties) { super(messageId, qos, retained, duplicate, topicName, payload); this.topicAlias = topicAlias; @@ -179,15 +181,15 @@ protected boolean isPropertiesSupported(MqttConnection connection) { @Override protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc511988586 - writeProperty(buffer, PacketProperty.PAYLOAD_FORMAT_INDICATOR, stringPayload); + writeProperty(buffer, MqttMessageProperty.PAYLOAD_FORMAT_INDICATOR, stringPayload); writeProperty( buffer, - PacketProperty.MESSAGE_EXPIRY_INTERVAL, + MqttMessageProperty.MESSAGE_EXPIRY_INTERVAL, 0, MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED); - writeProperty(buffer, PacketProperty.TOPIC_ALIAS, topicAlias, MqttProperties.TOPIC_ALIAS_DEFAULT); - writeNotEmptyProperty(buffer, PacketProperty.RESPONSE_TOPIC, responseTopic); - writeNotEmptyProperty(buffer, PacketProperty.CORRELATION_DATA, correlationData); - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeProperty(buffer, MqttMessageProperty.TOPIC_ALIAS, topicAlias, MqttProperties.TOPIC_ALIAS_UNDEFINED); + writeNotEmptyProperty(buffer, MqttMessageProperty.RESPONSE_TOPIC, responseTopic); + writeNotEmptyProperty(buffer, MqttMessageProperty.CORRELATION_DATA, correlationData); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqttOutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqttOutMessage.java index e72a876c..4bc1515b 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqttOutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishMqttOutMessage.java @@ -1,6 +1,6 @@ package javasabr.mqtt.network.message.out; -import javasabr.mqtt.network.message.HasMessageId; +import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.network.message.MqttMessageType; import lombok.AccessLevel; import lombok.Getter; @@ -11,7 +11,7 @@ @Accessors(fluent = true) @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public abstract class PublishMqttOutMessage extends MqttOutMessage implements HasMessageId { +public abstract class PublishMqttOutMessage extends MqttOutMessage implements TrackableMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.PUBLISH.ordinal(); diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/PublishReceivedMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/PublishReceivedMqtt5OutMessage.java index 8f7165fa..fe6e571a 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/PublishReceivedMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/PublishReceivedMqtt5OutMessage.java @@ -3,7 +3,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -18,7 +18,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class PublishReceivedMqtt5OutMessage extends PublishReceivedMqtt311OutMessage { - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the @@ -29,7 +29,7 @@ public class PublishReceivedMqtt5OutMessage extends PublishReceivedMqtt311OutMes specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the @@ -37,7 +37,7 @@ public class PublishReceivedMqtt5OutMessage extends PublishReceivedMqtt311OutMes Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); Array userProperties; PublishReceivedReasonCode reasonCode; @@ -79,7 +79,7 @@ protected boolean isPropertiesSupported(MqttConnection connection) { protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { super.writeProperties(connection, buffer); // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901135 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, MqttMessageProperty.REASON_STRING, reason); } } 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 db4f4244..b015238d 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 @@ -3,7 +3,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -17,7 +17,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class PublishReleaseMqtt5OutMessage extends PublishReleaseMqtt311OutMessage { - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the @@ -28,7 +28,7 @@ public class PublishReleaseMqtt5OutMessage extends PublishReleaseMqtt311OutMessa specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the @@ -36,7 +36,7 @@ public class PublishReleaseMqtt5OutMessage extends PublishReleaseMqtt311OutMessa Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); Array userProperties; PublishReleaseReasonCode reasonCode; @@ -74,7 +74,7 @@ protected boolean isPropertiesSupported(MqttConnection connection) { protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { super.writeProperties(connection, buffer); // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901145 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, MqttMessageProperty.REASON_STRING, reason); } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessage.java index a008d20a..d471fa27 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt311OutMessage.java @@ -7,15 +7,13 @@ import javasabr.mqtt.network.message.MqttMessageType; import javasabr.rlib.collections.array.Array; import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; /** * Subscribe acknowledgement. */ -@RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class SubscribeAckMqtt311OutMessage extends MqttOutMessage { +public class SubscribeAckMqtt311OutMessage extends TrackableMqttOutMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.SUBSCRIBE_ACK.ordinal(); @@ -28,10 +26,10 @@ public class SubscribeAckMqtt311OutMessage extends MqttOutMessage { */ Array reasonCodes; - /** - * The Packet Identifier from the SUBSCRIBE. - */ - int messageId; + public SubscribeAckMqtt311OutMessage(int messageId, Array reasonCodes) { + super(messageId); + this.reasonCodes = reasonCodes; + } @Override public int expectedLength(MqttConnection connection) { @@ -43,17 +41,11 @@ protected byte messageType() { return MESSAGE_TYPE; } - @Override - protected void writeVariableHeader(MqttConnection connection, ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718070 - writeShort(buffer, messageId); - } - @Override protected void writePayload(MqttConnection connection, ByteBuffer buffer) { // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718071 for (var reasonCode : reasonCodes) { - writeByte(buffer, reasonCode.getValue()); + writeByte(buffer, reasonCode.code()); } } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessage.java index 62119bee..71a3aa60 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeAckMqtt5OutMessage.java @@ -4,7 +4,7 @@ import java.util.EnumSet; import java.util.Set; import javasabr.mqtt.base.util.DebugUtils; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -22,7 +22,7 @@ public class SubscribeAckMqtt5OutMessage extends SubscribeAckMqtt311OutMessage { DebugUtils.registerIncludedFields("reasonCodes", "messageId"); } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the @@ -32,7 +32,7 @@ public class SubscribeAckMqtt5OutMessage extends SubscribeAckMqtt311OutMessage { Property if it would increase the size of the SUBACK packet beyond the Maximum Packet Size specified by the Client */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information. The Server MUST NOT send this property if it would increase the size of the SUBACK packet @@ -40,7 +40,7 @@ public class SubscribeAckMqtt5OutMessage extends SubscribeAckMqtt311OutMessage { appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); Array userProperties; String reason; @@ -50,7 +50,7 @@ public SubscribeAckMqtt5OutMessage( Array reasonCodes, Array userProperties, String reason) { - super(reasonCodes, messageId); + super(messageId, reasonCodes); this.userProperties = userProperties; this.reason = reason; } @@ -67,9 +67,8 @@ protected boolean isPropertiesSupported(MqttConnection connection) { @Override protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901174 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, MqttMessageProperty.REASON_STRING, reason); } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java index 49404549..73a208ae 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessage.java @@ -1,49 +1,45 @@ package javasabr.mqtt.network.message.out; import java.nio.ByteBuffer; -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.model.subscribtion.Subscription; import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.rlib.collections.array.Array; import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; /** * Subscribe request. */ -@RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public class SubscribeMqtt311OutMessage extends MqttOutMessage { +public class SubscribeMqtt311OutMessage extends TrackableMqttOutMessage { private static final byte MESSAGE_TYPE = (byte) MqttMessageType.SUBSCRIBE.ordinal(); - Array topicFilters; - int messageId; + Array subscriptions; - @Override - protected byte messageType() { - return MESSAGE_TYPE; + public SubscribeMqtt311OutMessage(int messageId, Array subscriptions) { + super(messageId); + this.subscriptions = subscriptions; } @Override - protected void writeVariableHeader(MqttConnection connection, ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718065 - writeShort(buffer, messageId); + protected byte messageType() { + return MESSAGE_TYPE; } @Override protected void writePayload(MqttConnection connection, ByteBuffer buffer) { // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718066 - for (SubscribeTopicFilter subscribedTopic : topicFilters) { - TopicFilter topicFilter = subscribedTopic.getTopicFilter(); - writeString(buffer, topicFilter.toString()); + for (Subscription subscribedTopic : subscriptions) { + TopicFilter topicFilter = subscribedTopic.topicFilter(); + writeString(buffer, topicFilter.rawTopic()); writeByte(buffer, buildSubscriptionOptions(subscribedTopic)); } } - protected int buildSubscriptionOptions(SubscribeTopicFilter topicFilter) { - return topicFilter.getQos().ordinal(); + protected int buildSubscriptionOptions(Subscription topicFilter) { + return topicFilter.qos().level(); } } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessage.java index 7e5456e3..e18b1f13 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/SubscribeMqtt5OutMessage.java @@ -3,12 +3,12 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.MqttProperties; -import javasabr.mqtt.model.PacketProperty; import javasabr.mqtt.model.QoS; import javasabr.mqtt.model.SubscribeRetainHandling; import javasabr.mqtt.model.data.type.StringPair; -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.model.subscribtion.Subscription; import javasabr.mqtt.network.MqttConnection; import javasabr.rlib.collections.array.Array; import lombok.AccessLevel; @@ -20,7 +20,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class SubscribeMqtt5OutMessage extends SubscribeMqtt311OutMessage { - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by a Variable Byte Integer representing the identifier of the subscription. The Subscription Identifier can have the value of 1 to 268,435,455. It is a Protocol Error if the Subscription Identifier has a @@ -31,44 +31,44 @@ public class SubscribeMqtt5OutMessage extends SubscribeMqtt311OutMessage { property is not specified, then the absence of a Subscription Identifier is stored with the subscription. */ - PacketProperty.SUBSCRIPTION_IDENTIFIER, + MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, /* The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY); - + MqttMessageProperty.USER_PROPERTY); // properties Array userProperties; int subscriptionId; - public SubscribeMqtt5OutMessage(Array topicFilters, int messageId) { - this(topicFilters, messageId, Array.empty(StringPair.class), MqttProperties.SUBSCRIPTION_ID_UNDEFINED); + public SubscribeMqtt5OutMessage(int messageId, Array subscriptions) { + this(messageId, subscriptions, EMPTY_USER_PROPERTIES, MqttProperties.SUBSCRIPTION_ID_UNDEFINED); } public SubscribeMqtt5OutMessage( - Array topicFilters, int messageId, + Array subscriptions, Array userProperties, int subscriptionId) { - super(topicFilters, messageId); + super(messageId, subscriptions); this.userProperties = userProperties; this.subscriptionId = subscriptionId; } - protected int buildSubscriptionOptions(SubscribeTopicFilter topicFilter) { + @Override + protected int buildSubscriptionOptions(Subscription subscription) { - SubscribeRetainHandling retainHandling = topicFilter.getRetainHandling(); - QoS qos = topicFilter.getQos(); + SubscribeRetainHandling retainHandling = subscription.retainHandling(); + QoS qos = subscription.qos(); - var subscriptionOptions = 0; + int subscriptionOptions = 0; subscriptionOptions |= retainHandling.ordinal() << 4; - if (topicFilter.isRetainAsPublished()) { + if (subscription.retainAsPublished()) { subscriptionOptions |= 0b0000_1000; } - if (topicFilter.isNoLocal()) { + if (subscription.noLocal()) { subscriptionOptions |= 0b0000_0100; } @@ -85,10 +85,10 @@ protected boolean isPropertiesSupported(MqttConnection connection) { @Override protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901164 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); writeProperty( buffer, - PacketProperty.SUBSCRIPTION_IDENTIFIER, + MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, subscriptionId, MqttProperties.SUBSCRIPTION_ID_UNDEFINED); } diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/TrackableMqttOutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/TrackableMqttOutMessage.java new file mode 100644 index 00000000..5b2e382c --- /dev/null +++ b/network/src/main/java/javasabr/mqtt/network/message/out/TrackableMqttOutMessage.java @@ -0,0 +1,24 @@ +package javasabr.mqtt.network.message.out; + +import java.nio.ByteBuffer; +import javasabr.mqtt.base.util.DebugUtils; +import javasabr.mqtt.network.MqttConnection; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) +public abstract class TrackableMqttOutMessage extends MqttOutMessage { + + static { + DebugUtils.registerIncludedFields("messageId"); + } + + int messageId; + + @Override + protected void writeVariableHeader(MqttConnection connection, ByteBuffer buffer) { + writeShort(buffer, messageId); + } +} diff --git a/network/src/main/java/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt5OutMessage.java b/network/src/main/java/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt5OutMessage.java index 8724c636..a0da8b65 100644 --- a/network/src/main/java/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt5OutMessage.java +++ b/network/src/main/java/javasabr/mqtt/network/message/out/UnsubscribeAckMqtt5OutMessage.java @@ -3,7 +3,7 @@ import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; -import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.MqttMessageProperty; import javasabr.mqtt.model.data.type.StringPair; import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; import javasabr.mqtt.network.MqttConnection; @@ -17,7 +17,7 @@ @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class UnsubscribeAckMqtt5OutMessage extends UnsubscribeAckMqtt311OutMessage { - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the @@ -28,7 +28,7 @@ public class UnsubscribeAckMqtt5OutMessage extends UnsubscribeAckMqtt311OutMessa specified by the Client [MQTT-3.11.2-1]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + MqttMessageProperty.REASON_STRING, /* Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other information. The Server MUST NOT send this property if it would increase the size of the UNSUBACK @@ -36,7 +36,7 @@ public class UnsubscribeAckMqtt5OutMessage extends UnsubscribeAckMqtt311OutMessa allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. */ - PacketProperty.USER_PROPERTY); + MqttMessageProperty.USER_PROPERTY); Array reasonCodes; Array userProperties; @@ -61,8 +61,8 @@ protected boolean isPropertiesSupported(MqttConnection connection) { @Override protected void writeProperties(MqttConnection connection, ByteBuffer buffer) { // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901182 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + writeStringPairProperties(buffer, MqttMessageProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, MqttMessageProperty.REASON_STRING, reason); } @Override 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 31453d38..cbc0e787 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 @@ -1,6 +1,6 @@ package javasabr.mqtt.network.message.in -import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.AuthenticateReasonCode import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils @@ -10,10 +10,10 @@ class AuthenticationMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) - it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.AUTHENTICATION_METHOD, authMethod) + it.putProperty(MqttMessageProperty.AUTHENTICATION_DATA, authData) + it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) + it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { it.put(AuthenticateReasonCode.SUCCESS.value) @@ -32,10 +32,10 @@ class AuthenticationMqttInMessageTest extends BaseMqttInMessageTest { packet.userProperties() == userProperties when: propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) + it.putProperty(MqttMessageProperty.AUTHENTICATION_METHOD, authMethod) + it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) + it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.AUTHENTICATION_DATA, authData) } dataBuffer = BufferUtils.prepareBuffer(512) { it.put(AuthenticateReasonCode.CONTINUE_AUTHENTICATION.value) @@ -53,8 +53,8 @@ class AuthenticationMqttInMessageTest extends BaseMqttInMessageTest { packet.userProperties() == userProperties when: propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) - it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) + it.putProperty(MqttMessageProperty.AUTHENTICATION_METHOD, authMethod) + it.putProperty(MqttMessageProperty.AUTHENTICATION_DATA, authData) } dataBuffer = BufferUtils.prepareBuffer(512) { it.put(AuthenticateReasonCode.CONTINUE_AUTHENTICATION.value) 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 c801720d..1434bc43 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 @@ -1,7 +1,7 @@ package javasabr.mqtt.network.message.in +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.MqttProperties -import javasabr.mqtt.model.PacketProperty import javasabr.mqtt.model.QoS import javasabr.mqtt.model.reason.code.ConnectAckReasonCode import javasabr.rlib.common.util.ArrayUtils @@ -33,32 +33,32 @@ class ConnectAckMqttInMessageTest extends BaseMqttInMessageTest { packet.wildcardSubscriptionAvailable == MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT packet.subscriptionIdAvailable == MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT packet.responseInformation == "" - packet.maxPacketSize == MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED + packet.maxMessageSize == MqttProperties.MAXIMUM_MESSAGE_SIZE_UNDEFINED packet.serverKeepAlive == MqttProperties.SERVER_KEEP_ALIVE_UNDEFINED packet.sessionExpiryInterval == MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED packet.topicAliasMaxValue == MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED - packet.receiveMaxPublishes == MqttProperties.RECEIVE_MAXIMUM_UNDEFINED + packet.receiveMaxPublishes == MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_UNDEFINED } def "should read packet correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.SERVER_REFERENCE, serverReference) - it.putProperty(PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, mqtt311ClientId) - it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) - it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) - it.putProperty(PacketProperty.MAXIMUM_PACKET_SIZE, maxPacketSize) - it.putProperty(PacketProperty.MAXIMUM_QOS, QoS.AT_LEAST_ONCE.ordinal()) - it.putProperty(PacketProperty.RECEIVE_MAXIMUM_PUBLISH, receiveMaxPublishes) - it.putProperty(PacketProperty.RETAIN_AVAILABLE, retainAvailable) - it.putProperty(PacketProperty.RESPONSE_INFORMATION, responseInformation) - it.putProperty(PacketProperty.SERVER_KEEP_ALIVE, serverKeepAlive) - it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) - it.putProperty(PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, sharedSubscriptionAvailable) - it.putProperty(PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, wildcardSubscriptionAvailable) - it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, subscriptionIdAvailable) - it.putProperty(PacketProperty.TOPIC_ALIAS_MAXIMUM, topicAliasMaxValue) + it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) + it.putProperty(MqttMessageProperty.SERVER_REFERENCE, serverReference) + it.putProperty(MqttMessageProperty.ASSIGNED_CLIENT_IDENTIFIER, mqtt311ClientId) + it.putProperty(MqttMessageProperty.AUTHENTICATION_DATA, authData) + it.putProperty(MqttMessageProperty.AUTHENTICATION_METHOD, authMethod) + it.putProperty(MqttMessageProperty.MAXIMUM_MESSAGE_SIZE, maxPacketSize) + it.putProperty(MqttMessageProperty.MAXIMUM_QOS, QoS.AT_LEAST_ONCE.ordinal()) + it.putProperty(MqttMessageProperty.RECEIVE_MAXIMUM_PUBLISHES, receiveMaxPublishes) + it.putProperty(MqttMessageProperty.RETAIN_AVAILABLE, retainAvailable) + it.putProperty(MqttMessageProperty.RESPONSE_INFORMATION, responseInformation) + it.putProperty(MqttMessageProperty.SERVER_KEEP_ALIVE, serverKeepAlive) + it.putProperty(MqttMessageProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) + it.putProperty(MqttMessageProperty.SHARED_SUBSCRIPTION_AVAILABLE, sharedSubscriptionAvailable) + it.putProperty(MqttMessageProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, wildcardSubscriptionAvailable) + it.putProperty(MqttMessageProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, subscriptionIdAvailable) + it.putProperty(MqttMessageProperty.TOPIC_ALIAS_MAXIMUM, topicAliasMaxValue) } def dataBuffer = BufferUtils.prepareBuffer(512) { it.putBoolean(sessionPresent) @@ -78,7 +78,7 @@ class ConnectAckMqttInMessageTest extends BaseMqttInMessageTest { packet.assignedClientId == mqtt311ClientId packet.authenticationData == authData packet.authenticationMethod == authMethod - packet.maxPacketSize == maxPacketSize + packet.maxMessageSize == maxPacketSize packet.maximumQos == QoS.AT_LEAST_ONCE packet.receiveMaxPublishes == receiveMaxPublishes packet.retainAvailable == retainAvailable @@ -92,9 +92,9 @@ class ConnectAckMqttInMessageTest extends BaseMqttInMessageTest { when: propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, sharedSubscriptionAvailable) - it.putProperty(PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, wildcardSubscriptionAvailable) - it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, subscriptionIdAvailable) + it.putProperty(MqttMessageProperty.SHARED_SUBSCRIPTION_AVAILABLE, sharedSubscriptionAvailable) + it.putProperty(MqttMessageProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, wildcardSubscriptionAvailable) + it.putProperty(MqttMessageProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, subscriptionIdAvailable) } dataBuffer = BufferUtils.prepareBuffer(512) { it.putBoolean(sessionPresent) 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 1a74f132..023cc30e 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 @@ -1,8 +1,8 @@ package javasabr.mqtt.network.message.in +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.MqttVersion -import javasabr.mqtt.model.PacketProperty -import javasabr.mqtt.model.exception.MalformedPacketMqttException +import javasabr.mqtt.model.exception.MalformedProtocolMqttException import javasabr.rlib.common.util.ArrayUtils import javasabr.rlib.common.util.BufferUtils @@ -36,15 +36,15 @@ class ConnectMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) - it.putProperty(PacketProperty.RECEIVE_MAXIMUM_PUBLISH, receiveMaxPublishes) - it.putProperty(PacketProperty.MAXIMUM_PACKET_SIZE, maxPacketSize) - it.putProperty(PacketProperty.TOPIC_ALIAS_MAXIMUM, topicAliasMaxValue) - it.putProperty(PacketProperty.REQUEST_RESPONSE_INFORMATION, requestResponseInformation ? 1 : 0) - it.putProperty(PacketProperty.REQUEST_PROBLEM_INFORMATION, requestProblemInformation ? 1 : 0) - it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) - it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) + it.putProperty(MqttMessageProperty.RECEIVE_MAXIMUM_PUBLISHES, receiveMaxPublishes) + it.putProperty(MqttMessageProperty.MAXIMUM_MESSAGE_SIZE, maxPacketSize) + it.putProperty(MqttMessageProperty.TOPIC_ALIAS_MAXIMUM, topicAliasMaxValue) + it.putProperty(MqttMessageProperty.REQUEST_RESPONSE_INFORMATION, requestResponseInformation ? 1 : 0) + 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) } def dataBuffer = BufferUtils.prepareBuffer(512) { it.putString("MQTT") @@ -96,7 +96,7 @@ class ConnectMqttInMessageTest extends BaseMqttInMessageTest { def result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: !result - packet.exception() instanceof MalformedPacketMqttException + packet.exception() instanceof MalformedProtocolMqttException where: stringBytes << [ // https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt 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 1d7492eb..d61d317d 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 @@ -1,53 +1,53 @@ package javasabr.mqtt.network.message.in -import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.DisconnectReasonCode import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class DisconnectMqttInMessageTest extends BaseMqttInMessageTest { - def "should read packet correctly as mqtt 5.0"() { + def 'should read message correctly as mqtt 5.0'() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.SERVER_REFERENCE, serverReference) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + 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) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.put(DisconnectReasonCode.QUOTA_EXCEEDED.value) + it.putByte(DisconnectReasonCode.QUOTA_EXCEEDED.code()) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) } when: - def packet = new DisconnectMqttInMessage(0b1110_0000 as byte) - def result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + def inMessage = new DisconnectMqttInMessage(0b1110_0000 as byte) + def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - packet.reason == reasonString - packet.serverReference == serverReference - packet.reasonCode == DisconnectReasonCode.QUOTA_EXCEEDED - packet.sessionExpiryInterval == sessionExpiryInterval - packet.userProperties() == userProperties + inMessage.reason() == reasonString + inMessage.serverReference() == serverReference + inMessage.reasonCode() == DisconnectReasonCode.QUOTA_EXCEEDED + inMessage.sessionExpiryInterval() == sessionExpiryInterval + inMessage.userProperties() == userProperties when: propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) - it.putProperty(PacketProperty.SERVER_REFERENCE, serverReference) + it.putProperty(MqttMessageProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) + it.putProperty(MqttMessageProperty.SERVER_REFERENCE, serverReference) } dataBuffer = BufferUtils.prepareBuffer(512) { - it.put(DisconnectReasonCode.PACKET_TOO_LARGE.value) + it.putByte(DisconnectReasonCode.PACKET_TOO_LARGE.code()) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) } - packet = new DisconnectMqttInMessage(0b1110_0000 as byte) - result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + inMessage = new DisconnectMqttInMessage(0b1110_0000 as byte) + result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - packet.reason == "" - packet.serverReference == serverReference - packet.reasonCode == DisconnectReasonCode.PACKET_TOO_LARGE - packet.sessionExpiryInterval == sessionExpiryInterval - packet.userProperties() == Array.empty() + inMessage.reason() == "" + inMessage.serverReference() == serverReference + inMessage.reasonCode() == DisconnectReasonCode.PACKET_TOO_LARGE + inMessage.sessionExpiryInterval() == sessionExpiryInterval + inMessage.userProperties() == Array.empty() } } 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 00d44f33..5db05a5b 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 @@ -1,6 +1,6 @@ package javasabr.mqtt.network.message.in -import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.PublishAckReasonCode import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils @@ -10,7 +10,7 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) } when: def packet = new PublishAckMqttInMessage(0b0100_0000 as byte) @@ -18,7 +18,7 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == "" - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishAckReasonCode.SUCCESS packet.userProperties() == Array.empty() } @@ -26,11 +26,11 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) + it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.put(PublishAckReasonCode.PAYLOAD_FORMAT_INVALID.value) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -41,12 +41,12 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == reasonString - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishAckReasonCode.PAYLOAD_FORMAT_INVALID packet.userProperties() == userProperties when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.put(PublishAckReasonCode.UNSPECIFIED_ERROR.value) it.putMbi(0) } @@ -55,7 +55,7 @@ class PublishAckMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == "" - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishAckReasonCode.UNSPECIFIED_ERROR packet.userProperties() == Array.empty() } 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 7b5cda40..cd1693d3 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 @@ -1,6 +1,6 @@ package javasabr.mqtt.network.message.in -import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils @@ -10,7 +10,7 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) } when: def packet = new PublishCompleteMqttInMessage(0b0111_0000 as byte) @@ -18,7 +18,7 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == "" - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishCompletedReasonCode.SUCCESS packet.userProperties() == Array.empty() } @@ -26,11 +26,11 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) + it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.put(PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND.value) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -41,12 +41,12 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == reasonString - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND packet.userProperties() == userProperties when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.put(PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND.value) it.putMbi(0) } @@ -55,7 +55,7 @@ class PublishCompleteMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == "" - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND packet.userProperties() == Array.empty() } 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 3b658bce..ed131cae 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 @@ -1,103 +1,103 @@ package javasabr.mqtt.network.message.in +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.MqttProperties -import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.PayloadFormat import javasabr.mqtt.model.QoS import javasabr.mqtt.model.data.type.StringPair import javasabr.rlib.collections.array.Array import javasabr.rlib.collections.array.IntArray -import javasabr.rlib.common.util.ArrayUtils import javasabr.rlib.common.util.BufferUtils class PublishMqttInMessageTest extends BaseMqttInMessageTest { - def "should read packet correctly as mqtt 3.1.1"() { + def "should read message correctly as mqtt 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { it.putString(publishTopic.toString()) - it.putShort(packetId) + it.putShort(messageId) it.put(publishPayload) } when: - def packet = new PublishMqttInMessage(0b0110_0011 as byte) - def result = packet.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) + def message = new PublishMqttInMessage(0b0110_0011 as byte) + def result = message.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - packet.qos == QoS.AT_LEAST_ONCE - !packet.duplicate - packet.retained - packet.responseTopic == "" - packet.subscriptionIds == IntArray.empty() - packet.contentType == "" - packet.correlationData == ArrayUtils.EMPTY_BYTE_ARRAY - packet.payload == publishPayload - packet.messageId == packetId - packet.userProperties() == Array.empty() - packet.messageExpiryInterval == MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED - packet.topicAlias == MqttProperties.TOPIC_ALIAS_DEFAULT - packet.payloadFormatIndicator == MqttProperties.PAYLOAD_FORMAT_INDICATOR_DEFAULT + message.qos() == QoS.AT_LEAST_ONCE + !message.duplicate() + message.retained() + message.rawResponseTopicName() == null + message.subscriptionIds() == IntArray.empty() + message.contentType() == null + message.correlationData() == null + message.payload() == publishPayload + message.messageId() == messageId + message.userProperties() == Array.empty() + message.messageExpiryInterval() == MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED + message.topicAlias() == MqttProperties.TOPIC_ALIAS_UNDEFINED + message.payloadFormat() == PayloadFormat.UNDEFINED } - def "should read packet correctly as mqtt 5.0"() { + def "should read message correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.PAYLOAD_FORMAT_INDICATOR, 1) - it.putProperty(PacketProperty.MESSAGE_EXPIRY_INTERVAL, messageExpiryInterval) - it.putProperty(PacketProperty.TOPIC_ALIAS, topicAlias) - it.putProperty(PacketProperty.RESPONSE_TOPIC, responseTopic) - it.putProperty(PacketProperty.CORRELATION_DATA, correlationData) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER, subscriptionIds) - it.putProperty(PacketProperty.CONTENT_TYPE, contentType) + 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) + it.putProperty(MqttMessageProperty.CORRELATION_DATA, correlationData) + it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, subscriptionIds) + it.putProperty(MqttMessageProperty.CONTENT_TYPE, contentType) } def dataBuffer = BufferUtils.prepareBuffer(512) { it.putString(publishTopic.toString()) - it.putShort(packetId) + it.putShort(messageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.put(publishPayload) } when: - def packet = new PublishMqttInMessage(0b0110_0011 as byte) - def result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + def message = new PublishMqttInMessage(0b0110_0011 as byte) + def result = message.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - packet.qos == QoS.AT_LEAST_ONCE - !packet.duplicate - packet.retained - packet.responseTopic == responseTopic - packet.subscriptionIds == subscriptionIds - packet.contentType == contentType - packet.correlationData == correlationData - packet.payload == publishPayload - packet.messageId == packetId - packet.userProperties() == userProperties - packet.messageExpiryInterval == messageExpiryInterval - packet.topicAlias == topicAlias - packet.payloadFormatIndicator + message.qos() == QoS.AT_LEAST_ONCE + !message.duplicate() + message.retained() + message.rawResponseTopicName() == responseTopic + 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 when: dataBuffer = BufferUtils.prepareBuffer(512) { it.putString(publishTopic.toString()) - it.putShort(packetId) + it.putShort(messageId) it.putMbi(0) it.put(publishPayload) } - packet = new PublishMqttInMessage(0b0110_0011 as byte) - result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + message = new PublishMqttInMessage(0b0110_0011 as byte) + result = message.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - packet.qos == QoS.AT_LEAST_ONCE - !packet.duplicate - packet.retained - packet.responseTopic == "" - packet.subscriptionIds == IntArray.empty() - packet.contentType == "" - packet.correlationData == ArrayUtils.EMPTY_BYTE_ARRAY - packet.payload == publishPayload - packet.messageId == packetId - packet.userProperties() == Array.empty(StringPair) - packet.messageExpiryInterval == MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED - packet.topicAlias == MqttProperties.TOPIC_ALIAS_DEFAULT - packet.payloadFormatIndicator == MqttProperties.PAYLOAD_FORMAT_INDICATOR_DEFAULT + message.qos() == QoS.AT_LEAST_ONCE + !message.duplicate() + message.retained() + message.rawResponseTopicName() == null + message.subscriptionIds() == IntArray.empty() + message.contentType() == null + message.correlationData() == null + message.payload() == publishPayload + message.messageId() == messageId + message.userProperties() == Array.empty(StringPair) + message.messageExpiryInterval() == MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED + message.topicAlias() == MqttProperties.TOPIC_ALIAS_UNDEFINED + message.payloadFormat() == PayloadFormat.UNDEFINED } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReceivedMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/PublishReceivedMqttInMessageTest.groovy index a4f498e0..a45ecbc4 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 @@ -1,6 +1,6 @@ package javasabr.mqtt.network.message.in -import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils @@ -10,7 +10,7 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) } when: def packet = new PublishReceivedMqttInMessage(0b0101_0000 as byte) @@ -18,7 +18,7 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == "" - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishReceivedReasonCode.SUCCESS packet.userProperties() == Array.empty() } @@ -26,11 +26,11 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) + it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.put(PublishReceivedReasonCode.QUOTA_EXCEEDED.value) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -41,12 +41,12 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == reasonString - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishReceivedReasonCode.QUOTA_EXCEEDED packet.userProperties() == userProperties when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.put(PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.value) it.putMbi(0) } @@ -55,7 +55,7 @@ class PublishReceivedMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == "" - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR packet.userProperties() == Array.empty() } 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 d455b256..37eba8fe 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 @@ -1,6 +1,6 @@ package javasabr.mqtt.network.message.in -import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils @@ -10,7 +10,7 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) } when: def packet = new PublishReleaseMqttInMessage(0b0110_0000 as byte) @@ -18,7 +18,7 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == "" - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishReleaseReasonCode.SUCCESS packet.userProperties() == Array.empty() } @@ -26,11 +26,11 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) + it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.put(PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND.value) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) @@ -41,12 +41,12 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == reasonString - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND packet.userProperties() == userProperties when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.put(PublishReleaseReasonCode.SUCCESS.value) it.putMbi(0) } @@ -55,7 +55,7 @@ class PublishReleaseMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == "" - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCode() == PublishReleaseReasonCode.SUCCESS packet.userProperties() == Array.empty() } 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 1f1cdc50..97e2e66f 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 @@ -1,83 +1,83 @@ package javasabr.mqtt.network.message.in -import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class SubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { - def "should read packet correctly as mqtt 3.1.1"() { + def "should read message correctly as mqtt 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.put(SubscribeAckReasonCode.GRANTED_QOS_0.value) - it.put(SubscribeAckReasonCode.GRANTED_QOS_2.value) - it.put(SubscribeAckReasonCode.GRANTED_QOS_1.value) - it.put(SubscribeAckReasonCode.UNSPECIFIED_ERROR.value) + it.putShort(messageId) + it.putByte(SubscribeAckReasonCode.GRANTED_QOS_0.code()) + it.putByte(SubscribeAckReasonCode.GRANTED_QOS_2.code()) + it.putByte(SubscribeAckReasonCode.GRANTED_QOS_1.code()) + it.putByte(SubscribeAckReasonCode.UNSPECIFIED_ERROR.code()) } when: - def packet = new SubscribeAckMqttInMessage(0b1001_0000 as byte) - def result = packet.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) + def inMessage = new SubscribeAckMqttInMessage(0b1001_0000 as byte) + def result = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - packet.reason == "" - packet.messageId == packetId - packet.reasonCodes.size() == 4 - packet.reasonCodes.get(0) == SubscribeAckReasonCode.GRANTED_QOS_0 - packet.reasonCodes.get(1) == SubscribeAckReasonCode.GRANTED_QOS_2 - packet.reasonCodes.get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 - packet.reasonCodes.get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR + inMessage.reason() == "" + inMessage.messageId() == messageId + inMessage.reasonCodes().size() == 4 + inMessage.reasonCodes().get(0) == SubscribeAckReasonCode.GRANTED_QOS_0 + inMessage.reasonCodes().get(1) == SubscribeAckReasonCode.GRANTED_QOS_2 + inMessage.reasonCodes().get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 + inMessage.reasonCodes().get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR } - def "should read packet correctly as mqtt 5.0"() { + def "should read message correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) + it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) - it.put(SubscribeAckReasonCode.GRANTED_QOS_0.value) - it.put(SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.value) - it.put(SubscribeAckReasonCode.GRANTED_QOS_1.value) - it.put(SubscribeAckReasonCode.UNSPECIFIED_ERROR.value) + it.putByte(SubscribeAckReasonCode.GRANTED_QOS_0.code()) + it.putByte(SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.code()) + it.putByte(SubscribeAckReasonCode.GRANTED_QOS_1.code()) + it.putByte(SubscribeAckReasonCode.UNSPECIFIED_ERROR.code()) } when: - def packet = new SubscribeAckMqttInMessage(0b1001_0000 as byte) - def result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + def inMessage = new SubscribeAckMqttInMessage(0b1001_0000 as byte) + def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - packet.reason == reasonString - packet.messageId == packetId - packet.reasonCodes.size() == 4 - packet.reasonCodes.get(0) == SubscribeAckReasonCode.GRANTED_QOS_0 - packet.reasonCodes.get(1) == SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR - packet.reasonCodes.get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 - packet.reasonCodes.get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR - packet.userProperties() == userProperties + inMessage.reason() == reasonString + inMessage.messageId() == messageId + inMessage.reasonCodes().size() == 4 + inMessage.reasonCodes().get(0) == SubscribeAckReasonCode.GRANTED_QOS_0 + inMessage.reasonCodes().get(1) == SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR + inMessage.reasonCodes().get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 + inMessage.reasonCodes().get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR + inMessage.userProperties() == userProperties when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.putMbi(0) - it.put(SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED.value) - it.put(SubscribeAckReasonCode.GRANTED_QOS_2.value) - it.put(SubscribeAckReasonCode.GRANTED_QOS_1.value) - it.put(SubscribeAckReasonCode.UNSPECIFIED_ERROR.value) + it.putByte(SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED.code()) + it.putByte(SubscribeAckReasonCode.GRANTED_QOS_2.code()) + it.putByte(SubscribeAckReasonCode.GRANTED_QOS_1.code()) + it.putByte(SubscribeAckReasonCode.UNSPECIFIED_ERROR.code()) } - packet = new SubscribeAckMqttInMessage(0b1001_0000 as byte) - result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + inMessage = new SubscribeAckMqttInMessage(0b1001_0000 as byte) + result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - packet.reason == "" - packet.messageId == packetId - packet.reasonCodes.size() == 4 - packet.reasonCodes.get(0) == SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED - packet.reasonCodes.get(1) == SubscribeAckReasonCode.GRANTED_QOS_2 - packet.reasonCodes.get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 - packet.reasonCodes.get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR - packet.userProperties() == Array.empty() + inMessage.reason() == "" + inMessage.messageId() == messageId + inMessage.reasonCodes().size() == 4 + inMessage.reasonCodes().get(0) == SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED + inMessage.reasonCodes().get(1) == SubscribeAckReasonCode.GRANTED_QOS_2 + inMessage.reasonCodes().get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 + inMessage.reasonCodes().get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR + inMessage.userProperties() == Array.empty() } } 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 ca9e546b..944d7736 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 @@ -1,7 +1,7 @@ package javasabr.mqtt.network.message.in +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.MqttProperties -import javasabr.mqtt.model.PacketProperty import javasabr.mqtt.model.QoS import javasabr.mqtt.model.SubscribeRetainHandling import javasabr.rlib.collections.array.Array @@ -9,44 +9,44 @@ import javasabr.rlib.common.util.BufferUtils class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { - def "should read packet correctly as mqtt 3.1.1"() { + def "should read message correctly as mqtt 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.putString(topicFilter) it.put(0b0000_0001 as byte) it.putString(topicFilter2) it.put(0b0000_0010 as byte) } when: - def packet = new SubscribeMqttInMessage(0b1000_0000 as byte) - def result = packet.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) + def message = new SubscribeMqttInMessage(0b1000_0000 as byte) + def result = message.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).getQos() == QoS.AT_LEAST_ONCE - packet.topicFilters.get(0).getTopicFilter().toString() == topicFilter - packet.topicFilters.get(0).isNoLocal() - packet.topicFilters.get(0).isRetainAsPublished() - packet.topicFilters.get(0).getRetainHandling() == SubscribeRetainHandling.SEND - packet.topicFilters.get(1).getQos() == QoS.EXACTLY_ONCE - packet.topicFilters.get(1).getTopicFilter().toString() == topicFilter2 - packet.topicFilters.get(1).isNoLocal() - packet.topicFilters.get(1).isRetainAsPublished() - packet.topicFilters.get(1).getRetainHandling() == SubscribeRetainHandling.SEND - packet.messageId == packetId - packet.userProperties() == Array.empty() - packet.subscriptionId == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + message.subscriptions().size() == 2 + message.subscriptions().get(0).qos() == QoS.AT_LEAST_ONCE + message.subscriptions().get(0).rawTopicFilter() == topicFilter + message.subscriptions().get(0).noLocal() + message.subscriptions().get(0).retainAsPublished() + message.subscriptions().get(0).retainHandling() == SubscribeRetainHandling.SEND + message.subscriptions().get(1).qos() == QoS.EXACTLY_ONCE + message.subscriptions().get(1).rawTopicFilter().toString() == topicFilter2 + message.subscriptions().get(1).noLocal() + message.subscriptions().get(1).retainAsPublished() + message.subscriptions().get(1).retainHandling() == SubscribeRetainHandling.SEND + message.messageId() == messageId + message.userProperties() == Array.empty() + message.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_UNDEFINED } - def "should read packet correctly as mqtt 5.0"() { + def "should read message correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER, subscriptionId) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.SUBSCRIPTION_IDENTIFIER, subscriptionId) + it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.putString(topicFilter) @@ -55,50 +55,50 @@ class SubscribeMqttInMessageTest extends BaseMqttInMessageTest { it.put(0b0001_0110 as byte) } when: - def packet = new SubscribeMqttInMessage(0b0110_0000 as byte) - def result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + def message = new SubscribeMqttInMessage(0b0110_0000 as byte) + def result = message.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).getQos() == QoS.AT_LEAST_ONCE - packet.topicFilters.get(0).getTopicFilter().toString() == topicFilter - !packet.topicFilters.get(0).isNoLocal() - packet.topicFilters.get(0).isRetainAsPublished() - packet.topicFilters.get(0).getRetainHandling() == SubscribeRetainHandling.SEND - packet.topicFilters.get(1).getQos() == QoS.EXACTLY_ONCE - packet.topicFilters.get(1).getTopicFilter().toString() == topicFilter2 - packet.topicFilters.get(1).isNoLocal() - !packet.topicFilters.get(1).isRetainAsPublished() - packet.topicFilters.get(1).getRetainHandling() == SubscribeRetainHandling.SEND_IF_SUBSCRIPTION_DOES_NOT_EXIST - packet.messageId == packetId - packet.userProperties() == userProperties - packet.subscriptionId == subscriptionId + message.subscriptions().size() == 2 + message.subscriptions().get(0).qos() == QoS.AT_LEAST_ONCE + message.subscriptions().get(0).rawTopicFilter().toString() == topicFilter + !message.subscriptions().get(0).noLocal() + message.subscriptions().get(0).retainAsPublished() + message.subscriptions().get(0).retainHandling() == SubscribeRetainHandling.SEND + message.subscriptions().get(1).qos() == QoS.EXACTLY_ONCE + message.subscriptions().get(1).rawTopicFilter().toString() == topicFilter2 + message.subscriptions().get(1).noLocal() + !message.subscriptions().get(1).retainAsPublished() + message.subscriptions().get(1).retainHandling() == SubscribeRetainHandling.SEND_IF_SUBSCRIPTION_DOES_NOT_EXIST + message.messageId() == messageId + message.userProperties() == userProperties + message.subscriptionId() == subscriptionId when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.putMbi(0) it.putString(topicFilter) it.put(0b0000_0001 as byte) it.putString(topicFilter2) it.put(0b0000_0010 as byte) } - packet = new SubscribeMqttInMessage(0b0110_0000 as byte) - result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + message = new SubscribeMqttInMessage(0b0110_0000 as byte) + result = message.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).getQos() == QoS.AT_LEAST_ONCE - packet.topicFilters.get(0).getTopicFilter().toString() == topicFilter - !packet.topicFilters.get(0).isNoLocal() - !packet.topicFilters.get(0).isRetainAsPublished() - packet.topicFilters.get(0).getRetainHandling() == SubscribeRetainHandling.SEND - packet.topicFilters.get(1).getQos() == QoS.EXACTLY_ONCE - packet.topicFilters.get(1).getTopicFilter().toString() == topicFilter2 - !packet.topicFilters.get(1).isNoLocal() - !packet.topicFilters.get(1).isRetainAsPublished() - packet.topicFilters.get(1).getRetainHandling() == SubscribeRetainHandling.SEND - packet.messageId == packetId - packet.userProperties() == Array.empty() - packet.subscriptionId == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + message.subscriptions().size() == 2 + message.subscriptions().get(0).qos() == QoS.AT_LEAST_ONCE + message.subscriptions().get(0).rawTopicFilter().toString() == topicFilter + !message.subscriptions().get(0).noLocal() + !message.subscriptions().get(0).retainAsPublished() + message.subscriptions().get(0).retainHandling() == SubscribeRetainHandling.SEND + message.subscriptions().get(1).qos() == QoS.EXACTLY_ONCE + message.subscriptions().get(1).rawTopicFilter().toString() == topicFilter2 + !message.subscriptions().get(1).noLocal() + !message.subscriptions().get(1).retainAsPublished() + message.subscriptions().get(1).retainHandling() == SubscribeRetainHandling.SEND + message.messageId() == messageId + message.userProperties() == Array.empty() + message.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_UNDEFINED } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeAckMqttInMessageTest.groovy index bafce0b9..d7740abb 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 @@ -1,6 +1,6 @@ package javasabr.mqtt.network.message.in -import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.MqttMessageProperty import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils @@ -10,7 +10,7 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) } when: def packet = new UnsubscribeAckMqttInMessage(0b1011_0000 as byte) @@ -18,18 +18,18 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == "" - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCodes() == Array.empty() } def "should read packet correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.REASON_STRING, reasonString) + it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.put(UnsubscribeAckReasonCode.SUCCESS.value) @@ -43,7 +43,7 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == reasonString - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCodes().size() == 4 packet.reasonCodes().get(0) == UnsubscribeAckReasonCode.SUCCESS packet.reasonCodes().get(1) == UnsubscribeAckReasonCode.SUCCESS @@ -52,7 +52,7 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { packet.userProperties() == userProperties when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.putMbi(0) it.put(UnsubscribeAckReasonCode.UNSPECIFIED_ERROR.value) it.put(UnsubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.value) @@ -62,7 +62,7 @@ class UnsubscribeAckMqttInMessageTest extends BaseMqttInMessageTest { then: result packet.reason() == "" - packet.messageId() == packetId + packet.messageId() == messageId packet.reasonCodes().size() == 2 packet.reasonCodes().get(0) == UnsubscribeAckReasonCode.UNSPECIFIED_ERROR packet.reasonCodes().get(1) == UnsubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR 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 c4bdc9d3..9726ffb5 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/in/UnsubscribeMqttInMessageTest.groovy @@ -1,6 +1,6 @@ package javasabr.mqtt.network.message.in -import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.MqttMessageProperty import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils @@ -9,7 +9,7 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def "should read packet correctly as mqtt 3.1.1"() { given: def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.putString(topicFilter) it.putString(topicFilter2) } @@ -18,20 +18,20 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def result = packet.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).toString() == topicFilter - packet.topicFilters.get(1).toString() == topicFilter2 - packet.messageId == packetId + packet.rawTopicFilters.size() == 2 + packet.rawTopicFilters.get(0).toString() == topicFilter + packet.rawTopicFilters.get(1).toString() == topicFilter2 + packet.messageId == messageId packet.userProperties() == Array.empty() } def "should read packet correctly as mqtt 5.0"() { given: def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(MqttMessageProperty.USER_PROPERTY, userProperties) } def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.putMbi(propertiesBuffer.limit()) it.put(propertiesBuffer) it.putString(topicFilter) @@ -42,14 +42,14 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { def result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).toString() == topicFilter - packet.topicFilters.get(1).toString() == topicFilter2 - packet.messageId == packetId + packet.rawTopicFilters.size() == 2 + packet.rawTopicFilters.get(0).toString() == topicFilter + packet.rawTopicFilters.get(1).toString() == topicFilter2 + packet.messageId == messageId packet.userProperties() == userProperties when: dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) + it.putShort(messageId) it.putMbi(0) it.putString(topicFilter) it.putString(topicFilter2) @@ -58,10 +58,10 @@ class UnsubscribeMqttInMessageTest extends BaseMqttInMessageTest { result = packet.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).toString() == topicFilter - packet.topicFilters.get(1).toString() == topicFilter2 - packet.messageId == packetId + packet.rawTopicFilters.size() == 2 + packet.rawTopicFilters.get(0).toString() == topicFilter + packet.rawTopicFilters.get(1).toString() == topicFilter2 + packet.messageId == messageId packet.userProperties() == Array.empty() } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt311OutMessageTest.groovy index 16cd9e97..67342b1b 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt311OutMessageTest.groovy @@ -37,8 +37,8 @@ class ConnectAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { reader.authenticationMethod == "" reader.topicAliasMaxValue == MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED reader.serverKeepAlive == MqttProperties.SERVER_KEEP_ALIVE_UNDEFINED - reader.receiveMaxPublishes == MqttProperties.RECEIVE_MAXIMUM_UNDEFINED + reader.receiveMaxPublishes == MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_UNDEFINED reader.sessionExpiryInterval == MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED - reader.maxPacketSize == MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED + reader.maxMessageSize == MqttProperties.MAXIMUM_MESSAGE_SIZE_UNDEFINED } } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/ConnectAckMqtt5OutMessageTest.groovy index aed36f39..c0a1ae09 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 @@ -7,9 +7,10 @@ import javasabr.rlib.common.util.BufferUtils class ConnectAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { - def "should write packet correctly"() { + def "should write message correctly"() { given: def clientConfig = clientConnectionConfig( + defaultServerConnectionConfig(), maxQos, MqttVersion.MQTT_5, 240, @@ -18,17 +19,12 @@ class ConnectAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { 300, 30, false, - false, - sessionsEnabled, - retainAvailable, - wildcardSubscriptionAvailable, - subscriptionIdAvailable, - sharedSubscriptionAvailable); + false); def requestedClientId = "-1" def requestedSessionExpireInterval = 360 def requestedKeepAlive = 120 def requestedReceiveMaxPublishes = 500 - def packet = new ConnectAckMqtt5OutMessage( + def outMessage = new ConnectAckMqtt5OutMessage( clientConfig, ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD, sessionPresent, @@ -45,29 +41,29 @@ class ConnectAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { userProperties) when: def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(defaultMqtt5Connection, it) + outMessage.write(defaultMqtt5Connection, it) } - def reader = new ConnectAckMqttInMessage(0b0010_0000 as byte) - def result = reader.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + def inMessage = new ConnectAckMqttInMessage(0b0010_0000 as byte) + def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - reader.reasonCode == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD - reader.sessionPresent == sessionPresent - reader.retainAvailable == retainAvailable - reader.sessionExpiryInterval == 240 - reader.receiveMaxPublishes == 250 - reader.maxPacketSize == maxPacketSize - reader.assignedClientId == mqtt311ClientId - reader.topicAliasMaxValue == 300 - reader.reason == reasonString - reader.userProperties() == userProperties - reader.wildcardSubscriptionAvailable == wildcardSubscriptionAvailable - reader.subscriptionIdAvailable == subscriptionIdAvailable - reader.sharedSubscriptionAvailable == sharedSubscriptionAvailable - reader.serverKeepAlive == 30 - reader.responseInformation == responseInformation - reader.serverReference == serverReference - reader.authenticationData == authData - reader.authenticationMethod == authMethod + inMessage.reasonCode() == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD + inMessage.sessionPresent() == sessionPresent + inMessage.retainAvailable() == retainAvailable + inMessage.sessionExpiryInterval() == 240 + inMessage.receiveMaxPublishes() == 250 + inMessage.maxMessageSize() == maxPacketSize + inMessage.assignedClientId() == mqtt311ClientId + inMessage.topicAliasMaxValue() == 300 + inMessage.reason() == reasonString + inMessage.userProperties() == userProperties + inMessage.wildcardSubscriptionAvailable() == wildcardSubscriptionAvailable + inMessage.subscriptionIdAvailable() == subscriptionIdAvailable + inMessage.sharedSubscriptionAvailable() == sharedSubscriptionAvailable + inMessage.serverKeepAlive() == 30 + inMessage.responseInformation() == responseInformation + inMessage.serverReference() == serverReference + inMessage.authenticationData() == authData + inMessage.authenticationMethod() == authMethod } } 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 e3468ba6..ce989ad2 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 @@ -9,7 +9,7 @@ class PublishAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { def "should write packet correctly"() { given: - def packet = new PublishAckMqtt311OutMessage(packetId) + def packet = new PublishAckMqtt311OutMessage(messageId) when: def dataBuffer = BufferUtils.prepareBuffer(512) { packet.write(defaultMqtt311Connection, it) @@ -19,7 +19,7 @@ class PublishAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCode() == PublishAckReasonCode.SUCCESS - reader.messageId() == packetId + reader.messageId() == messageId reader.userProperties() == Array.empty() reader.reason() == "" } 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 49e49328..a9b3ac61 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 @@ -9,7 +9,7 @@ class PublishAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write packet correctly"() { given: def packet = new PublishAckMqtt5OutMessage( - packetId, + messageId, PublishAckReasonCode.NOT_AUTHORIZED, userProperties, reasonString) @@ -22,7 +22,7 @@ class PublishAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCode() == PublishAckReasonCode.NOT_AUTHORIZED - reader.messageId() == packetId + reader.messageId() == messageId reader.userProperties() == userProperties reader.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 3955c856..39382af8 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 @@ -9,7 +9,7 @@ class PublishCompleteMqtt311OutMessageTest extends BaseMqttOutMessageTest { def "should write packet correctly"() { given: - def packet = new PublishCompleteMqtt311OutMessage(packetId) + def packet = new PublishCompleteMqtt311OutMessage(messageId) when: def dataBuffer = BufferUtils.prepareBuffer(512) { packet.write(defaultMqtt311Connection, it) @@ -19,7 +19,7 @@ class PublishCompleteMqtt311OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCode() == PublishCompletedReasonCode.SUCCESS - reader.messageId() == packetId + reader.messageId() == messageId reader.userProperties() == Array.empty() reader.reason() == "" } 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 2958b85c..7b1887e9 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 @@ -9,7 +9,7 @@ class PublishCompleteMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write packet correctly"() { given: def packet = new PublishCompleteMqtt5OutMessage( - packetId, + messageId, PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND, userProperties, reasonString) @@ -22,7 +22,7 @@ class PublishCompleteMqtt5OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCode() == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND - reader.messageId() == packetId + reader.messageId() == messageId reader.userProperties() == userProperties reader.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 6e885d4e..9364146f 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 @@ -7,10 +7,10 @@ import javasabr.rlib.common.util.BufferUtils class PublishMqtt311OutMessageTest extends BaseMqttOutMessageTest { - def "should write packet correctly"() { + def "should write message correctly"() { given: - def packet = new PublishMqtt311OutMessage( - packetId, + def outMessage = new PublishMqtt311OutMessage( + messageId, QoS.EXACTLY_ONCE, true, true, @@ -18,40 +18,40 @@ class PublishMqtt311OutMessageTest extends BaseMqttOutMessageTest { publishPayload) when: def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(defaultMqtt311Connection, it) + outMessage.write(defaultMqtt311Connection, it) } - def reader = new PublishMqttInMessage(0b0011_1101 as byte) - def result = reader.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) + def inMessage = new PublishMqttInMessage(0b0011_1101 as byte) + def result = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - reader.messageId == packetId - reader.qos == QoS.EXACTLY_ONCE - reader.retained - reader.duplicate - reader.payload == publishPayload - reader.topicName == publishTopic - reader.userProperties() == Array.empty() + inMessage.messageId() == messageId + inMessage.qos() == QoS.EXACTLY_ONCE + inMessage.retained() + inMessage.duplicate() + inMessage.payload() == publishPayload + inMessage.rawTopicName() == publishTopic.rawTopic() + inMessage.userProperties() == Array.empty() when: - packet = new PublishMqtt311OutMessage( - packetId, + outMessage = new PublishMqtt311OutMessage( + messageId, QoS.AT_MOST_ONCE, false, false, publishTopic.toString(), publishPayload) dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(defaultMqtt311Connection, it) + outMessage.write(defaultMqtt311Connection, it) } - reader = new PublishMqttInMessage(0b0011_0000 as byte) - result = reader.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) + inMessage = new PublishMqttInMessage(0b0011_0000 as byte) + result = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - reader.messageId == 0 - reader.qos == QoS.AT_MOST_ONCE - !reader.retained - !reader.duplicate - reader.payload == publishPayload - reader.topicName == publishTopic - reader.userProperties() == Array.empty() + inMessage.messageId() == 0 + inMessage.qos() == QoS.AT_MOST_ONCE + !inMessage.retained() + !inMessage.duplicate() + inMessage.payload() == publishPayload + inMessage.rawTopicName() == publishTopic.rawTopic() + inMessage.userProperties() == Array.empty() } } 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 96fc7eaf..56ae66f5 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 @@ -1,15 +1,16 @@ package javasabr.mqtt.network.message.out +import javasabr.mqtt.model.PayloadFormat import javasabr.mqtt.model.QoS import javasabr.mqtt.network.message.in.PublishMqttInMessage import javasabr.rlib.common.util.BufferUtils class PublishMqtt5OutMessageTest extends BaseMqttOutMessageTest { - def "should write packet correctly"() { + def "should write message correctly"() { given: - def packet = new PublishMqtt5OutMessage( - packetId, + def outMessage = new PublishMqtt5OutMessage( + messageId, QoS.EXACTLY_ONCE, true, true, @@ -22,26 +23,26 @@ class PublishMqtt5OutMessageTest extends BaseMqttOutMessageTest { userProperties) when: def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(defaultMqtt5Connection, it) + outMessage.write(defaultMqtt5Connection, it) } - def reader = new PublishMqttInMessage(0b0011_1101 as byte) - def result = reader.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + def inMessage = new PublishMqttInMessage(0b0011_1101 as byte) + def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - reader.messageId == packetId - reader.qos == QoS.EXACTLY_ONCE - reader.retained - reader.duplicate - reader.payload == publishPayload - reader.topicName == publishTopic - reader.userProperties() == userProperties - reader.topicAlias == topicAlias - !reader.payloadFormatIndicator - reader.responseTopic == responseTopic - reader.correlationData == correlationData + inMessage.messageId() == messageId + inMessage.qos() == QoS.EXACTLY_ONCE + inMessage.retained() + inMessage.duplicate() + inMessage.payload() == publishPayload + inMessage.rawTopicName() == publishTopic.rawTopic() + inMessage.userProperties() == userProperties + inMessage.topicAlias() == topicAlias + inMessage.payloadFormat() == PayloadFormat.BINARY + inMessage.rawResponseTopicName() == responseTopic + inMessage.correlationData() == correlationData when: - packet = new PublishMqtt5OutMessage( - packetId, + outMessage = new PublishMqtt5OutMessage( + messageId, QoS.AT_MOST_ONCE, false, false, @@ -54,23 +55,23 @@ class PublishMqtt5OutMessageTest extends BaseMqttOutMessageTest { userProperties) dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(defaultMqtt5Connection, it) + outMessage.write(defaultMqtt5Connection, it) } - reader = new PublishMqttInMessage(0b0011_0000 as byte) - result = reader.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + inMessage = new PublishMqttInMessage(0b0011_0000 as byte) + result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - reader.messageId == 0 - reader.qos == QoS.AT_MOST_ONCE - !reader.retained - !reader.duplicate - reader.payload == publishPayload - reader.topicName == publishTopic - reader.userProperties() == userProperties - reader.topicAlias == topicAlias - reader.payloadFormatIndicator - reader.responseTopic == responseTopic - reader.correlationData == correlationData + inMessage.messageId() == 0 + inMessage.qos() == QoS.AT_MOST_ONCE + !inMessage.retained() + !inMessage.duplicate() + inMessage.payload() == publishPayload + inMessage.rawTopicName() == publishTopic.rawTopic() + inMessage.userProperties() == userProperties + inMessage.topicAlias() == topicAlias + inMessage.payloadFormat() == PayloadFormat.UTF8_STRING + inMessage.rawResponseTopicName() == responseTopic + inMessage.correlationData() == correlationData } } 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 c4b036f7..0d487213 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 @@ -10,7 +10,7 @@ class PublishReceivedMqtt311OutMessageTest extends BaseMqttOutMessageTest { def "should write packet correctly"() { given: - def packet = new PublishReceivedMqtt311OutMessage(packetId) + def packet = new PublishReceivedMqtt311OutMessage(messageId) when: def dataBuffer = BufferUtils.prepareBuffer(512) { packet.write(defaultMqtt311Connection, it) @@ -20,7 +20,7 @@ class PublishReceivedMqtt311OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCode() == PublishReceivedReasonCode.SUCCESS - reader.messageId() == packetId + reader.messageId() == messageId reader.userProperties() == Array.empty(StringPair) reader.reason() == "" } 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 837aa90c..fdba4afa 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 @@ -9,7 +9,7 @@ class PublishReceivedMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write packet correctly"() { given: def packet = new PublishReceivedMqtt5OutMessage( - packetId, + messageId, PublishReceivedReasonCode.UNSPECIFIED_ERROR, userProperties, reasonString) @@ -22,7 +22,7 @@ class PublishReceivedMqtt5OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCode() == PublishReceivedReasonCode.UNSPECIFIED_ERROR - reader.messageId() == packetId + reader.messageId() == messageId reader.userProperties() == userProperties reader.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 92424cac..3439dbf3 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 packet correctly"() { given: - def packet = new PublishReleaseMqtt311OutMessage(packetId) + def packet = new PublishReleaseMqtt311OutMessage(messageId) when: def dataBuffer = BufferUtils.prepareBuffer(512) { packet.write(defaultMqtt311Connection, it) @@ -20,7 +20,7 @@ class PublishReleaseMqtt311OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCode() == PublishReleaseReasonCode.SUCCESS - reader.messageId() == packetId + reader.messageId() == messageId reader.userProperties() == Array.empty(StringPair) reader.reason() == "" } 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 d59263de..c6fa121e 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 @@ -9,7 +9,7 @@ class PublishReleaseMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write packet correctly"() { given: def packet = new PublishReleaseMqtt5OutMessage( - packetId, + messageId, PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND, userProperties, reasonString) @@ -22,7 +22,7 @@ class PublishReleaseMqtt5OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCode() == PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND - reader.messageId() == packetId + reader.messageId() == messageId reader.userProperties() == userProperties reader.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 0ca18cc7..f6e5bbf8 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 @@ -9,7 +9,7 @@ class SubscribeAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { def "should write packet correctly"() { given: - def packet = new SubscribeAckMqtt311OutMessage(subscribeAckReasonCodes, packetId) + def packet = new SubscribeAckMqtt311OutMessage(messageId, subscribeAckReasonCodes) when: def dataBuffer = BufferUtils.prepareBuffer(512) { packet.write(defaultMqtt311Connection, it) @@ -18,9 +18,9 @@ class SubscribeAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { def result = reader.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - reader.reasonCodes == subscribeAckReasonCodes - reader.messageId == packetId + reader.reasonCodes() == subscribeAckReasonCodes + reader.messageId() == messageId reader.userProperties() == Array.empty(StringPair) - reader.reason == "" + reader.reason() == "" } } 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 09a33110..42ecdbc6 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 @@ -8,7 +8,7 @@ class SubscribeAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write packet correctly"() { given: def packet = new SubscribeAckMqtt5OutMessage( - packetId, + messageId, subscribeAckReasonCodes, userProperties, reasonString) @@ -21,7 +21,7 @@ class SubscribeAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCodes == subscribeAckReasonCodes - reader.messageId == packetId + reader.messageId == messageId reader.userProperties() == userProperties reader.reason == reasonString } diff --git a/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessageTest.groovy b/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessageTest.groovy index ccdd7c5a..1255ed0f 100644 --- a/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessageTest.groovy +++ b/network/src/test/groovy/javasabr/mqtt/network/message/out/SubscribeMqtt311OutMessageTest.groovy @@ -1,29 +1,41 @@ package javasabr.mqtt.network.message.out import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.QoS import javasabr.mqtt.model.data.type.StringPair +import javasabr.mqtt.model.subscribtion.RequestedSubscription +import javasabr.mqtt.model.subscribtion.Subscription +import javasabr.mqtt.model.topic.TopicFilter import javasabr.mqtt.network.message.in.SubscribeMqttInMessage import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class SubscribeMqtt311OutMessageTest extends BaseMqttOutMessageTest { - def "should write packet correctly"() { + def "should write message correctly"() { given: - def packet = new SubscribeMqtt311OutMessage( - topicFiltersObj311, - 1) + def subscription1 = Subscription + .minimal(TopicFilter.valueOf("topic/name1"), QoS.AT_LEAST_ONCE) + def requestedSubscription1 = RequestedSubscription + .minimal("topic/name1", QoS.AT_LEAST_ONCE) + def subscription2 = Subscription + .minimal(TopicFilter.valueOf("topic/name2"), QoS.EXACTLY_ONCE) + def requestedSubscription2 = RequestedSubscription + .minimal("topic/name2", QoS.EXACTLY_ONCE,) + def subscriptions = Array.of(subscription1, subscription2) + def requestedSubscriptions = Array.of(requestedSubscription1, requestedSubscription2) + def outMessage = new SubscribeMqtt311OutMessage(1, subscriptions) when: def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(defaultMqtt311Connection, it) + outMessage.write(defaultMqtt311Connection, it) } - def reader = new SubscribeMqttInMessage(0b1000_0000 as byte) - def result = reader.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) + def inMessage = new SubscribeMqttInMessage(0b1000_0000 as byte) + def result = inMessage.read(defaultMqtt311Connection, dataBuffer, dataBuffer.limit()) then: result - reader.messageId == 1 - reader.topicFilters == topicFiltersObj311 - reader.userProperties() == Array.empty(StringPair) - reader.subscriptionId == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + inMessage.messageId() == 1 + inMessage.subscriptions() == requestedSubscriptions + inMessage.userProperties() == Array.empty(StringPair) + inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_UNDEFINED } } 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 0373f1e0..f930c14a 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 @@ -1,29 +1,71 @@ package javasabr.mqtt.network.message.out import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.SubscribeRetainHandling +import javasabr.mqtt.model.subscribtion.RequestedSubscription +import javasabr.mqtt.model.subscribtion.Subscription +import javasabr.mqtt.model.topic.TopicFilter import javasabr.mqtt.network.message.in.SubscribeMqttInMessage +import javasabr.rlib.collections.array.Array import javasabr.rlib.common.util.BufferUtils class SubscribeMqtt5OutMessageTest extends BaseMqttOutMessageTest { - def "should write packet correctly"() { + def "should write message correctly"() { given: - def packet = new SubscribeMqtt5OutMessage( - topicFiltersObj5, + def subscription1 = Subscription + .minimal(TopicFilter.valueOf("topic/name1"), QoS.AT_LEAST_ONCE) + def requestedSubscription1 = RequestedSubscription + .minimal("topic/name1", QoS.AT_LEAST_ONCE) + def subscription2 = new Subscription( + TopicFilter.valueOf("topic/name2"), + 15, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.DO_NOT_SEND, + false, + false) + def requestedSubscription2 = new RequestedSubscription( + "topic/name2", + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.DO_NOT_SEND, + false, + false) + def subscriptions = Array.of(subscription1, subscription2) + def requestedSubscriptions = Array.of(requestedSubscription1, requestedSubscription2) + def outMessage = new SubscribeMqtt5OutMessage( 1, + subscriptions, userProperties, MqttProperties.SUBSCRIPTION_ID_UNDEFINED) when: def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(defaultMqtt5Connection, it) + outMessage.write(defaultMqtt5Connection, it) } - def reader = new SubscribeMqttInMessage(0b1000_0000 as byte) - def result = reader.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + def inMessage = new SubscribeMqttInMessage(0b1000_0000 as byte) + def result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) then: result - reader.messageId == 1 - reader.topicFilters == topicFiltersObj5 - reader.userProperties() == userProperties - reader.subscriptionId == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + inMessage.messageId() == 1 + inMessage.subscriptions() == requestedSubscriptions + inMessage.userProperties() == userProperties + inMessage.subscriptionId() == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + when: + outMessage = new SubscribeMqtt5OutMessage( + 25, + subscriptions, + userProperties, + 35) + dataBuffer = BufferUtils.prepareBuffer(512) { + outMessage.write(defaultMqtt5Connection, it) + } + inMessage = new SubscribeMqttInMessage(0b1000_0000 as byte) + result = inMessage.read(defaultMqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + inMessage.messageId() == 25 + inMessage.subscriptions() == requestedSubscriptions + inMessage.userProperties() == userProperties + inMessage.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 54fe0b69..ddc5a9fd 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 packet correctly"() { given: - def packet = new UnsubscribeAckMqtt311OutMessage(packetId) + def packet = new UnsubscribeAckMqtt311OutMessage(messageId) when: def dataBuffer = BufferUtils.prepareBuffer(512) { packet.write(defaultMqtt311Connection, it) @@ -20,7 +20,7 @@ class UnsubscribeAckMqtt311OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCodes() == Array.empty(UnsubscribeAckReasonCode) - reader.messageId() == packetId + reader.messageId() == messageId reader.userProperties() == Array.empty(StringPair) reader.reason() == "" } 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 0f04821f..83ad7494 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 @@ -8,7 +8,7 @@ class UnsubscribeAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { def "should write packet correctly"() { given: def packet = new UnsubscribeAckMqtt5OutMessage( - packetId, + messageId, unsubscribeAckReasonCodes, userProperties, reasonString) @@ -21,7 +21,7 @@ class UnsubscribeAckMqtt5OutMessageTest extends BaseMqttOutMessageTest { then: result reader.reasonCodes() == unsubscribeAckReasonCodes - reader.messageId() == packetId + reader.messageId() == messageId reader.userProperties() == userProperties 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 7f1b8c13..e6c0b188 100644 --- a/network/src/testFixtures/groovy/javasabr/mqtt/network/NetworkUnitSpecification.groovy +++ b/network/src/testFixtures/groovy/javasabr/mqtt/network/NetworkUnitSpecification.groovy @@ -4,7 +4,9 @@ import javasabr.mqtt.model.* import javasabr.mqtt.model.data.type.StringPair import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter +import javasabr.mqtt.model.subscribtion.Subscription +import javasabr.mqtt.model.topic.TopicFilter +import javasabr.mqtt.model.topic.TopicName import javasabr.mqtt.test.support.UnitSpecification import javasabr.rlib.collections.array.Array import javasabr.rlib.collections.array.IntArray @@ -13,9 +15,6 @@ import spock.lang.Shared import java.nio.charset.StandardCharsets import java.util.concurrent.atomic.AtomicInteger -import static javasabr.mqtt.model.util.TopicUtils.buildTopicFilter -import static javasabr.mqtt.model.util.TopicUtils.buildTopicName - class NetworkUnitSpecification extends UnitSpecification { public static final keepAliveEnabled = true @@ -31,7 +30,7 @@ class NetworkUnitSpecification extends UnitSpecification { public static final willRetain = false public static final mqtt311ClientId = "testMqtt311ClientId" public static final mqtt5ClientId = "testMqtt5ClientId" - public static final packetId = 1234 as short + public static final messageId = 1234 as short public static final userName = "testUser" public static final userPassword = "testPassword".getBytes(StandardCharsets.UTF_8) public static final keepAlive = 120 @@ -40,8 +39,9 @@ class NetworkUnitSpecification extends UnitSpecification { public static final topicAlias = 252 public static final receiveMaxPublishes = 10 public static final maxPacketSize = 1024 - public static final maxStringLength = 1024 + public static final maxStringLength = 256 public static final maxBinarySize = 1024 + public static final maxTopicLevels = 10 public static final topicAliasMaxValue = 32 public static final subscriptionId = 637 public static final subscriptionId2 = 623 @@ -52,21 +52,23 @@ class NetworkUnitSpecification extends UnitSpecification { public static final authMethod = "testAuthMethod" public static final authData = "testAuthData".getBytes(StandardCharsets.UTF_8) public static final reasonString = "reasonString" - public static final publishTopic = buildTopicName("publish/Topic") + public static final publishTopic = TopicName.valueOf("publish/Topic") public static final responseTopic = "response/Topic" public static final topicFilter = "topic/Filter" - public static final topicFilter1Obj311 = new SubscribeTopicFilter(buildTopicFilter(topicFilter), QoS.AT_LEAST_ONCE) - public static final topicFilter1Obj5 = new SubscribeTopicFilter( - buildTopicFilter(topicFilter), + public static final topicFilter1Obj311 = Subscription.minimal(TopicFilter.valueOf(topicFilter), QoS.AT_LEAST_ONCE) + public static final topicFilter1Obj5 = new Subscription( + TopicFilter.valueOf(topicFilter), + 15, QoS.AT_LEAST_ONCE, SubscribeRetainHandling.DO_NOT_SEND, true, false) public static final topicFilter2 = "topic/Filter2" - public static final topicFilter2Obj311 = new SubscribeTopicFilter(buildTopicFilter(topicFilter2), QoS.EXACTLY_ONCE) - public static final topicFilter2Obj5 = new SubscribeTopicFilter( - buildTopicFilter(topicFilter2), + public static final topicFilter2Obj311 = Subscription.minimal(TopicFilter.valueOf(topicFilter2), QoS.EXACTLY_ONCE) + public static final topicFilter2Obj5 = new Subscription( + TopicFilter.valueOf(topicFilter2), + 15, QoS.EXACTLY_ONCE, SubscribeRetainHandling.DO_NOT_SEND, true, @@ -94,7 +96,7 @@ class NetworkUnitSpecification extends UnitSpecification { public static final subscriptionIds = IntArray.of(subscriptionId, subscriptionId2) public static final topicFilters = Array.of(topicFilter, topicFilter2) - public static final topicFiltersObj311 = Array.of(topicFilter1Obj311, topicFilter2Obj311) + 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) @@ -121,6 +123,7 @@ class NetworkUnitSpecification extends UnitSpecification { maxPacketSize, maxStringLength, maxBinarySize, + maxTopicLevels, serverKeepAlive, receiveMaxPublishes, topicAliasMaxValue, @@ -135,6 +138,7 @@ class NetworkUnitSpecification extends UnitSpecification { MqttClientConnectionConfig defaultMqtt311ClientConnectionConfig() { return clientConnectionConfig( + defaultServerConnectionConfig, maxQos, MqttVersion.MQTT_3_1_1, sessionExpiryInterval, @@ -143,16 +147,12 @@ class NetworkUnitSpecification extends UnitSpecification { topicAliasMaxValue, keepAlive, false, - false, - sessionsEnabled, - retainAvailable, - wildcardSubscriptionAvailable, - subscriptionIdAvailable, - sharedSubscriptionAvailable) + false) } MqttClientConnectionConfig defaultMqtt5ClientConnectionConfig() { return clientConnectionConfig( + defaultServerConnectionConfig, maxQos, MqttVersion.MQTT_5, sessionExpiryInterval, @@ -161,12 +161,7 @@ class NetworkUnitSpecification extends UnitSpecification { topicAliasMaxValue, keepAlive, false, - false, - sessionsEnabled, - retainAvailable, - wildcardSubscriptionAvailable, - subscriptionIdAvailable, - sharedSubscriptionAvailable) + false) } MqttConnection mqtt311Connection() { @@ -188,6 +183,7 @@ class NetworkUnitSpecification extends UnitSpecification { int maxPacketSize, int maxStringLength, int maxBinarySize, + int maxTopicLevels, int serverKeepAlive, int receiveMaxPublishes, int topicAliasMaxValue, @@ -203,6 +199,7 @@ class NetworkUnitSpecification extends UnitSpecification { maxPacketSize, maxStringLength, maxBinarySize, + maxTopicLevels, serverKeepAlive, receiveMaxPublishes, topicAliasMaxValue, @@ -216,6 +213,7 @@ class NetworkUnitSpecification extends UnitSpecification { } static MqttClientConnectionConfig clientConnectionConfig( + MqttServerConnectionConfig serverConnectionConfig, QoS maxQos, MqttVersion mqttVersion, long sessionExpiryInterval, @@ -224,13 +222,9 @@ class NetworkUnitSpecification extends UnitSpecification { int topicAliasMaxValue, int keepAlive, boolean requestResponseInformation, - boolean requestProblemInformation, - boolean sessionsEnabled, - boolean retainAvailable, - boolean wildcardSubscriptionAvailable, - boolean subscriptionIdAvailable, - boolean sharedSubscriptionAvailable) { + boolean requestProblemInformation) { return new MqttClientConnectionConfig( + serverConnectionConfig, maxQos, mqttVersion, sessionExpiryInterval, @@ -239,12 +233,7 @@ class NetworkUnitSpecification extends UnitSpecification { topicAliasMaxValue, keepAlive, requestResponseInformation, - requestProblemInformation, - sessionsEnabled, - retainAvailable, - wildcardSubscriptionAvailable, - subscriptionIdAvailable, - sharedSubscriptionAvailable) + requestProblemInformation) } MqttConnection mqttConnection( @@ -252,7 +241,7 @@ class NetworkUnitSpecification extends UnitSpecification { MqttClientConnectionConfig clientConfig, String clientId) { return Stub(MqttConnection) { - isSupported(spock.lang.Specification._) >> { MqttVersion version -> + isSupported(_) >> { MqttVersion version -> clientConfig.mqttVersion().include(version) } serverConnectionConfig() >> serverConfig diff --git a/network/src/testFixtures/groovy/javasabr/mqtt/network/SpecificationNetworkExtensions.groovy b/network/src/testFixtures/groovy/javasabr/mqtt/network/SpecificationNetworkExtensions.groovy index 8772c8e0..79865f27 100644 --- a/network/src/testFixtures/groovy/javasabr/mqtt/network/SpecificationNetworkExtensions.groovy +++ b/network/src/testFixtures/groovy/javasabr/mqtt/network/SpecificationNetworkExtensions.groovy @@ -1,7 +1,7 @@ package javasabr.mqtt.network -import javasabr.mqtt.model.PacketProperty -import javasabr.mqtt.model.data.type.PacketDataType +import javasabr.mqtt.model.MqttMessageProperty +import javasabr.mqtt.model.data.type.MqttDataType import javasabr.mqtt.model.data.type.StringPair import javasabr.mqtt.network.message.out.MqttOutMessage import javasabr.mqtt.network.util.MqttDataUtils @@ -19,26 +19,31 @@ class SpecificationNetworkExtensions extends Specification { protected void writeImpl(MqttConnection connection, ByteBuffer buffer) {} } + static ByteBuffer putByte(ByteBuffer self, int value) { + self.put((byte) value) + return self + } + static ByteBuffer putMbi(ByteBuffer self, int value) { MqttDataUtils.writeMbi(value, self) return self } - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, boolean value) { + static ByteBuffer putProperty(ByteBuffer self, MqttMessageProperty property, boolean value) { return putProperty(self, property, value ? 1 : 0) } - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, long value) { + static ByteBuffer putProperty(ByteBuffer self, MqttMessageProperty property, long value) { writer.writeProperty(self, property, value) return self } - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, byte[] value) { + static ByteBuffer putProperty(ByteBuffer self, MqttMessageProperty property, byte[] value) { writer.writeProperty(self, property, value) return self } - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, String value) { + static ByteBuffer putProperty(ByteBuffer self, MqttMessageProperty property, String value) { writer.writeProperty(self, property, value) return self } @@ -53,10 +58,10 @@ class SpecificationNetworkExtensions extends Specification { return self } - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, Array values) { + static ByteBuffer putProperty(ByteBuffer self, MqttMessageProperty property, Array values) { switch (property.dataType()) { - case PacketDataType.UTF_8_STRING_PAIR: { + case MqttDataType.UTF_8_STRING_PAIR: { writer.writeStringPairProperties(self, property, values as Array) break } @@ -68,7 +73,7 @@ class SpecificationNetworkExtensions extends Specification { return self } - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, IntArray values) { + static ByteBuffer putProperty(ByteBuffer self, MqttMessageProperty property, IntArray values) { values.each { writer.writeProperty(self, property, it) } return self } diff --git a/service/build.gradle b/service/build.gradle index cbd312e2..e5a7ccfe 100644 --- a/service/build.gradle +++ b/service/build.gradle @@ -1,10 +1,13 @@ plugins { id("java-library") id("configure-java") + id("groovy") } dependencies { api projects.network testImplementation projects.testSupport + testImplementation libs.rlib.logger.impl + testImplementation testFixtures(projects.network) } \ No newline at end of file diff --git a/service/src/main/java/javasabr/mqtt/service/PublishDeliveringService.java b/service/src/main/java/javasabr/mqtt/service/PublishDeliveringService.java index 5463cab9..8110b7bf 100644 --- a/service/src/main/java/javasabr/mqtt/service/PublishDeliveringService.java +++ b/service/src/main/java/javasabr/mqtt/service/PublishDeliveringService.java @@ -1,10 +1,10 @@ package javasabr.mqtt.service; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.subscriber.SingleSubscriber; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.service.publish.handler.PublishHandlingResult; public interface PublishDeliveringService { - PublishHandlingResult startDelivering(PublishMqttInMessage publish, SingleSubscriber subscriber); + PublishHandlingResult startDelivering(Publish publish, SingleSubscriber subscriber); } diff --git a/service/src/main/java/javasabr/mqtt/service/PublishReceivingService.java b/service/src/main/java/javasabr/mqtt/service/PublishReceivingService.java index 371dc2e7..8417449a 100644 --- a/service/src/main/java/javasabr/mqtt/service/PublishReceivingService.java +++ b/service/src/main/java/javasabr/mqtt/service/PublishReceivingService.java @@ -1,9 +1,9 @@ package javasabr.mqtt.service; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; public interface PublishReceivingService { - void processReceivedPublish(MqttClient client, PublishMqttInMessage publish); + void processReceivedPublish(MqttClient client, Publish publish); } diff --git a/service/src/main/java/javasabr/mqtt/service/SubscriptionService.java b/service/src/main/java/javasabr/mqtt/service/SubscriptionService.java index 4a1c0d5b..641f49bd 100644 --- a/service/src/main/java/javasabr/mqtt/service/SubscriptionService.java +++ b/service/src/main/java/javasabr/mqtt/service/SubscriptionService.java @@ -1,12 +1,10 @@ package javasabr.mqtt.service; -import java.util.function.BiFunction; -import javasabr.mqtt.model.ActionResult; import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; import javasabr.mqtt.model.subscriber.SingleSubscriber; -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; import javasabr.mqtt.model.subscriber.Subscriber; +import javasabr.mqtt.model.subscribtion.Subscription; import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.model.topic.TopicName; import javasabr.mqtt.network.MqttClient; @@ -19,8 +17,6 @@ */ public interface SubscriptionService { - boolean isValid(TopicName topicName); - MqttClient resolveClient(Subscriber subscriber); default Array findSubscribers(TopicName topicName) { @@ -30,41 +26,24 @@ default Array findSubscribers(TopicName topicName) { Array findSubscribersTo(MutableArray container, TopicName topicName); /** - * Runs function for each topic subscriber - * - * @param topicName topic name - * @param argument additional argument - * @param action function to run - * @return {@link ActionResult} of function - */ - ActionResult forEachTopicSubscriber( - TopicName topicName, - A argument, - BiFunction action); - - /** - * Adds MQTT client to topic filter subscribers + * Subscribes MQTT client to listen to topics. * - * @param mqttClient MQTT client to be added - * @param topicFilters topic filters + * @param client MQTT client which requests subscriptions + * @param subscriptions the list of request to subscribe topics * @return array of subscribe ack reason codes */ - Array subscribe( - MqttClient mqttClient, - Array topicFilters); + Array subscribe(MqttClient client, Array subscriptions); /** - * Removes MQTT client from subscribers by array of topic names + * Removes MQTT client from listening to the topics. * - * @param mqttClient MQTT client to be removed + * @param client MQTT client to be removed * @param topicFilters topic filters * @return array of unsubscribe ack reason codes */ - Array unsubscribe( - MqttClient mqttClient, - Array topicFilters); + Array unsubscribe(MqttClient client, Array topicFilters); - void cleanSubscriptions(MqttClient mqttClient, MqttSession mqttSession); + void cleanSubscriptions(MqttClient client, MqttSession session); - void restoreSubscriptions(MqttClient mqttClient, MqttSession mqttSession); + void restoreSubscriptions(MqttClient client, MqttSession session); } diff --git a/service/src/main/java/javasabr/mqtt/service/TopicService.java b/service/src/main/java/javasabr/mqtt/service/TopicService.java new file mode 100644 index 00000000..fa98a1d3 --- /dev/null +++ b/service/src/main/java/javasabr/mqtt/service/TopicService.java @@ -0,0 +1,14 @@ +package javasabr.mqtt.service; + +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.mqtt.network.MqttClient; + +public interface TopicService { + + TopicFilter createTopicFilter(MqttClient client, String rawTopicFilter); + + boolean isValidTopicFilter(MqttClient client, String rawTopicFilter); + + TopicName createTopicName(MqttClient client, String rawTopicName); +} diff --git a/service/src/main/java/javasabr/mqtt/service/handler/client/AbstractMqttClientReleaseHandler.java b/service/src/main/java/javasabr/mqtt/service/handler/client/AbstractMqttClientReleaseHandler.java index 9cf9b0d6..07dd849a 100644 --- a/service/src/main/java/javasabr/mqtt/service/handler/client/AbstractMqttClientReleaseHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/handler/client/AbstractMqttClientReleaseHandler.java @@ -1,36 +1,42 @@ package javasabr.mqtt.service.handler.client; import javasabr.mqtt.model.MqttClientConnectionConfig; +import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.network.MqttSession; import javasabr.mqtt.network.handler.MqttClientReleaseHandler; import javasabr.mqtt.network.impl.AbstractMqttClient; import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.session.MqttSessionService; import javasabr.rlib.common.util.StringUtils; +import lombok.AccessLevel; import lombok.CustomLog; import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; import reactor.core.publisher.Mono; @CustomLog @RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public abstract class AbstractMqttClientReleaseHandler implements MqttClientReleaseHandler { - private final ClientIdRegistry clientIdRegistry; - private final MqttSessionService sessionService; - private final SubscriptionService subscriptionService; + ClientIdRegistry clientIdRegistry; + MqttSessionService sessionService; + SubscriptionService subscriptionService; @Override public Mono release(UnsafeMqttClient client) { var clientId = client.clientId(); //noinspection unchecked - return releaseImpl((T) client).doOnNext(aVoid -> log.info(clientId, "[%s] Client was released"::formatted)); + return releaseImpl((T) client) + .doOnNext(_ -> log.info(clientId, "[%s] Client was released"::formatted)); } protected Mono releaseImpl(T client) { - var clientId = client.clientId(); + String clientId = client.clientId(); client.clientId(StringUtils.EMPTY); if (StringUtils.isEmpty(clientId)) { @@ -38,21 +44,21 @@ protected Mono releaseImpl(T client) { return Mono.empty(); } - var session = client.session(); - + MqttSession session = client.session(); Mono asyncActions = null; if (session != null) { subscriptionService.cleanSubscriptions(client, session); - MqttClientConnectionConfig connectionConfig = client.connectionConfig(); - if (connectionConfig.sessionsEnabled()) { - asyncActions = sessionService.store(clientId, session, connectionConfig.sessionExpiryInterval()); + MqttClientConnectionConfig clientConfig = client.connectionConfig(); + MqttServerConnectionConfig serverConfig = clientConfig.server(); + if (serverConfig.sessionsEnabled()) { + asyncActions = sessionService.store(clientId, session, clientConfig.sessionExpiryInterval()); client.session(null); } } if (asyncActions != null) { - asyncActions = asyncActions.flatMap(any -> clientIdRegistry.unregister(clientId)); + asyncActions = asyncActions.flatMap(_ -> clientIdRegistry.unregister(clientId)); } else { asyncActions = clientIdRegistry.unregister(clientId); } diff --git a/service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishDeliveringService.java b/service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishDeliveringService.java index c0f27b11..8e0a7529 100644 --- a/service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishDeliveringService.java +++ b/service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishDeliveringService.java @@ -2,8 +2,8 @@ import java.util.Collection; import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.subscriber.SingleSubscriber; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.service.PublishDeliveringService; import javasabr.mqtt.service.publish.handler.MqttPublishOutMessageHandler; import javasabr.mqtt.service.publish.handler.PublishHandlingResult; @@ -25,7 +25,7 @@ public DefaultPublishDeliveringService( int maxIndex = knownPublishOutHandlers .stream() .map(MqttPublishOutMessageHandler::qos) - .mapToInt(QoS::index) + .mapToInt(QoS::level) .max() .orElse(0); @@ -33,11 +33,11 @@ public DefaultPublishDeliveringService( for (MqttPublishOutMessageHandler knownPublishOutHandler : knownPublishOutHandlers) { QoS qos = knownPublishOutHandler.qos(); - if (handlers[qos.index()] != null) { + if (handlers[qos.level()] != null) { throw new IllegalArgumentException( "Found duplicate MqttPublishOutMessageHandler:[" + knownPublishOutHandler + "]"); } - handlers[qos.index()] = knownPublishOutHandler; + handlers[qos.level()] = knownPublishOutHandler; } this.publishOutMessageHandlers = handlers; @@ -45,10 +45,10 @@ public DefaultPublishDeliveringService( } @Override - public PublishHandlingResult startDelivering(PublishMqttInMessage publish, SingleSubscriber subscriber) { + public PublishHandlingResult startDelivering(Publish publish, SingleSubscriber subscriber) { try { //noinspection DataFlowIssue - return publishOutMessageHandlers[subscriber.getQos().index()].handle(publish, subscriber); + return publishOutMessageHandlers[subscriber.qos().level()].handle(publish, subscriber); } catch (IndexOutOfBoundsException | NullPointerException ex) { log.warning(publish, "Received not supported publish message:[%s]"::formatted); return PublishHandlingResult.UNSPECIFIED_ERROR; diff --git a/service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishReceivingService.java b/service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishReceivingService.java index 0cd50e08..3ec66b26 100644 --- a/service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishReceivingService.java +++ b/service/src/main/java/javasabr/mqtt/service/impl/DefaultPublishReceivingService.java @@ -2,8 +2,8 @@ import java.util.Collection; import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.service.PublishReceivingService; import javasabr.mqtt.service.publish.handler.MqttPublishInMessageHandler; import lombok.AccessLevel; @@ -24,7 +24,7 @@ public DefaultPublishReceivingService( int maxIndex = knownPublishInHandlers .stream() .map(MqttPublishInMessageHandler::qos) - .mapToInt(QoS::index) + .mapToInt(QoS::level) .max() .orElse(0); @@ -32,11 +32,11 @@ public DefaultPublishReceivingService( for (MqttPublishInMessageHandler knownPublishInHandler : knownPublishInHandlers) { QoS qos = knownPublishInHandler.qos(); - if (handlers[qos.index()] != null) { + if (handlers[qos.level()] != null) { throw new IllegalArgumentException( "Found duplicate MqttPublishInMessageHandler:[" + knownPublishInHandler + "]"); } - handlers[qos.index()] = knownPublishInHandler; + handlers[qos.level()] = knownPublishInHandler; } this.publishInHandlers = handlers; @@ -44,13 +44,14 @@ public DefaultPublishReceivingService( } @Override - public void processReceivedPublish(MqttClient client, PublishMqttInMessage publish) { + public void processReceivedPublish(MqttClient client, Publish publish) { + log.debug(client.clientId(), publish, "[%s] Processing received publish: [%s]"::formatted); QoS qos = publish.qos(); try { //noinspection DataFlowIssue - publishInHandlers[qos.index()].handle(client, publish); + publishInHandlers[qos.level()].handle(client, publish); } catch (IndexOutOfBoundsException | NullPointerException ex) { - log.warning(publish, "Received not supported publish message:[%s]"::formatted); + log.warning(client.clientId(), publish, "[%s] Received not supported publish message:[%s]"::formatted); } } diff --git a/service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java b/service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java new file mode 100644 index 00000000..f8d99e44 --- /dev/null +++ b/service/src/main/java/javasabr/mqtt/service/impl/DefaultTopicService.java @@ -0,0 +1,85 @@ +package javasabr.mqtt.service.impl; + +import javasabr.mqtt.model.MqttServerConnectionConfig; +import javasabr.mqtt.model.topic.SharedTopicFilter; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.mqtt.model.topic.TopicValidator; +import javasabr.mqtt.network.MqttClient; +import javasabr.mqtt.service.TopicService; +import lombok.CustomLog; + +@CustomLog +public class DefaultTopicService implements TopicService { + + @Override + public TopicFilter createTopicFilter(MqttClient client, String rawTopicFilter) { + if (SharedTopicFilter.isShared(rawTopicFilter)) { + return createSharedTopicFilter(client, rawTopicFilter); + } + return createStandardTopicFilter(client, rawTopicFilter); + } + + @Override + public boolean isValidTopicFilter(MqttClient client, String rawTopicFilter) { + if (SharedTopicFilter.isShared(rawTopicFilter)) { + if (!TopicValidator.validateSharedTopicFilter(rawTopicFilter)) { + log.warning(client.clientId(), rawTopicFilter, "[%s] Invalid shared topic filter:[%s]"::formatted); + return false; + } + return true; + } + if (!TopicValidator.validateTopicFilter(rawTopicFilter)) { + log.warning(client.clientId(), rawTopicFilter, "[%s] Invalid topic filter:[%s]"::formatted); + return false; + } + return true; + } + + @Override + public TopicName createTopicName(MqttClient client, String rawTopicName) { + if (!TopicValidator.validateTopicName(rawTopicName)) { + log.warning(client.clientId(), rawTopicName, "[%s] Invalid topic name:[%s]"::formatted); + return TopicName.INVALID_TOPIC_NAME; + } + return TopicName.valueOf(rawTopicName); + } + + private TopicFilter createSharedTopicFilter(MqttClient client, String rawTopicFilter) { + if (!TopicValidator.validateSharedTopicFilter(rawTopicFilter)) { + log.warning(client.clientId(), rawTopicFilter, "[%s] Invalid shared topic filter:[%s]"::formatted); + return TopicFilter.INVALID_TOPIC_FILTER; + } + + SharedTopicFilter sharedTopicFilter = SharedTopicFilter.valueOf(rawTopicFilter); + MqttServerConnectionConfig connectionConfig = client + .connectionConfig() + .server(); + + if (sharedTopicFilter.levelsCount() > connectionConfig.maxTopicLevels()) { + log.warning(client.clientId(), rawTopicFilter, "[%s] Too deep shared topic filter:[%s]"::formatted); + return TopicFilter.INVALID_TOPIC_FILTER; + } + + return sharedTopicFilter; + } + + private TopicFilter createStandardTopicFilter(MqttClient client, String rawTopicFilter) { + if (!TopicValidator.validateTopicFilter(rawTopicFilter)) { + log.warning(client.clientId(), rawTopicFilter, "[%s] Invalid topic filter:[%s]"::formatted); + return TopicFilter.INVALID_TOPIC_FILTER; + } + + TopicFilter topicFilter = TopicFilter.valueOf(rawTopicFilter); + MqttServerConnectionConfig connectionConfig = client + .connectionConfig() + .server(); + + if (topicFilter.levelsCount() > connectionConfig.maxTopicLevels()) { + log.warning(client.clientId(), rawTopicFilter, "[%s] Too deep topic filter:[%s]"::formatted); + return TopicFilter.INVALID_TOPIC_FILTER; + } + + return topicFilter; + } +} diff --git a/service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java b/service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java new file mode 100644 index 00000000..c9420bbb --- /dev/null +++ b/service/src/main/java/javasabr/mqtt/service/impl/InMemorySubscriptionService.java @@ -0,0 +1,147 @@ +package javasabr.mqtt.service.impl; + +import static javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode.NO_SUBSCRIPTION_EXISTED; +import static javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode.SUCCESS; + +import javasabr.mqtt.model.MqttClientConnectionConfig; +import javasabr.mqtt.model.MqttServerConnectionConfig; +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; +import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.model.subscriber.Subscriber; +import javasabr.mqtt.model.subscribtion.Subscription; +import javasabr.mqtt.model.topic.SharedTopicFilter; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.mqtt.model.topic.tree.ConcurrentTopicTree; +import javasabr.mqtt.network.MqttClient; +import javasabr.mqtt.network.MqttSession; +import javasabr.mqtt.service.SubscriptionService; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.ArrayFactory; +import javasabr.rlib.collections.array.MutableArray; +import lombok.AccessLevel; +import lombok.CustomLog; +import lombok.experimental.FieldDefaults; + +/** + * In memory subscription service based on {@link ConcurrentTopicTree} + */ +@CustomLog +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class InMemorySubscriptionService implements SubscriptionService { + + ConcurrentTopicTree topicTree; + + public InMemorySubscriptionService() { + this.topicTree = new ConcurrentTopicTree(); + } + + @Override + public MqttClient resolveClient(Subscriber subscriber) { + if (subscriber instanceof SingleSubscriber single) { + return (MqttClient) single.owner(); + } + throw new IllegalArgumentException("Unexpected subscriber: " + subscriber); + } + + @Override + public Array findSubscribersTo(MutableArray container, TopicName topicName) { + Array matched = topicTree.matches(topicName); + container.addAll(matched); + return container; + } + + @Override + public Array subscribe( + MqttClient client, + Array subscriptions) { + + MutableArray subscribeResults = ArrayFactory.mutableArray( + SubscribeAckReasonCode.class, + subscriptions.size()); + + MqttSession session = client.session(); + if (session == null) { + // without session just fill error for each topic filter + log.warning(client.clientId(), "[%s] Cannot add subscription for client without session"::formatted); + for (int i = 0, length = subscriptions.size(); i < length; i++) { + subscribeResults.add(SubscribeAckReasonCode.UNSPECIFIED_ERROR); + } + return subscribeResults; + } + + for (Subscription subscription : subscriptions) { + subscribeResults.add(addSubscription(client, session, subscription)); + } + + return subscribeResults; + } + + private SubscribeAckReasonCode addSubscription(MqttClient client, MqttSession session, Subscription subscription) { + MqttClientConnectionConfig clientConfig = client.connectionConfig(); + MqttServerConnectionConfig serverConfig = clientConfig.server(); + TopicFilter topicFilter = subscription.topicFilter(); + if (topicFilter.isInvalid()) { + return SubscribeAckReasonCode.TOPIC_FILTER_INVALID; + } else if (!serverConfig.sharedSubscriptionAvailable() && topicFilter instanceof SharedTopicFilter) { + return SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED; + } else if (!serverConfig.wildcardSubscriptionAvailable() && topicFilter.wildcard()) { + return SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED; + } + session.storeSubscription(subscription); + topicTree.subscribe(client, subscription); + return subscription.qos().subscribeAckReasonCode(); + } + + @Override + public Array unsubscribe(MqttClient client, Array topicFilters) { + + MutableArray unsubscribeResults = ArrayFactory.mutableArray( + UnsubscribeAckReasonCode.class, + topicFilters.size()); + + MqttSession session = client.session(); + if (session == null) { + // without session just fill error for each topic filter + log.warning(client.clientId(), "[%s] Cannot add subscription for client without session"::formatted); + for (int i = 0, length = topicFilters.size(); i < length; i++) { + unsubscribeResults.add(UnsubscribeAckReasonCode.UNSPECIFIED_ERROR); + } + return unsubscribeResults; + } + + for (TopicFilter topicFilter : topicFilters) { + unsubscribeResults.add(removeSubscription(client, session, topicFilter)); + } + + return unsubscribeResults; + } + + private UnsubscribeAckReasonCode removeSubscription(MqttClient client, MqttSession session, TopicFilter topicFilter) { + if (topicFilter.isInvalid()) { + return UnsubscribeAckReasonCode.TOPIC_FILTER_INVALID; + } else if (topicTree.unsubscribe(client, topicFilter)) { + session.removeSubscription(topicFilter); + return SUCCESS; + } else { + return NO_SUBSCRIPTION_EXISTED; + } + } + + @Override + public void cleanSubscriptions(MqttClient client, MqttSession session) { + Array subscriptions = session.storedSubscriptions(); + for (Subscription subscription : subscriptions) { + topicTree.unsubscribe(client, subscription.topicFilter()); + } + } + + @Override + public void restoreSubscriptions(MqttClient client, MqttSession session) { + Array subscriptions = session.storedSubscriptions(); + for (Subscription subscription : subscriptions) { + topicTree.subscribe(client, subscription); + } + } +} diff --git a/service/src/main/java/javasabr/mqtt/service/impl/SimpleSubscriptionService.java b/service/src/main/java/javasabr/mqtt/service/impl/SimpleSubscriptionService.java deleted file mode 100644 index 2d1ee4bf..00000000 --- a/service/src/main/java/javasabr/mqtt/service/impl/SimpleSubscriptionService.java +++ /dev/null @@ -1,141 +0,0 @@ -package javasabr.mqtt.service.impl; - -import static javasabr.mqtt.model.ActionResult.EMPTY; -import static javasabr.mqtt.model.ActionResult.FAILED; -import static javasabr.mqtt.model.reason.code.SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED; -import static javasabr.mqtt.model.reason.code.SubscribeAckReasonCode.UNSPECIFIED_ERROR; -import static javasabr.mqtt.model.reason.code.SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED; -import static javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode.NO_SUBSCRIPTION_EXISTED; -import static javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode.SUCCESS; -import static javasabr.mqtt.model.util.TopicUtils.hasWildcard; -import static javasabr.mqtt.model.util.TopicUtils.isShared; - -import java.util.function.BiFunction; -import javasabr.mqtt.model.ActionResult; -import javasabr.mqtt.model.MqttClientConnectionConfig; -import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; -import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; -import javasabr.mqtt.model.subscriber.SingleSubscriber; -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; -import javasabr.mqtt.model.subscriber.Subscriber; -import javasabr.mqtt.model.topic.TopicFilter; -import javasabr.mqtt.model.topic.TopicName; -import javasabr.mqtt.model.topic.TopicSubscribers; -import javasabr.mqtt.model.util.TopicUtils; -import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.MqttSession; -import javasabr.mqtt.service.SubscriptionService; -import javasabr.rlib.collections.array.Array; -import javasabr.rlib.collections.array.ArrayCollectors; -import javasabr.rlib.collections.array.MutableArray; -import lombok.AccessLevel; -import lombok.experimental.FieldDefaults; -import org.jspecify.annotations.Nullable; - -/** - * Simple subscription service - */ -@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class SimpleSubscriptionService implements SubscriptionService { - - TopicSubscribers topicSubscribers = new TopicSubscribers(); - - @Override - public boolean isValid(TopicName topicName) { - return !TopicUtils.isInvalid(topicName); - } - - @Override - public MqttClient resolveClient(Subscriber subscriber) { - if (subscriber instanceof SingleSubscriber ss) { - return (MqttClient) ss.getUser(); - } - throw new IllegalArgumentException("Unsupported: " + subscriber); - } - - @Override - public Array findSubscribersTo(MutableArray container, TopicName topicName) { - Array matched = topicSubscribers.matches(topicName); - container.addAll(matched); - return container; - } - - @Override - public ActionResult forEachTopicSubscriber( - TopicName topicName, - A arg1, - BiFunction action) { - if (TopicUtils.isInvalid(topicName)) { - return FAILED; - } - ActionResult result = EMPTY; - for (var subscriber : topicSubscribers.matches(topicName)) { - result = result.and(action.apply(subscriber, arg1)); - } - return result; - } - - @Override - public Array subscribe(MqttClient mqttClient, Array topicFilters) { - return topicFilters - .stream() - .map(topicFilter -> addSubscription(topicFilter, mqttClient)) - .collect(ArrayCollectors.toArray(SubscribeAckReasonCode.class)); - } - - @Nullable - private SubscribeAckReasonCode addSubscription(SubscribeTopicFilter subscribe, MqttClient client) { - MqttSession session = client.session(); - if (session == null) { - return null; - } - - MqttClientConnectionConfig connectionConfig = client.connectionConfig(); - TopicFilter topic = subscribe.getTopicFilter(); - - if (!connectionConfig.sharedSubscriptionAvailable() && isShared(topic)) { - return SHARED_SUBSCRIPTIONS_NOT_SUPPORTED; - } else if (!connectionConfig.wildcardSubscriptionAvailable() && hasWildcard(topic)) { - return WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED; - } else if (TopicUtils.isInvalid(topic)) { - return UNSPECIFIED_ERROR; - } else { - session.addSubscriber(subscribe); - topicSubscribers.addSubscriber(client, subscribe); - return subscribe - .getQos() - .subscribeAckReasonCode(); - } - } - - @Override - public Array unsubscribe(MqttClient mqttClient, Array topicFilters) { - return topicFilters - .stream() - .map(topicFilter -> removeSubscription(topicFilter, mqttClient)) - .collect(ArrayCollectors.toArray(UnsubscribeAckReasonCode.class)); - } - - @Nullable - private UnsubscribeAckReasonCode removeSubscription(TopicFilter topic, MqttClient client) { - var session = client.session(); - if (session == null) { - return null; - } else if (TopicUtils.isInvalid(topic)) { - return UnsubscribeAckReasonCode.UNSPECIFIED_ERROR; - } else if (topicSubscribers.removeSubscriber(client, topic)) { - session.removeSubscriber(topic); - return SUCCESS; - } else { - return NO_SUBSCRIPTION_EXISTED; - } - } - - public void cleanSubscriptions(MqttClient mqttClient, MqttSession mqttSession) { - mqttSession.forEachTopicFilter(topicSubscribers, mqttClient, TopicSubscribers::removeSubscriber); - } - - public void restoreSubscriptions(MqttClient mqttClient, MqttSession mqttSession) { - mqttSession.forEachTopicFilter(topicSubscribers, mqttClient, TopicSubscribers::addSubscriber); - } -} diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java index b71a5a06..8137aaa4 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/AbstractMqttInMessageHandler.java @@ -1,8 +1,12 @@ package javasabr.mqtt.service.message.handler.impl; +import javasabr.mqtt.model.exception.MalformedProtocolMqttException; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; import javasabr.mqtt.network.MqttClient; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.message.in.MqttInMessage; +import javasabr.mqtt.network.message.out.MqttOutMessage; +import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.message.handler.MqttInMessageHandler; import lombok.AccessLevel; import lombok.CustomLog; @@ -12,27 +16,46 @@ @CustomLog @RequiredArgsConstructor @FieldDefaults(level = AccessLevel.PROTECTED, makeFinal = true) -public abstract class AbstractMqttInMessageHandler +public abstract class AbstractMqttInMessageHandler implements MqttInMessageHandler { Class expectedClient; - Class

expectedNetworkPacket; + Class expectedNetworkPacket; + MessageOutFactoryService messageOutFactoryService; @Override - public void processReceived(MqttConnection connection, MqttInMessage networkPacket) { + public void processReceived(MqttConnection connection, MqttInMessage message) { MqttClient client = connection.client(); if (!expectedClient.isInstance(client)) { log.warning(client, "Received not expected client:[%s]"::formatted); return; - } else if (!expectedNetworkPacket.isInstance(networkPacket)) { - log.warning(networkPacket, "Received not expected network packet:[%s]"::formatted); + } else if (!expectedNetworkPacket.isInstance(message)) { + log.warning(message, "Received not expected network packet:[%s]"::formatted); return; } - processReceived( - connection, - expectedClient.cast(client), - expectedNetworkPacket.cast(networkPacket)); + + C castedClient = expectedClient.cast(client); + M castedMessage = expectedNetworkPacket.cast(message); + if (checkMessageException(connection, castedClient, castedMessage)) { + return; + } + processReceived(connection, castedClient, castedMessage); } - protected abstract void processReceived(MqttConnection connection, C client, P networkPacket); + protected abstract void processReceived(MqttConnection connection, C client, M message); + + protected boolean checkMessageException(MqttConnection connection, C client, M message) { + Exception exception = message.exception(); + if (exception instanceof MalformedProtocolMqttException) { + // send feedback and close connection + MqttOutMessage feedback = messageOutFactoryService + .resolveFactory(client) + .newDisconnect(client, DisconnectReasonCode.MALFORMED_PACKET); + client + .sendWithFeedback(feedback) + .thenAccept(_ -> connection.close()); + return true; + } + return false; + } } diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java index 3335ba51..8c10076c 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/ConnectInMqttInMessageHandler.java @@ -1,8 +1,8 @@ package javasabr.mqtt.service.message.handler.impl; import static javasabr.mqtt.base.util.ReactorUtils.ifTrue; -import static javasabr.mqtt.model.MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED; -import static javasabr.mqtt.model.MqttProperties.RECEIVE_MAXIMUM_UNDEFINED; +import static javasabr.mqtt.model.MqttProperties.MAXIMUM_MESSAGE_SIZE_UNDEFINED; +import static javasabr.mqtt.model.MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_UNDEFINED; import static javasabr.mqtt.model.MqttProperties.SERVER_KEEP_ALIVE_DISABLED; import static javasabr.mqtt.model.MqttProperties.SESSION_EXPIRY_INTERVAL_DISABLED; import static javasabr.mqtt.model.MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED; @@ -15,7 +15,7 @@ import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.MqttVersion; import javasabr.mqtt.model.exception.ConnectionRejectException; -import javasabr.mqtt.model.exception.MalformedPacketMqttException; +import javasabr.mqtt.model.exception.MalformedProtocolMqttException; import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; import javasabr.mqtt.network.MqttClient; import javasabr.mqtt.network.MqttConnection; @@ -23,6 +23,7 @@ import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.ConnectMqttInMessage; +import javasabr.mqtt.network.message.out.MqttOutMessage; import javasabr.mqtt.service.AuthenticationService; import javasabr.mqtt.service.ClientIdRegistry; import javasabr.mqtt.service.MessageOutFactoryService; @@ -42,7 +43,6 @@ public class ConnectInMqttInMessageHandler extends AbstractMqttInMessageHandler< AuthenticationService authenticationService; MqttSessionService sessionService; SubscriptionService subscriptionService; - MessageOutFactoryService messageOutFactoryService; public ConnectInMqttInMessageHandler( ClientIdRegistry clientIdRegistry, @@ -50,12 +50,11 @@ public ConnectInMqttInMessageHandler( MqttSessionService sessionService, SubscriptionService subscriptionService, MessageOutFactoryService messageOutFactoryService) { - super(ExternalMqttClient.class, ConnectMqttInMessage.class); + super(ExternalMqttClient.class, ConnectMqttInMessage.class, messageOutFactoryService); this.clientIdRegistry = clientIdRegistry; this.authenticationService = authenticationService; this.sessionService = sessionService; this.subscriptionService = subscriptionService; - this.messageOutFactoryService = messageOutFactoryService; } @Override @@ -67,16 +66,14 @@ public MqttMessageType messageType() { protected void processReceived( MqttConnection connection, ExternalMqttClient client, - ConnectMqttInMessage networkPacket) { - - if (checkPacketException(client, networkPacket)) { - return; - } - resolveClientConnectionConfig(client, networkPacket); + ConnectMqttInMessage message) { + resolveClientConnectionConfig(client, message); authenticationService - .auth(networkPacket.username(), networkPacket.password()) - .flatMap(ifTrue(client, networkPacket, this::registerClient, BAD_USER_NAME_OR_PASSWORD, connectAckReasonCode -> reject(client, connectAckReasonCode))) - .flatMap(ifTrue(client, networkPacket, this::restoreSession, CLIENT_IDENTIFIER_NOT_VALID, connectAckReasonCode -> reject(client, connectAckReasonCode))) + .auth(message.username(), message.password()) + .flatMap(ifTrue(client, + message, this::registerClient, BAD_USER_NAME_OR_PASSWORD, connectAckReasonCode -> reject(client, connectAckReasonCode))) + .flatMap(ifTrue(client, + message, this::restoreSession, CLIENT_IDENTIFIER_NOT_VALID, connectAckReasonCode -> reject(client, connectAckReasonCode))) .subscribe(); } @@ -146,14 +143,14 @@ private void resolveClientConnectionConfig(MqttClient.UnsafeMqttClient client, C } // select result receive max - int receiveMaxPublishes = packet.receiveMaxPublishes() == RECEIVE_MAXIMUM_UNDEFINED + int receiveMaxPublishes = packet.receiveMaxPublishes() == RECEIVE_MAXIMUM_PUBLISHES_UNDEFINED ? serverConfig.receiveMaxPublishes() : Math.min(packet.receiveMaxPublishes(), serverConfig.receiveMaxPublishes()); // select result maximum packet size - var maximumPacketSize = packet.maxPacketSize() == MAXIMUM_PACKET_SIZE_UNDEFINED - ? serverConfig.maxPacketSize() - : Math.min(packet.maxPacketSize(), serverConfig.maxPacketSize()); + var maximumPacketSize = packet.maxPacketSize() == MAXIMUM_MESSAGE_SIZE_UNDEFINED + ? serverConfig.maxMessageSize() + : Math.min(packet.maxPacketSize(), serverConfig.maxMessageSize()); // select result topic alias maximum var topicAliasMaxValue = packet.topicAliasMaxValue() == TOPIC_ALIAS_MAXIMUM_UNDEFINED @@ -161,6 +158,7 @@ private void resolveClientConnectionConfig(MqttClient.UnsafeMqttClient client, C : Math.min(packet.topicAliasMaxValue(), serverConfig.topicAliasMaxValue()); connection.configure(new MqttClientConnectionConfig( + serverConfig, serverConfig.maxQos(), packet.mqttVersion(), sessionExpiryInterval, @@ -169,12 +167,7 @@ private void resolveClientConnectionConfig(MqttClient.UnsafeMqttClient client, C topicAliasMaxValue, keepAlive, packet.requestResponseInformation(), - packet.requestProblemInformation(), - serverConfig.sessionsEnabled(), - serverConfig.retainAvailable(), - serverConfig.wildcardSubscriptionAvailable(), - serverConfig.subscriptionIdAvailable(), - serverConfig.sharedSubscriptionAvailable())); + packet.requestProblemInformation())); } private Mono onConnected( @@ -224,19 +217,29 @@ private boolean onSentConnAck(MqttClient.UnsafeMqttClient client, MqttSession se return true; } - private boolean checkPacketException(MqttClient.UnsafeMqttClient client, ConnectMqttInMessage packet) { - Exception exception = packet.exception(); + @Override + protected boolean checkMessageException( + MqttConnection connection, + ExternalMqttClient client, + ConnectMqttInMessage message) { + Exception exception = message.exception(); if (exception instanceof ConnectionRejectException cre) { - client.send(messageOutFactoryService + MqttOutMessage feedback = messageOutFactoryService .resolveFactory(client) - .newConnectAck(client, cre.getReasonCode())); + .newConnectAck(client, cre.getReasonCode()); + client + .sendWithFeedback(feedback) + .thenAccept(_ -> connection.close()); return true; - } else if (exception instanceof MalformedPacketMqttException) { - client.send(messageOutFactoryService + } else if (exception instanceof MalformedProtocolMqttException) { + MqttOutMessage feedback = messageOutFactoryService .resolveFactory(client) - .newConnectAck(client, ConnectAckReasonCode.MALFORMED_PACKET)); + .newConnectAck(client, ConnectAckReasonCode.MALFORMED_PACKET); + client + .sendWithFeedback(feedback) + .thenAccept(_ -> connection.close()); return true; } - return false; + return super.checkMessageException(connection, client, message); } } diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/DisconnectMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/DisconnectMqttInMessageHandler.java index 5a0142ce..74209886 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/DisconnectMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/DisconnectMqttInMessageHandler.java @@ -5,13 +5,14 @@ import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.DisconnectMqttInMessage; +import javasabr.mqtt.service.MessageOutFactoryService; import lombok.CustomLog; @CustomLog public class DisconnectMqttInMessageHandler extends AbstractMqttInMessageHandler { - public DisconnectMqttInMessageHandler() { - super(ExternalMqttClient.class, DisconnectMqttInMessage.class); + public DisconnectMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { + super(ExternalMqttClient.class, DisconnectMqttInMessage.class, messageOutFactoryService); } @Override @@ -23,8 +24,8 @@ public MqttMessageType messageType() { protected void processReceived( MqttConnection connection, ExternalMqttClient client, - DisconnectMqttInMessage networkPacket) { - DisconnectReasonCode reasonCode = networkPacket.reasonCode(); + DisconnectMqttInMessage message) { + DisconnectReasonCode reasonCode = message.reasonCode(); if (reasonCode == DisconnectReasonCode.NORMAL_DISCONNECTION) { log.info(client.clientId(), "Disconnect client:[%s]"::formatted); } else { diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java index 505a3b30..fe136dce 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PendingOutResponseMqttInMessageHandler.java @@ -1,23 +1,26 @@ package javasabr.mqtt.service.message.handler.impl; +import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.MqttSession; import javasabr.mqtt.network.impl.ExternalMqttClient; -import javasabr.mqtt.network.message.HasMessageId; import javasabr.mqtt.network.message.in.MqttInMessage; +import javasabr.mqtt.service.MessageOutFactoryService; -public abstract class PendingOutResponseMqttInMessageHandler

+public abstract class PendingOutResponseMqttInMessageHandler

extends AbstractMqttInMessageHandler { - protected PendingOutResponseMqttInMessageHandler(Class

expectedNetworkPacket) { - super(ExternalMqttClient.class, expectedNetworkPacket); + protected PendingOutResponseMqttInMessageHandler( + Class

expectedNetworkPacket, + MessageOutFactoryService messageOutFactoryService) { + super(ExternalMqttClient.class, expectedNetworkPacket, messageOutFactoryService); } @Override - protected void processReceived(MqttConnection connection, ExternalMqttClient client, P networkPacket) { + protected void processReceived(MqttConnection connection, ExternalMqttClient client, P message) { MqttSession session = client.session(); if (session != null) { - session.updateOutPendingPacket(client, networkPacket); + session.updateOutPendingPacket(client, message); } } } diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishAckMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishAckMqttInMessageHandler.java index 42718dfe..af453173 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishAckMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishAckMqttInMessageHandler.java @@ -2,11 +2,12 @@ import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.PublishAckMqttInMessage; +import javasabr.mqtt.service.MessageOutFactoryService; public class PublishAckMqttInMessageHandler extends PendingOutResponseMqttInMessageHandler { - public PublishAckMqttInMessageHandler() { - super(PublishAckMqttInMessage.class); + public PublishAckMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { + super(PublishAckMqttInMessage.class, messageOutFactoryService); } @Override diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishCompleteMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishCompleteMqttInMessageHandler.java index c4709854..42eb3293 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishCompleteMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishCompleteMqttInMessageHandler.java @@ -2,12 +2,13 @@ import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.PublishCompleteMqttInMessage; +import javasabr.mqtt.service.MessageOutFactoryService; public class PublishCompleteMqttInMessageHandler extends PendingOutResponseMqttInMessageHandler { - public PublishCompleteMqttInMessageHandler() { - super(PublishCompleteMqttInMessage.class); + public PublishCompleteMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { + super(PublishCompleteMqttInMessage.class, messageOutFactoryService); } @Override diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java index d497c5e1..d9b2652e 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishMqttInMessageHandler.java @@ -1,29 +1,96 @@ package javasabr.mqtt.service.message.handler.impl; +import javasabr.mqtt.model.PayloadFormat; +import javasabr.mqtt.model.publishing.Publish; +import javasabr.mqtt.model.reason.code.PublishAckReasonCode; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.mqtt.model.topic.TopicValidator; import javasabr.mqtt.network.MqttConnection; +import javasabr.mqtt.network.MqttSession; import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.PublishMqttInMessage; +import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.PublishReceivingService; +import javasabr.mqtt.service.TopicService; +import javasabr.rlib.collections.array.IntArray; import lombok.AccessLevel; +import lombok.CustomLog; import lombok.experimental.FieldDefaults; +@CustomLog @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class PublishMqttInMessageHandler extends AbstractMqttInMessageHandler { +public class PublishMqttInMessageHandler + extends AbstractMqttInMessageHandler { PublishReceivingService publishReceivingService; + TopicService topicService; - public PublishMqttInMessageHandler(PublishReceivingService publishReceivingService) { - super(ExternalMqttClient.class, PublishMqttInMessage.class); + public PublishMqttInMessageHandler( + PublishReceivingService publishReceivingService, + MessageOutFactoryService messageOutFactoryService, + TopicService topicService) { + super(ExternalMqttClient.class, PublishMqttInMessage.class, messageOutFactoryService); this.publishReceivingService = publishReceivingService; + this.topicService = topicService; } @Override protected void processReceived( MqttConnection connection, ExternalMqttClient client, - PublishMqttInMessage networkPacket) { - publishReceivingService.processReceivedPublish(client, networkPacket); + PublishMqttInMessage message) { + + MqttSession session = client.session(); + if (session == null) { + log.warning(client.clientId(), "[%s] Client has no any session..."::formatted); + return; + } + + int messageId = message.messageId(); + + if (messageId > 0 && session.hasInPending(messageId)) { + client.send(messageOutFactoryService + .resolveFactory(client) + .newPublishAck(messageId, PublishAckReasonCode.PACKET_IDENTIFIER_IN_USE)); + log.warning(client.clientId(), messageId, "[%s] Client provided already in use messageId:[%s]..."::formatted); + return; + } + + String rawTopicName = message.rawTopicName(); + + if (!TopicValidator.validateTopicName(rawTopicName)) { + client.send(messageOutFactoryService + .resolveFactory(client) + .newPublishAck(messageId, PublishAckReasonCode.TOPIC_NAME_INVALID)); + log.warning(client.clientId(), rawTopicName, "[%s] Client provided invalid topic name:[%s]..."::formatted); + return; + } + + TopicName topicName = topicService.createTopicName(client, rawTopicName); + + // TODO + byte[] payload = message.payload(); + int topicAlias = message.topicAlias(); + IntArray subscriptionIds = message.subscriptionIds(); + String rawResponseTopicName = message.rawResponseTopicName(); + PayloadFormat payloadFormat = message.payloadFormat(); + + publishReceivingService.processReceivedPublish(client, new Publish( + messageId, + message.qos(), + topicName, + null, + payload, + message.duplicate(), + message.retained(), + message.contentType(), + message.subscriptionIds(), + message.correlationData(), + message.messageExpiryInterval(), + topicAlias, + payloadFormat, + message.userProperties())); } @Override diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReceiveMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReceiveMqttInMessageHandler.java index 39f2ed7e..7a0b29f8 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReceiveMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReceiveMqttInMessageHandler.java @@ -2,12 +2,13 @@ import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.PublishReceivedMqttInMessage; +import javasabr.mqtt.service.MessageOutFactoryService; public class PublishReceiveMqttInMessageHandler extends PendingOutResponseMqttInMessageHandler { - public PublishReceiveMqttInMessageHandler() { - super(PublishReceivedMqttInMessage.class); + public PublishReceiveMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { + super(PublishReceivedMqttInMessage.class, messageOutFactoryService); } @Override diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java index 77ff6195..4f0b3cae 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/PublishReleaseMqttInMessageHandler.java @@ -5,12 +5,13 @@ import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.PublishReleaseMqttInMessage; +import javasabr.mqtt.service.MessageOutFactoryService; public class PublishReleaseMqttInMessageHandler extends AbstractMqttInMessageHandler { - public PublishReleaseMqttInMessageHandler() { - super(ExternalMqttClient.class, PublishReleaseMqttInMessage.class); + public PublishReleaseMqttInMessageHandler(MessageOutFactoryService messageOutFactoryService) { + super(ExternalMqttClient.class, PublishReleaseMqttInMessage.class, messageOutFactoryService); } @Override @@ -22,10 +23,10 @@ public MqttMessageType messageType() { protected void processReceived( MqttConnection connection, ExternalMqttClient client, - PublishReleaseMqttInMessage networkPacket) { + PublishReleaseMqttInMessage message) { MqttSession session = client.session(); if (session != null) { - session.updateInPendingPacket(client, networkPacket); + session.updateInPendingPacket(client, message); } } } diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java index af0731e6..968a367b 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/SubscribeMqttInMessageHandler.java @@ -1,20 +1,28 @@ package javasabr.mqtt.service.message.handler.impl; -import static java.lang.Byte.toUnsignedInt; import static javasabr.mqtt.model.reason.code.SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED; import static javasabr.mqtt.model.reason.code.SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED; import java.util.Set; +import javasabr.mqtt.model.MqttClientConnectionConfig; +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttServerConnectionConfig; import javasabr.mqtt.model.reason.code.DisconnectReasonCode; import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.mqtt.model.subscribtion.RequestedSubscription; +import javasabr.mqtt.model.subscribtion.Subscription; +import javasabr.mqtt.network.MqttClient; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.SubscribeMqttInMessage; -import javasabr.mqtt.network.message.out.MqttOutMessage; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; +import javasabr.mqtt.service.TopicService; import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.ArrayFactory; +import javasabr.rlib.collections.array.MutableArray; +import javasabr.rlib.common.util.StringUtils; import lombok.AccessLevel; import lombok.experimental.FieldDefaults; @@ -22,19 +30,23 @@ public class SubscribeMqttInMessageHandler extends AbstractMqttInMessageHandler { - private final static Set INVALID_ACK_CODE = Set.of( + private final static Set DISCONNECT_CASES = Set.of( SHARED_SUBSCRIPTIONS_NOT_SUPPORTED, WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED); + private static final Array SUBSCRIPTION_ID_NOT_SUPPORTED = + Array.of(SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED); + SubscriptionService subscriptionService; - MessageOutFactoryService messageOutFactoryService; + TopicService topicService; public SubscribeMqttInMessageHandler( SubscriptionService subscriptionService, - MessageOutFactoryService messageOutFactoryService) { - super(ExternalMqttClient.class, SubscribeMqttInMessage.class); + MessageOutFactoryService messageOutFactoryService, + TopicService topicService) { + super(ExternalMqttClient.class, SubscribeMqttInMessage.class, messageOutFactoryService); this.subscriptionService = subscriptionService; - this.messageOutFactoryService = messageOutFactoryService; + this.topicService = topicService; } @Override @@ -46,31 +58,78 @@ public MqttMessageType messageType() { protected void processReceived( MqttConnection connection, ExternalMqttClient client, - SubscribeMqttInMessage networkPacket) { + SubscribeMqttInMessage message) { - Array ackReasonCodes = subscriptionService - .subscribe(client, networkPacket.topicFilters()); - MqttOutMessage subscribeAck = messageOutFactoryService - .resolveFactory(client) - .newSubscribeAck(networkPacket.messageId(), ackReasonCodes); + MqttClientConnectionConfig clientConfig = client.connectionConfig(); + MqttServerConnectionConfig serverConfig = clientConfig.server(); + + if (message.subscriptionId() != MqttProperties.SUBSCRIPTION_ID_UNDEFINED) { + if (!serverConfig.subscriptionIdAvailable()) { + sendSubscriptionIdNotSupported(client, message); + return; + } + } + + Array subscriptions = transformSubscriptions( + client, + message.subscriptions(), + message.subscriptionId()); - client.send(subscribeAck); + Array subscriptionResults = subscriptionService + .subscribe(client, subscriptions); - SubscribeAckReasonCode anyReason = ackReasonCodes + sendSubscriptionResults(client, message, subscriptionResults); + + SubscribeAckReasonCode anyReasonToDisconnect = subscriptionResults .reversedIterations() - .findAny(INVALID_ACK_CODE, Set::contains); + .findAny(DISCONNECT_CASES, Set::contains); - if (anyReason != null) { - var disconnectReasonCode = DisconnectReasonCode.of(toUnsignedInt(anyReason.getValue())); - MqttOutMessage disconnect = messageOutFactoryService + if (anyReasonToDisconnect != null) { + DisconnectReasonCode reasonCode = DisconnectReasonCode.ofCode(anyReasonToDisconnect.code()); + client.closeWithReason(messageOutFactoryService .resolveFactory(client) - .newDisconnect(client, disconnectReasonCode); + .newDisconnect(client, reasonCode, message.userProperties())); + } + } + + private Array transformSubscriptions( + MqttClient client, + Array requestedSubscriptions, + int subscriptionId) { - client - .sendWithFeedback(disconnect) - .thenAccept(_ -> client - .connection() - .close()); + MutableArray subscriptions = + ArrayFactory.mutableArray(Subscription.class, requestedSubscriptions.size()); + + for (RequestedSubscription requested : requestedSubscriptions) { + String rawTopicFilter = requested.rawTopicFilter(); + subscriptions.add(new Subscription( + topicService.createTopicFilter(client, rawTopicFilter), + subscriptionId, + requested.qos(), + requested.retainHandling(), + requested.noLocal(), + requested.retainAsPublished())); } + + return subscriptions; + } + + private void sendSubscriptionIdNotSupported(ExternalMqttClient client, SubscribeMqttInMessage message) { + client.send(messageOutFactoryService + .resolveFactory(client) + .newSubscribeAck( + message.messageId(), + SUBSCRIPTION_ID_NOT_SUPPORTED, + StringUtils.EMPTY, + message.userProperties())); + } + + private void sendSubscriptionResults( + ExternalMqttClient client, + SubscribeMqttInMessage message, + Array subscriptionResults) { + client.send(messageOutFactoryService + .resolveFactory(client) + .newSubscribeAck(message.messageId(), subscriptionResults, message.userProperties())); } } diff --git a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java index e8444b41..80989420 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/message/handler/impl/UnsubscribeMqttInMessageHandler.java @@ -1,28 +1,33 @@ package javasabr.mqtt.service.message.handler.impl; import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; +import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.network.MqttConnection; import javasabr.mqtt.network.impl.ExternalMqttClient; import javasabr.mqtt.network.message.MqttMessageType; import javasabr.mqtt.network.message.in.UnsubscribeMqttInMessage; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; +import javasabr.mqtt.service.TopicService; import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.ArrayCollectors; import lombok.AccessLevel; import lombok.experimental.FieldDefaults; @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) -public class UnsubscribeMqttInMessageHandler extends AbstractMqttInMessageHandler { +public class UnsubscribeMqttInMessageHandler + extends AbstractMqttInMessageHandler { SubscriptionService subscriptionService; - MessageOutFactoryService messageOutFactoryService; + TopicService topicService; public UnsubscribeMqttInMessageHandler( SubscriptionService subscriptionService, - MessageOutFactoryService messageOutFactoryService) { - super(ExternalMqttClient.class, UnsubscribeMqttInMessage.class); + MessageOutFactoryService messageOutFactoryService, + TopicService topicService) { + super(ExternalMqttClient.class, UnsubscribeMqttInMessage.class, messageOutFactoryService); this.subscriptionService = subscriptionService; - this.messageOutFactoryService = messageOutFactoryService; + this.topicService = topicService; } @Override @@ -34,13 +39,19 @@ public MqttMessageType messageType() { protected void processReceived( MqttConnection connection, ExternalMqttClient client, - UnsubscribeMqttInMessage networkPacket) { + UnsubscribeMqttInMessage message) { - Array ackReasonCodes = subscriptionService - .unsubscribe(client, networkPacket.topicFilters()); + Array topicFilters = message + .rawTopicFilters() + .stream() + .map(rawTopicFilter -> topicService.createTopicFilter(client, rawTopicFilter)) + .collect(ArrayCollectors.toArray(TopicFilter.class)); + + Array unsubscribeResults = subscriptionService + .unsubscribe(client, topicFilters); client.send(messageOutFactoryService .resolveFactory(client) - .newUnsubscribeAck(networkPacket.messageId(), ackReasonCodes)); + .newUnsubscribeAck(message.messageId(), unsubscribeResults, message.userProperties())); } } diff --git a/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt311MessageOutFactory.java b/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt311MessageOutFactory.java index 3b9b3189..70d49c87 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt311MessageOutFactory.java +++ b/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt311MessageOutFactory.java @@ -55,7 +55,7 @@ public MqttOutMessage newConnectAck( @Override public PublishMqttOutMessage newPublish( - int packetId, + int messageId, QoS qos, boolean retained, boolean duplicate, @@ -66,34 +66,34 @@ public PublishMqttOutMessage newPublish( String responseTopic, byte[] correlationData, Array userProperties) { - return new PublishMqtt311OutMessage(packetId, qos, retained, duplicate, topicName, payload); + return new PublishMqtt311OutMessage(messageId, qos, retained, duplicate, topicName, payload); } @Override public MqttOutMessage newPublishAck( - int packetId, + int messageId, PublishAckReasonCode reasonCode, String reason, Array userProperties) { - return new PublishAckMqtt311OutMessage(packetId); + return new PublishAckMqtt311OutMessage(messageId); } @Override public MqttOutMessage newSubscribeAck( - int packetId, + int messageId, Array reasonCodes, String reason, Array userProperties) { - return new SubscribeAckMqtt311OutMessage(reasonCodes, packetId); + return new SubscribeAckMqtt311OutMessage(messageId, reasonCodes); } @Override public MqttOutMessage newUnsubscribeAck( - int packetId, + int messageId, Array reasonCodes, Array userProperties, String reason) { - return new UnsubscribeAckMqtt311OutMessage(packetId); + return new UnsubscribeAckMqtt311OutMessage(messageId); } @Override @@ -128,28 +128,28 @@ public MqttOutMessage newPingResponse() { @Override public MqttOutMessage newPublishRelease( - int packetId, + int messageId, PublishReleaseReasonCode reasonCode, Array userProperties, String reason) { - return new PublishReleaseMqtt311OutMessage(packetId); + return new PublishReleaseMqtt311OutMessage(messageId); } @Override public MqttOutMessage newPublishReceived( - int packetId, + int messageId, PublishReceivedReasonCode reasonCode, Array userProperties, String reason) { - return new PublishReceivedMqtt311OutMessage(packetId); + return new PublishReceivedMqtt311OutMessage(messageId); } @Override public MqttOutMessage newPublishCompleted( - int packetId, + int messageId, PublishCompletedReasonCode reasonCode, Array userProperties, String reason) { - return new PublishCompleteMqtt311OutMessage(packetId); + return new PublishCompleteMqtt311OutMessage(messageId); } } diff --git a/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt5MessageOutFactory.java b/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt5MessageOutFactory.java index 0806a15e..1238847f 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt5MessageOutFactory.java +++ b/service/src/main/java/javasabr/mqtt/service/message/out/factory/Mqtt5MessageOutFactory.java @@ -27,6 +27,7 @@ import javasabr.mqtt.network.message.out.SubscribeAckMqtt5OutMessage; import javasabr.mqtt.network.message.out.UnsubscribeAckMqtt5OutMessage; import javasabr.rlib.collections.array.Array; +import org.jspecify.annotations.Nullable; public class Mqtt5MessageOutFactory extends Mqtt311MessageOutFactory { @@ -70,7 +71,7 @@ public MqttOutMessage newConnectAck( @Override public PublishMqttOutMessage newPublish( - int packetId, + int messageId, QoS qos, boolean retained, boolean duplicate, @@ -79,10 +80,10 @@ public PublishMqttOutMessage newPublish( byte[] payload, boolean stringPayload, String responseTopic, - byte[] correlationData, + byte @Nullable [] correlationData, Array userProperties) { return new PublishMqtt5OutMessage( - packetId, + messageId, qos, retained, duplicate, @@ -97,29 +98,29 @@ public PublishMqttOutMessage newPublish( @Override public MqttOutMessage newPublishAck( - int packetId, + int messageId, PublishAckReasonCode reasonCode, String reason, Array userProperties) { - return new PublishAckMqtt5OutMessage(packetId, reasonCode, userProperties, reason); + return new PublishAckMqtt5OutMessage(messageId, reasonCode, userProperties, reason); } @Override public MqttOutMessage newSubscribeAck( - int packetId, + int messageId, Array reasonCodes, String reason, Array userProperties) { - return new SubscribeAckMqtt5OutMessage(packetId, reasonCodes, userProperties, reason); + return new SubscribeAckMqtt5OutMessage(messageId, reasonCodes, userProperties, reason); } @Override public MqttOutMessage newUnsubscribeAck( - int packetId, + int messageId, Array reasonCodes, Array userProperties, String reason) { - return new UnsubscribeAckMqtt5OutMessage(packetId, reasonCodes, userProperties, reason); + return new UnsubscribeAckMqtt5OutMessage(messageId, reasonCodes, userProperties, reason); } @Override @@ -150,28 +151,28 @@ public MqttOutMessage newAuthenticate( @Override public MqttOutMessage newPublishRelease( - int packetId, + int messageId, PublishReleaseReasonCode reasonCode, Array userProperties, String reason) { - return new PublishReleaseMqtt5OutMessage(packetId, reasonCode, userProperties, reason); + return new PublishReleaseMqtt5OutMessage(messageId, reasonCode, userProperties, reason); } @Override public MqttOutMessage newPublishReceived( - int packetId, + int messageId, PublishReceivedReasonCode reasonCode, Array userProperties, String reason) { - return new PublishReceivedMqtt5OutMessage(packetId, reasonCode, userProperties, reason); + return new PublishReceivedMqtt5OutMessage(messageId, reasonCode, userProperties, reason); } @Override public MqttOutMessage newPublishCompleted( - int packetId, + int messageId, PublishCompletedReasonCode reasonCode, Array userProperties, String reason) { - return new PublishCompleteMqtt5OutMessage(packetId, reasonCode, userProperties, reason); + return new PublishCompleteMqtt5OutMessage(messageId, reasonCode, userProperties, reason); } } diff --git a/service/src/main/java/javasabr/mqtt/service/message/out/factory/MqttMessageOutFactory.java b/service/src/main/java/javasabr/mqtt/service/message/out/factory/MqttMessageOutFactory.java index e1711731..b8607940 100644 --- a/service/src/main/java/javasabr/mqtt/service/message/out/factory/MqttMessageOutFactory.java +++ b/service/src/main/java/javasabr/mqtt/service/message/out/factory/MqttMessageOutFactory.java @@ -20,6 +20,7 @@ import javasabr.rlib.collections.array.MutableArray; import javasabr.rlib.common.util.ArrayUtils; import javasabr.rlib.common.util.StringUtils; +import org.jspecify.annotations.Nullable; public abstract class MqttMessageOutFactory { @@ -83,14 +84,14 @@ public MqttOutMessage newConnectAck(MqttClient client, ConnectAckReasonCode reas } public PublishMqttOutMessage newPublish( - int packetId, + int messageId, QoS qos, boolean retained, boolean duplicate, String topicName, byte[] payload) { return newPublish( - packetId, + messageId, qos, retained, duplicate, @@ -104,7 +105,7 @@ public PublishMqttOutMessage newPublish( } public abstract PublishMqttOutMessage newPublish( - int packetId, + int messageId, QoS qos, boolean retained, boolean duplicate, @@ -112,12 +113,12 @@ public abstract PublishMqttOutMessage newPublish( int topicAlias, byte[] payload, boolean stringPayload, - String responseTopic, - byte[] correlationData, + @Nullable String responseTopic, + byte @Nullable [] correlationData, Array userProperties); public abstract MqttOutMessage newPublishAck( - int packetId, + int messageId, PublishAckReasonCode reasonCode, String reason, Array userProperties); @@ -127,23 +128,37 @@ public MqttOutMessage newPublishAck(int packetId, PublishAckReasonCode reasonCod } public abstract MqttOutMessage newSubscribeAck( - int packetId, + int messageId, Array reasonCodes, String reason, Array userProperties); - public MqttOutMessage newSubscribeAck(int packetId, Array reasonCodes) { - return newSubscribeAck(packetId, reasonCodes, StringUtils.EMPTY, Array.empty(StringPair.class)); + public MqttOutMessage newSubscribeAck(int messageId, Array reasonCodes) { + return newSubscribeAck(messageId, reasonCodes, StringUtils.EMPTY, Array.empty(StringPair.class)); + } + + public MqttOutMessage newSubscribeAck( + int messageId, + Array reasonCodes, + Array userProperties) { + return newSubscribeAck(messageId, reasonCodes, StringUtils.EMPTY, userProperties); } public abstract MqttOutMessage newUnsubscribeAck( - int packetId, + int messageId, Array reasonCodes, Array userProperties, String reason); - public MqttOutMessage newUnsubscribeAck(int packetId, Array reasonCodes) { - return newUnsubscribeAck(packetId, reasonCodes, Array.empty(StringPair.class), StringUtils.EMPTY); + public MqttOutMessage newUnsubscribeAck(int messageId, Array reasonCodes) { + return newUnsubscribeAck(messageId, reasonCodes, Array.empty(StringPair.class), StringUtils.EMPTY); + } + + public MqttOutMessage newUnsubscribeAck( + int messageId, + Array reasonCodes, + Array userProperties) { + return newUnsubscribeAck(messageId, reasonCodes, userProperties, StringUtils.EMPTY); } public abstract MqttOutMessage newDisconnect( @@ -162,6 +177,18 @@ public MqttOutMessage newDisconnect(MqttClient client, DisconnectReasonCode reas StringUtils.EMPTY); } + public MqttOutMessage newDisconnect( + MqttClient client, + DisconnectReasonCode reasonCode, + Array userProperties) { + return newDisconnect( + client, + reasonCode, + userProperties, + StringUtils.EMPTY, + StringUtils.EMPTY); + } + public abstract MqttOutMessage newAuthenticate( AuthenticateReasonCode reasonCode, String authenticateMethod, diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/MqttPublishInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/MqttPublishInMessageHandler.java index 44a1195f..d3b11a91 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/MqttPublishInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/MqttPublishInMessageHandler.java @@ -1,12 +1,12 @@ package javasabr.mqtt.service.publish.handler; import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; public interface MqttPublishInMessageHandler { QoS qos(); - void handle(MqttClient client, PublishMqttInMessage packet); + void handle(MqttClient client, Publish packet); } diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/MqttPublishOutMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/MqttPublishOutMessageHandler.java index e0258d09..d8b3d0a9 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/MqttPublishOutMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/MqttPublishOutMessageHandler.java @@ -1,12 +1,12 @@ package javasabr.mqtt.service.publish.handler; import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.subscriber.SingleSubscriber; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; public interface MqttPublishOutMessageHandler { QoS qos(); - PublishHandlingResult handle(PublishMqttInMessage packet, SingleSubscriber subscriber); + PublishHandlingResult handle(Publish publish, SingleSubscriber subscriber); } diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java index 6ca8ad56..29089334 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishInMessageHandler.java @@ -1,9 +1,9 @@ package javasabr.mqtt.service.publish.handler.impl; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.subscriber.SingleSubscriber; import javasabr.mqtt.model.topic.TopicName; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.service.PublishDeliveringService; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.publish.handler.MqttPublishInMessageHandler; @@ -25,7 +25,7 @@ public abstract class AbstractMqttPublishInMessageHandler PublishDeliveringService publishDeliveringService; @Override - public void handle(MqttClient client, PublishMqttInMessage packet) { + public void handle(MqttClient client, Publish packet) { if (!expectedClient.isInstance(client)) { log.warning(client, "Not expected client:[%s]"::formatted); return; @@ -33,23 +33,21 @@ public void handle(MqttClient client, PublishMqttInMessage packet) { handleImpl(expectedClient.cast(client), packet); } - protected void handleImpl(C client, PublishMqttInMessage packet) { - TopicName topicName = packet.topicName(); - if (!subscriptionService.isValid(topicName)) { - handleInvalidTopic(client, packet.messageId(), topicName); - return; - } - + protected void handleImpl(C client, Publish publish) { + TopicName topicName = publish.topicName(); Array subscribers = subscriptionService.findSubscribers(topicName); if (subscribers.isEmpty()) { - handleEmptySubscriptions(client, packet.messageId(), topicName); + log.debug(client.clientId(), publish, "[%s] Not found any subscriber for publish: [%s]"::formatted); + handleEmptySubscriptions(client, publish); return; } for (SingleSubscriber subscriber : subscribers) { - PublishHandlingResult checkResult = checkSubscriber(client, packet, subscriber); + PublishHandlingResult checkResult = checkSubscriber(client, publish, subscriber); if (checkResult.error()) { - handleError(client, packet.messageId(), checkResult); + log.debug(client.clientId(), checkResult, subscriber, + "[%s] Found error:[%s] for subscriber:[%s] during checking"::formatted); + handleError(client, publish, checkResult); return; } } @@ -57,7 +55,7 @@ protected void handleImpl(C client, PublishMqttInMessage packet) { int count = 0; PublishHandlingResult errorResult = null; for (SingleSubscriber subscriber : subscribers) { - PublishHandlingResult result = startDelivering(client, packet, subscriber); + PublishHandlingResult result = startDelivering(client, publish, subscriber); if (result.error()) { errorResult = result; } else if(result == PublishHandlingResult.SUCCESS) { @@ -66,28 +64,30 @@ protected void handleImpl(C client, PublishMqttInMessage packet) { } if (errorResult != null) { - handleError(client, packet.messageId(), errorResult); + log.debug(client.clientId(), errorResult, + "[%s] Found final error:[%s] during processing publish"::formatted); + handleError(client, publish, errorResult); } else { - handleSuccessfulResult(client, packet, count); + log.debug(client.clientId(), count, + "[%s] Successfully started delivering publish to [%s] subscribers"::formatted); + handleSuccessfulResult(client, publish, count); } } - protected void handleInvalidTopic(C client, int messageId, TopicName topicName) {} - - protected void handleEmptySubscriptions(C client, int messageId, TopicName topicName) {} + protected void handleEmptySubscriptions(C client, Publish publish) {} - protected void handleError(C client, int messageId, PublishHandlingResult handlingResult) {} + protected void handleError(C client, Publish publish, PublishHandlingResult handlingResult) {} - protected void handleSuccessfulResult(C client, PublishMqttInMessage packet, int subscribers) {} + protected void handleSuccessfulResult(C client, Publish publish, int subscribers) {} protected PublishHandlingResult checkSubscriber( C client, - PublishMqttInMessage packet, + Publish publish, SingleSubscriber subscriber) { return PublishHandlingResult.SUCCESS; } - protected PublishHandlingResult startDelivering(C client, PublishMqttInMessage packet, SingleSubscriber subscriber) { - return publishDeliveringService.startDelivering(packet, subscriber); + protected PublishHandlingResult startDelivering(C client, Publish publish, SingleSubscriber subscriber) { + return publishDeliveringService.startDelivering(publish, subscriber); } } diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java index 6c0fcec1..a161019c 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/AbstractMqttPublishOutMessageHandler.java @@ -1,9 +1,11 @@ package javasabr.mqtt.service.publish.handler.impl; import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.PayloadFormat; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.model.topic.TopicName; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.network.message.out.PublishMqttOutMessage; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; @@ -13,6 +15,7 @@ import lombok.CustomLog; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; +import org.jspecify.annotations.Nullable; @CustomLog @RequiredArgsConstructor @@ -25,38 +28,46 @@ public abstract class AbstractMqttPublishOutMessageHandler MessageOutFactoryService messageOutFactoryService; @Override - public PublishHandlingResult handle(PublishMqttInMessage packet, SingleSubscriber subscriber) { - MqttClient mqttClient = subscriptionService.resolveClient(subscriber); - if (!expectedClient.isInstance(mqttClient)) { - log.warning(mqttClient, "Accepted not expected client:[%s]"::formatted); + public PublishHandlingResult handle(Publish publish, SingleSubscriber subscriber) { + MqttClient client = subscriptionService.resolveClient(subscriber); + if (!expectedClient.isInstance(client)) { + log.warning(client, "Accepted not expected client:[%s]"::formatted); return PublishHandlingResult.NOT_EXPECTED_CLIENT; } - return handleImpl(packet, expectedClient.cast(mqttClient)); + publish = reconstruct(client, publish); + if (publish == null) { + return PublishHandlingResult.SKIPPED; + } + return handleImpl(publish, expectedClient.cast(client)); + } + + @Nullable + protected Publish reconstruct(MqttClient client, Publish original) { + return original.with( + MqttProperties.MESSAGE_ID_IS_NOT_SET, + qos(), + false, + MqttProperties.TOPIC_ALIAS_UNDEFINED); } - protected abstract PublishHandlingResult handleImpl(PublishMqttInMessage packet, C client) ; + protected abstract PublishHandlingResult handleImpl(Publish publish, C client) ; - protected void startDelivering( - MqttClient client, - PublishMqttInMessage packet, - int messageId, - boolean duplicate) { - PublishMqttOutMessage publish = messageOutFactoryService + protected void startDelivering(MqttClient client, Publish publish) { + TopicName responseTopicName = publish.responseTopicName(); + PublishMqttOutMessage outMessage = messageOutFactoryService .resolveFactory(client) .newPublish( - messageId, + publish.messageId(), qos(), - packet.retained(), - duplicate, - packet - .topicName() - .toString(), - MqttProperties.TOPIC_ALIAS_NOT_SET, - packet.payload(), - packet.payloadFormatIndicator(), - packet.responseTopic(), - packet.correlationData(), - packet.userProperties()); - client.send(publish); + publish.retained(), + publish.duplicated(), + publish.topicName().toString(), + publish.topicAlias(), + publish.payload(), + publish.payloadFormat() == PayloadFormat.UTF8_STRING, + responseTopicName == null ? null : responseTopicName.toString(), + publish.correlationData(), + publish.userProperties()); + client.send(outMessage); } } diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java index 633cbaae..8f14dce6 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/PersistedMqttPublishOutMessageHandler.java @@ -1,19 +1,22 @@ package javasabr.mqtt.service.publish.handler.impl; +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.TrackableMessage; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.network.MqttClient; import javasabr.mqtt.network.MqttSession; import javasabr.mqtt.network.MqttSession.PendingMessageHandler; import javasabr.mqtt.network.impl.ExternalMqttClient; -import javasabr.mqtt.network.message.HasMessageId; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; 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 { +public abstract class PersistedMqttPublishOutMessageHandler extends + AbstractMqttPublishOutMessageHandler { PendingMessageHandler pendingMessageHandler; @@ -23,39 +26,52 @@ protected PersistedMqttPublishOutMessageHandler( super(ExternalMqttClient.class, subscriptionService, messageOutFactoryService); this.pendingMessageHandler = new PendingMessageHandler() { @Override - public boolean handleResponse(MqttClient client, HasMessageId response) { + public boolean handleResponse(MqttClient client, TrackableMessage response) { return handleReceivedResponse(client, response); } @Override - public void resend(MqttClient client, PublishMqttInMessage packet, int packetId) { - tryToDeliverAgain(client, packet, packetId); + public void resend(MqttClient client, Publish publish) { + tryToDeliverAgain(client, publish); } }; } + @Nullable @Override - protected PublishHandlingResult handleImpl(PublishMqttInMessage packet, ExternalMqttClient client) { + protected Publish reconstruct(MqttClient client, Publish original) { + MqttSession session = client.session(); + if (session == null) { + return null; + } + return original.with( + // generate new uniq packet id per client + session.nextMessageId(), + qos(), + false, + MqttProperties.TOPIC_ALIAS_UNDEFINED); + } + + @Override + protected PublishHandlingResult handleImpl(Publish publish, ExternalMqttClient client) { MqttSession session = client.session(); if (session == null) { return PublishHandlingResult.SKIPPED; } - // generate new uniq packet id per client - int packetId = session.nextPacketId(); // register waiting async response - session.registerOutPublish(packet, pendingMessageHandler, packetId); + session.registerOutPublish(publish, pendingMessageHandler); // send publish - startDelivering(client, packet, packetId, false); + startDelivering(client, publish); return PublishHandlingResult.SUCCESS; } - protected boolean handleReceivedResponse(MqttClient client, HasMessageId response) { + protected boolean handleReceivedResponse(MqttClient client, TrackableMessage response) { return false; } - protected void tryToDeliverAgain(MqttClient client, PublishMqttInMessage packet, int messageId) { - startDelivering(client, packet, messageId, true); + protected void tryToDeliverAgain(MqttClient client, Publish publish) { + startDelivering(client, publish.withDuplicated()); } } diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java index 592f24cf..2dafce82 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos0MqttPublishOutMessageHandler.java @@ -1,8 +1,8 @@ package javasabr.mqtt.service.publish.handler.impl; import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.network.impl.ExternalMqttClient; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.publish.handler.PublishHandlingResult; @@ -21,8 +21,8 @@ public QoS qos() { } @Override - protected PublishHandlingResult handleImpl(PublishMqttInMessage packet, ExternalMqttClient client) { - startDelivering(client, packet, packet.messageId(), false); + protected PublishHandlingResult handleImpl(Publish publish, ExternalMqttClient client) { + startDelivering(client, publish); return PublishHandlingResult.SUCCESS; } } diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishInMessageHandler.java index df1ddb1f..0f4447a9 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishInMessageHandler.java @@ -1,17 +1,18 @@ package javasabr.mqtt.service.publish.handler.impl; import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.reason.code.PublishAckReasonCode; -import javasabr.mqtt.model.topic.TopicName; import javasabr.mqtt.network.impl.ExternalMqttClient; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.PublishDeliveringService; import javasabr.mqtt.service.SubscriptionService; import javasabr.mqtt.service.publish.handler.PublishHandlingResult; import lombok.AccessLevel; +import lombok.CustomLog; import lombok.experimental.FieldDefaults; +@CustomLog @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class Qos1MqttPublishInMessageHandler extends Qos0MqttPublishInMessageHandler { @@ -31,34 +32,29 @@ public QoS qos() { } @Override - protected void handleEmptySubscriptions(ExternalMqttClient client, int messageId, TopicName topicName) { - super.handleEmptySubscriptions(client, messageId, topicName); + protected void handleEmptySubscriptions(ExternalMqttClient client, Publish publish) { + super.handleEmptySubscriptions(client, publish); + log.debug(client.clientId(), "[%s] Send PUBACK after not found any subscriber..."::formatted); client.send(messageOutFactoryService .resolveFactory(client) - .newPublishAck(messageId, PublishAckReasonCode.NO_MATCHING_SUBSCRIBERS)); + .newPublishAck(publish.messageId(), PublishAckReasonCode.NO_MATCHING_SUBSCRIBERS)); } @Override - protected void handleInvalidTopic(ExternalMqttClient client, int messageId, TopicName topicName) { - super.handleInvalidTopic(client, messageId, topicName); + protected void handleError(ExternalMqttClient client, Publish publish, PublishHandlingResult handlingResult) { + super.handleError(client, publish, handlingResult); + log.debug(client.clientId(), "[%s] Send PUBACK after failed processing publish..."::formatted); client.send(messageOutFactoryService .resolveFactory(client) - .newPublishAck(messageId, PublishAckReasonCode.TOPIC_NAME_INVALID)); + .newPublishAck(publish.messageId(), handlingResult.ackReasonCode())); } @Override - protected void handleError(ExternalMqttClient client, int messageId, PublishHandlingResult handlingResult) { - super.handleError(client, messageId, handlingResult); + protected void handleSuccessfulResult(ExternalMqttClient client, Publish publish, int subscribers) { + super.handleSuccessfulResult(client, publish, subscribers); + log.debug(client.clientId(), "[%s] Send PUBACK after successful processing publish..."::formatted); client.send(messageOutFactoryService .resolveFactory(client) - .newPublishAck(messageId, handlingResult.ackReasonCode())); - } - - @Override - protected void handleSuccessfulResult(ExternalMqttClient client, PublishMqttInMessage packet, int subscribers) { - super.handleSuccessfulResult(client, packet, subscribers); - client.send(messageOutFactoryService - .resolveFactory(client) - .newPublishAck(packet.messageId(), PublishAckReasonCode.SUCCESS)); + .newPublishAck(publish.messageId(), PublishAckReasonCode.SUCCESS)); } } diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java index 53cd94ef..b0681508 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos1MqttPublishOutMessageHandler.java @@ -1,8 +1,8 @@ package javasabr.mqtt.service.publish.handler.impl; import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.message.HasMessageId; import javasabr.mqtt.network.message.in.PublishAckMqttInMessage; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.SubscriptionService; @@ -21,7 +21,7 @@ public QoS qos() { } @Override - protected boolean handleReceivedResponse(MqttClient client, HasMessageId response) { + protected boolean handleReceivedResponse(MqttClient client, TrackableMessage response) { if (!(response instanceof PublishAckMqttInMessage)) { throw new IllegalStateException("Unexpected response: " + response); } diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java index bc92eff2..b7ecc51c 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishInMessageHandler.java @@ -1,15 +1,14 @@ package javasabr.mqtt.service.publish.handler.impl; import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.TrackableMessage; +import javasabr.mqtt.model.publishing.Publish; import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode; import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; -import javasabr.mqtt.model.topic.TopicName; import javasabr.mqtt.network.MqttClient; import javasabr.mqtt.network.MqttSession; import javasabr.mqtt.network.MqttSession.PendingMessageHandler; import javasabr.mqtt.network.impl.ExternalMqttClient; -import javasabr.mqtt.network.message.HasMessageId; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; import javasabr.mqtt.network.message.in.PublishReleaseMqttInMessage; import javasabr.mqtt.service.MessageOutFactoryService; import javasabr.mqtt.service.PublishDeliveringService; @@ -41,63 +40,54 @@ public QoS qos() { } @Override - protected void handleImpl(ExternalMqttClient client, PublishMqttInMessage packet) { + protected void handleImpl(ExternalMqttClient client, Publish publish) { MqttSession session = client.session(); if (session == null) { return; } // if this packet is re-try from client - if (packet.duplicate()) { + if (publish.duplicated()) { // if this packet was accepted before then we can skip it - if (session.hasInPending(packet.messageId())) { + if (session.hasInPending(publish.messageId())) { return; } } - super.handleImpl(client, packet); + super.handleImpl(client, publish); } @Override - protected void handleInvalidTopic(ExternalMqttClient client, int messageId, TopicName topicName) { - super.handleInvalidTopic(client, messageId, topicName); + protected void handleEmptySubscriptions(ExternalMqttClient client, Publish publish) { + super.handleEmptySubscriptions(client, publish); client.send(messageOutFactoryService .resolveFactory(client) - .newPublishReceived(messageId, PublishReceivedReasonCode.TOPIC_NAME_INVALID)); + .newPublishReceived(publish.messageId(), PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS)); } @Override - protected void handleEmptySubscriptions(ExternalMqttClient client, int messageId, TopicName topicName) { - super.handleEmptySubscriptions(client, messageId, topicName); + protected void handleError(ExternalMqttClient client, Publish publish, PublishHandlingResult handlingResult) { + super.handleError(client, publish, handlingResult); client.send(messageOutFactoryService .resolveFactory(client) - .newPublishReceived(messageId, PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS)); + .newPublishReceived(publish.messageId(), handlingResult.receivedReasonCode())); } @Override - protected void handleError(ExternalMqttClient client, int messageId, PublishHandlingResult handlingResult) { - super.handleError(client, messageId, handlingResult); - client.send(messageOutFactoryService - .resolveFactory(client) - .newPublishReceived(messageId, handlingResult.receivedReasonCode())); - } - - @Override - protected void handleSuccessfulResult(ExternalMqttClient client, PublishMqttInMessage packet, int subscribers) { - super.handleSuccessfulResult(client, packet, subscribers); + protected void handleSuccessfulResult(ExternalMqttClient client, Publish publish, int subscribers) { + super.handleSuccessfulResult(client, publish, subscribers); MqttSession session = client.session(); if (session == null) { return; } - session.registerInPublish(packet, pendingMessageHandler, packet.messageId()); + session.registerInPublish(publish, pendingMessageHandler); client.send(messageOutFactoryService .resolveFactory(client) - .newPublishReceived(packet.messageId(), PublishReceivedReasonCode.SUCCESS)); + .newPublishReceived(publish.messageId(), PublishReceivedReasonCode.SUCCESS)); } - private boolean processPublishRelease(MqttClient client, HasMessageId response) { + private boolean processPublishRelease(MqttClient client, TrackableMessage response) { if (!(response instanceof PublishReleaseMqttInMessage)) { throw new IllegalStateException("Unexpected response " + response); } - client.send(messageOutFactoryService .resolveFactory(client) .newPublishCompleted(response.messageId(), PublishCompletedReasonCode.SUCCESS)); diff --git a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java index 40afec99..1b104bc3 100644 --- a/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java +++ b/service/src/main/java/javasabr/mqtt/service/publish/handler/impl/Qos2MqttPublishOutMessageHandler.java @@ -3,8 +3,8 @@ import static javasabr.mqtt.model.reason.code.PublishReleaseReasonCode.SUCCESS; import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.TrackableMessage; import javasabr.mqtt.network.MqttClient; -import javasabr.mqtt.network.message.HasMessageId; import javasabr.mqtt.network.message.in.PublishCompleteMqttInMessage; import javasabr.mqtt.network.message.in.PublishReceivedMqttInMessage; import javasabr.mqtt.service.MessageOutFactoryService; @@ -24,7 +24,7 @@ public QoS qos() { } @Override - protected boolean handleReceivedResponse(MqttClient client, HasMessageId response) { + protected boolean handleReceivedResponse(MqttClient client, TrackableMessage response) { if (response instanceof PublishReceivedMqttInMessage) { client.send(messageOutFactoryService .resolveFactory(client) diff --git a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java index 8b1c0be0..2149e428 100644 --- a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java +++ b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSession.java @@ -3,16 +3,16 @@ import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; import javasabr.mqtt.model.MqttProperties; -import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.model.TrackableMessage; +import javasabr.mqtt.model.publishing.Publish; +import javasabr.mqtt.model.subscribtion.Subscription; import javasabr.mqtt.model.topic.TopicFilter; import javasabr.mqtt.network.MqttClient; import javasabr.mqtt.network.MqttSession.UnsafeMqttSession; -import javasabr.mqtt.network.message.HasMessageId; -import javasabr.mqtt.network.message.in.PublishMqttInMessage; +import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.LockableArray; -import javasabr.rlib.functions.TriConsumer; -import lombok.AllArgsConstructor; +import javasabr.rlib.collections.array.MutableArray; import lombok.CustomLog; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -26,20 +26,15 @@ @Accessors(fluent = true, chain = false) public class InMemoryMqttSession implements UnsafeMqttSession { - @Getter - @AllArgsConstructor - private static class PendingPublish { - private final PublishMqttInMessage publish; - private final PendingMessageHandler handler; - private final int packetId; - } + private static final Array EMPTY_SUBSCRIPTIONS = Array.empty(Subscription.class); + + private record PendingPublish(Publish publish, PendingMessageHandler handler) {} private static void registerPublish( - PublishMqttInMessage publish, + Publish publish, PendingMessageHandler handler, - int packetId, LockableArray pendingPublishes) { - PendingPublish pendingPublish = new PendingPublish(publish, handler, packetId); + PendingPublish pendingPublish = new PendingPublish(publish, handler); pendingPublishes .operations() .inWriteLock(pendingPublish, Collection::add); @@ -47,18 +42,18 @@ private static void registerPublish( private static void updatePendingPacket( MqttClient client, - HasMessageId response, + TrackableMessage response, LockableArray pendingPublishes, String clientId) { - int packetId = response.messageId(); + int messageId = response.messageId(); PendingPublish pendingPublish; long stamp = pendingPublishes.readLock(); try { pendingPublish = pendingPublishes .iterations() - .findAny(packetId, (element, targetId) -> element.packetId == targetId); + .findAny(messageId, (pending, targetId) -> pending.publish.messageId() == targetId); } finally { pendingPublishes.readUnlock(stamp); } @@ -68,8 +63,7 @@ private static void updatePendingPacket( return; } - var shouldBeRemoved = pendingPublish.handler.handleResponse(client, response); - + boolean shouldBeRemoved = pendingPublish.handler.handleResponse(client, response); if (shouldBeRemoved) { pendingPublishes .operations() @@ -81,7 +75,7 @@ private static void updatePendingPacket( private final LockableArray pendingOutPublishes; private final LockableArray pendingInPublishes; private final AtomicInteger packetIdGenerator; - private final LockableArray topicFilters; + private final LockableArray subscriptions; @Getter @Setter @@ -92,17 +86,17 @@ public InMemoryMqttSession(String clientId) { this.pendingOutPublishes = ArrayFactory.stampedLockBasedArray(PendingPublish.class); this.pendingInPublishes = ArrayFactory.stampedLockBasedArray(PendingPublish.class); this.packetIdGenerator = new AtomicInteger(0); - this.topicFilters = ArrayFactory.stampedLockBasedArray(SubscribeTopicFilter.class); + this.subscriptions = ArrayFactory.stampedLockBasedArray(Subscription.class); } @Override - public int nextPacketId() { + public int nextMessageId() { var nextId = packetIdGenerator.incrementAndGet(); if (nextId >= MqttProperties.MAXIMUM_PACKET_ID) { packetIdGenerator.compareAndSet(nextId, 0); - return nextPacketId(); + return nextMessageId(); } return nextId; @@ -114,13 +108,13 @@ public String clientId() { } @Override - public void registerOutPublish(PublishMqttInMessage publish, PendingMessageHandler handler, int packetId) { - registerPublish(publish, handler, packetId, pendingOutPublishes); + public void registerOutPublish(Publish publish, PendingMessageHandler handler) { + registerPublish(publish, handler, pendingOutPublishes); } @Override - public void registerInPublish(PublishMqttInMessage publish, PendingMessageHandler handler, int packetId) { - registerPublish(publish, handler, packetId, pendingInPublishes); + public void registerInPublish(Publish publish, PendingMessageHandler handler) { + registerPublish(publish, handler, pendingInPublishes); } @Override @@ -134,85 +128,106 @@ public boolean hasInPending() { } @Override - public boolean hasOutPending(int packetId) { + public boolean hasOutPending(int messageId) { long stamp = pendingOutPublishes.readLock(); try { return pendingOutPublishes .iterations() - .findAny(packetId, (element, targetId) -> element.packetId == targetId) != null; + .findAny(messageId, (pending, targetId) -> pending.publish.messageId() == targetId) != null; } finally { pendingOutPublishes.readUnlock(stamp); } } @Override - public boolean hasInPending(int packetId) { + public boolean hasInPending(int messageId) { long stamp = pendingInPublishes.readLock(); try { return pendingInPublishes .iterations() - .findAny(packetId, (element, targetId) -> element.packetId == targetId) != null; + .findAny(messageId, (pending, targetId) -> pending.publish.messageId() == targetId) != null; } finally { pendingInPublishes.readUnlock(stamp); } } @Override - public void resendPendingPackets(MqttClient mqttClient) { + public void resendPendingPackets(MqttClient client) { long stamp = pendingOutPublishes.readLock(); try { - pendingOutPublishes - .iterations() - .forEach( - mqttClient, (pendingPublish, client) -> { - PendingMessageHandler handler = pendingPublish.handler; - handler.resend(client, pendingPublish.publish, pendingPublish.packetId); - }); + for (PendingPublish pending : pendingOutPublishes) { + PendingMessageHandler handler = pending.handler; + Publish publish = pending.publish; + handler.resend(client, publish); + } } finally { pendingOutPublishes.readUnlock(stamp); } } @Override - public void updateOutPendingPacket(MqttClient client, HasMessageId response) { + public void updateOutPendingPacket(MqttClient client, TrackableMessage response) { updatePendingPacket(client, response, pendingOutPublishes, clientId); } @Override - public void updateInPendingPacket(MqttClient client, HasMessageId response) { + public void updateInPendingPacket(MqttClient client, TrackableMessage response) { updatePendingPacket(client, response, pendingInPublishes, clientId); } @Override - public void forEachTopicFilter(A arg1, B arg2, TriConsumer consumer) { - long stamp = topicFilters.readLock(); + public void storeSubscription(Subscription subscription) { + long stamp = subscriptions.writeLock(); + try { + subscriptions.add(subscription); + } finally { + subscriptions.writeUnlock(stamp); + } + } + + @Override + public void removeSubscription(TopicFilter topicFilter) { + long stamp = subscriptions.writeLock(); try { - for (SubscribeTopicFilter topicFilter : topicFilters) { - consumer.accept(arg1, arg2, topicFilter); + int index = subscriptions.indexOf(Subscription::topicFilter, topicFilter); + if (index >= 0) { + subscriptions.remove(index); } } finally { - topicFilters.readUnlock(stamp); + subscriptions.writeUnlock(stamp); } } @Override - public void addSubscriber(SubscribeTopicFilter subscribe) { - topicFilters - .operations() - .inWriteLock(subscribe, Collection::add); + public Array storedSubscriptions() { + if (subscriptions.isEmpty()) { + return EMPTY_SUBSCRIPTIONS; + } + long stamp = subscriptions.readLock(); + try { + return Array.copyOf(subscriptions); + } finally { + subscriptions.readUnlock(stamp); + } } @Override - public void removeSubscriber(TopicFilter topicFilter) { - long stamp = topicFilters.writeLock(); + public Array findStoredSubscriptionWithId(int subscriptionId) { + if (subscriptions.isEmpty()) { + return EMPTY_SUBSCRIPTIONS; + } + MutableArray result = ArrayFactory.mutableArray(Subscription.class); + long stamp = subscriptions.readLock(); try { - int index = topicFilters.indexOf(SubscribeTopicFilter::getTopicFilter, topicFilter); - if (index >= 0) { - topicFilters.remove(index); + for (Subscription subscription : subscriptions) { + if (subscription.subscriptionId() == subscriptionId) { + result.add(subscription); + } } } finally { - topicFilters.writeUnlock(stamp); + subscriptions.readUnlock(stamp); } + return result; } @Override diff --git a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java index d2b81dc1..5e301fa0 100644 --- a/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java +++ b/service/src/main/java/javasabr/mqtt/service/session/impl/InMemoryMqttSessionService.java @@ -23,11 +23,11 @@ public class InMemoryMqttSessionService implements MqttSessionService, Closeable final LockableRefToRefDictionary storedSession; final Thread cleanThread; - final int cleanInterval; + final int cleanIntervalInMs; volatile boolean closed; - public InMemoryMqttSessionService(int cleanInterval) { - this.cleanInterval = cleanInterval; + public InMemoryMqttSessionService(int cleanIntervalInMs) { + this.cleanIntervalInMs = cleanIntervalInMs; this.storedSession = DictionaryFactory.stampedLockBasedRefToRefDictionary(); this.cleanThread = new Thread(this::cleanup, "InMemoryMqttSessionService-Cleanup"); this.cleanThread.setPriority(Thread.MIN_PRIORITY); @@ -90,7 +90,7 @@ private void cleanup() { var toRemove = ArrayFactory.mutableArray(UnsafeMqttSession.class); while (!closed) { - ThreadUtils.sleep(cleanInterval); + ThreadUtils.sleep(cleanIntervalInMs); toCheck.clear(); toRemove.clear(); diff --git a/service/src/test/groovy/javasabr/mqtt/service/InMemorySubscriptionServiceTest.groovy b/service/src/test/groovy/javasabr/mqtt/service/InMemorySubscriptionServiceTest.groovy new file mode 100644 index 00000000..cc2eaf4d --- /dev/null +++ b/service/src/test/groovy/javasabr/mqtt/service/InMemorySubscriptionServiceTest.groovy @@ -0,0 +1,172 @@ +package javasabr.mqtt.service + +import javasabr.mqtt.model.MqttVersion +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.SubscribeRetainHandling +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode +import javasabr.mqtt.model.subscribtion.Subscription +import javasabr.mqtt.service.impl.InMemorySubscriptionService +import javasabr.rlib.collections.array.Array + +class InMemorySubscriptionServiceTest extends IntegrationServiceSpecification { + + SubscriptionService subscriptionService = new InMemorySubscriptionService() + + def "should subscribe with expected results in default settings"() { + given: + def serverConfig = defaultExternalServerConnectionConfig + def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) + def mqttClient = mqttConnection.client() + def subscriptions = Array.typed(Subscription, + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), + 30, + QoS.AT_MOST_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/2"), + 30, + QoS.AT_LEAST_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/3"), + 30, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/invalid/##"), + 30, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.SEND, + true, + true)) + when: + def result = subscriptionService.subscribe(mqttClient, subscriptions) + then: + result.size() == 4 + result == Array.of( + SubscribeAckReasonCode.GRANTED_QOS_0, + SubscribeAckReasonCode.GRANTED_QOS_1, + SubscribeAckReasonCode.GRANTED_QOS_2, + SubscribeAckReasonCode.TOPIC_FILTER_INVALID) + } + + def "should not subscribe with for not supported topic filter"() { + given: + def serverConfig = defaultExternalServerConnectionConfig + .withSharedSubscriptionAvailable(false) + .withWildcardSubscriptionAvailable(false) + def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) + def mqttClient = mqttConnection.client() + def subscriptions = Array.typed(Subscription, + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/+"), + 5, + QoS.AT_MOST_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/#"), + 5, + QoS.AT_LEAST_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "\$share/group1/topic/filter/3"), + 5, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "\$share/group1/topic/filter/#"), + 5, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.SEND, + true, + true), + new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "\$share/group1/topic/filter/+"), + 5, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.SEND, + true, + true)) + when: + def result = subscriptionService.subscribe(mqttClient, subscriptions) + then: + result.size() == 5 + result == Array.typed( + SubscribeAckReasonCode.class, + SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED, + SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED, + SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED, + SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED, + SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED) + } + + def "should store subscription with correct subscription id"() { + given: + def serverConfig = defaultExternalServerConnectionConfig + def mqttConnection = mockedExternalConnection(serverConfig, MqttVersion.MQTT_5) + def mqttClient = mqttConnection.client() + + def sub1 = new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/1"), + 15, + QoS.AT_MOST_ONCE, + SubscribeRetainHandling.SEND, + true, + true) + def sub2 = new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/2"), + 15, + QoS.AT_LEAST_ONCE, + SubscribeRetainHandling.SEND, + true, + true) + + def sub3 = new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/filter/3"), + 30, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.SEND, + true, + true) + def sub4 = new Subscription( + defaultTopicService.createTopicFilter(mqttClient, "topic/invalid/4"), + 30, + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.SEND, + true, + true) + + def subscriptions = Array.typed(Subscription, sub1, sub2, sub3, sub4) + when: + def result = subscriptionService.subscribe(mqttClient, subscriptions) + then: + result.size() == 4 + result == Array.of( + SubscribeAckReasonCode.GRANTED_QOS_0, + SubscribeAckReasonCode.GRANTED_QOS_1, + SubscribeAckReasonCode.GRANTED_QOS_2, + SubscribeAckReasonCode.GRANTED_QOS_2) + when: + def session = mqttClient.session() + def subsWithId15 = session.findStoredSubscriptionWithId(15) + def subsWithId30 = session.findStoredSubscriptionWithId(30) + then: + subsWithId15.size() == 2 + subsWithId30.size() == 2 + subsWithId15 == Array.of(sub1, sub2) + subsWithId30 == Array.of(sub3, sub4) + } +} diff --git a/service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy b/service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy new file mode 100644 index 00000000..1fe81609 --- /dev/null +++ b/service/src/test/groovy/javasabr/mqtt/service/IntegrationServiceSpecification.groovy @@ -0,0 +1,83 @@ +package javasabr.mqtt.service + +import javasabr.mqtt.model.MqttClientConnectionConfig +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.MqttServerConnectionConfig +import javasabr.mqtt.model.MqttVersion +import javasabr.mqtt.model.QoS +import javasabr.mqtt.network.MqttConnection +import javasabr.mqtt.network.MqttSession +import javasabr.mqtt.network.handler.MqttClientReleaseHandler +import javasabr.mqtt.network.impl.ExternalMqttClient +import javasabr.mqtt.service.impl.DefaultTopicService +import javasabr.mqtt.service.session.impl.InMemoryMqttSessionService +import javasabr.rlib.network.BufferAllocator +import javasabr.rlib.network.Network +import spock.lang.Shared +import spock.lang.Specification + +import java.nio.channels.AsynchronousSocketChannel +import java.util.concurrent.atomic.AtomicInteger + +class IntegrationServiceSpecification extends Specification { + + @Shared + def clientIdGenerator = new AtomicInteger(); + + @Shared + def defaultTopicService = new DefaultTopicService() + + @Shared + def defaultMqttSessionService = new InMemoryMqttSessionService(60_000); + + @Shared + def defaultExternalServerConnectionConfig = new MqttServerConnectionConfig( + QoS.EXACTLY_ONCE, + MqttProperties.MAXIMUM_MESSAGE_SIZE_DEFAULT, + MqttProperties.MAXIMUM_STRING_LENGTH, + MqttProperties.MAXIMUM_BINARY_SIZE, + MqttProperties.MAXIMUM_TOPIC_LEVELS, + MqttProperties.SERVER_KEEP_ALIVE_DEFAULT, + MqttProperties.RECEIVE_MAXIMUM_PUBLISHES_DEFAULT, + MqttProperties.TOPIC_ALIAS_DEFAULT, + 0, + true, + true, + true, + true, + true, + true) + + def mockedExternalConnection( + MqttServerConnectionConfig serverConnectionConfig, + MqttVersion mqttVersion) { + + def connection = new MqttConnection( + Mock(Network), + Mock(AsynchronousSocketChannel), + Mock(BufferAllocator), + 100, + serverConnectionConfig, + { MqttConnection connection -> + def client = new ExternalMqttClient(connection, Mock(MqttClientReleaseHandler)) + def clientId = "mockedClient_${clientIdGenerator.incrementAndGet()}" + client.clientId(clientId) + client.session(defaultMqttSessionService.create(clientId).block()) + return client + }) + + connection.configure(new MqttClientConnectionConfig( + serverConnectionConfig, + serverConnectionConfig.maxQos(), + mqttVersion, + MqttProperties.SESSION_EXPIRY_INTERVAL_DEFAULT, + serverConnectionConfig.receiveMaxPublishes(), + serverConnectionConfig.maxMessageSize(), + serverConnectionConfig.topicAliasMaxValue(), + 0, + false, + false)) + + return connection + } +}