diff --git a/Source/MQTTnet/Server/MqttClientConnection.cs b/Source/MQTTnet/Server/MqttClientConnection.cs index 9d10e329..bf51269c 100644 --- a/Source/MQTTnet/Server/MqttClientConnection.cs +++ b/Source/MQTTnet/Server/MqttClientConnection.cs @@ -392,22 +392,18 @@ namespace MQTTnet.Server } publishPacket = _dataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage); - publishPacket.QualityOfServiceLevel = queuedApplicationMessage.QualityOfServiceLevel; + publishPacket.QualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel; // Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9]. publishPacket.Retain = queuedApplicationMessage.IsRetainedMessage; - if (publishPacket.QualityOfServiceLevel > 0) - { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); - } - if (_serverOptions.ClientMessageQueueInterceptor != null) { var context = new MqttClientMessageQueueInterceptorContext( queuedApplicationMessage.SenderClientId, ClientId, - queuedApplicationMessage.ApplicationMessage); + queuedApplicationMessage.ApplicationMessage, + queuedApplicationMessage.SubscriptionQualityOfServiceLevel); if (_serverOptions.ClientMessageQueueInterceptor != null) { @@ -421,7 +417,12 @@ namespace MQTTnet.Server publishPacket.Topic = context.ApplicationMessage.Topic; publishPacket.Payload = context.ApplicationMessage.Payload; - publishPacket.QualityOfServiceLevel = context.ApplicationMessage.QualityOfServiceLevel; + publishPacket.QualityOfServiceLevel = context.SubscriptionQualityOfServiceLevel; + } + + if (publishPacket.QualityOfServiceLevel > 0) + { + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); } if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) diff --git a/Source/MQTTnet/Server/MqttClientMessageQueueInterceptorContext.cs b/Source/MQTTnet/Server/MqttClientMessageQueueInterceptorContext.cs index ca0e2a0e..209f8c03 100644 --- a/Source/MQTTnet/Server/MqttClientMessageQueueInterceptorContext.cs +++ b/Source/MQTTnet/Server/MqttClientMessageQueueInterceptorContext.cs @@ -1,12 +1,15 @@ -namespace MQTTnet.Server +using MQTTnet.Protocol; + +namespace MQTTnet.Server { public class MqttClientMessageQueueInterceptorContext { - public MqttClientMessageQueueInterceptorContext(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage) + public MqttClientMessageQueueInterceptorContext(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, MqttQualityOfServiceLevel subscriptionQualityOfServiceLevel) { SenderClientId = senderClientId; ReceiverClientId = receiverClientId; ApplicationMessage = applicationMessage; + SubscriptionQualityOfServiceLevel = subscriptionQualityOfServiceLevel; } public string SenderClientId { get; } @@ -16,5 +19,7 @@ public MqttApplicationMessage ApplicationMessage { get; set; } public bool AcceptEnqueue { get; set; } = true; + + public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; } } } diff --git a/Source/MQTTnet/Server/MqttClientMessageQueueInterceptorDelegate.cs b/Source/MQTTnet/Server/MqttClientMessageQueueInterceptorDelegate.cs new file mode 100644 index 00000000..2cb7fc33 --- /dev/null +++ b/Source/MQTTnet/Server/MqttClientMessageQueueInterceptorDelegate.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace MQTTnet.Server +{ + public class MqttClientMessageQueueInterceptorDelegate : IMqttServerClientMessageQueueInterceptor + { + private readonly Func _callback; + + public MqttClientMessageQueueInterceptorDelegate(Action callback) + { + if (callback == null) throw new ArgumentNullException(nameof(callback)); + + _callback = context => + { + callback(context); + return Task.FromResult(0); + }; + } + + public MqttClientMessageQueueInterceptorDelegate(Func callback) + { + _callback = callback ?? throw new ArgumentNullException(nameof(callback)); + } + + public Task InterceptClientMessageQueueEnqueueAsync(MqttClientMessageQueueInterceptorContext context) + { + return _callback(context); + } + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs b/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs index 0cf19c8e..19c4f93d 100644 --- a/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs +++ b/Source/MQTTnet/Server/MqttClientSessionApplicationMessagesQueue.cs @@ -27,7 +27,7 @@ namespace MQTTnet.Server { ApplicationMessage = applicationMessage, SenderClientId = senderClientId, - QualityOfServiceLevel = qualityOfServiceLevel, + SubscriptionQualityOfServiceLevel = qualityOfServiceLevel, IsRetainedMessage = isRetainedMessage }); } diff --git a/Source/MQTTnet/Server/MqttQueuedApplicationMessage.cs b/Source/MQTTnet/Server/MqttQueuedApplicationMessage.cs index 5a3300ad..635894b3 100644 --- a/Source/MQTTnet/Server/MqttQueuedApplicationMessage.cs +++ b/Source/MQTTnet/Server/MqttQueuedApplicationMessage.cs @@ -1,4 +1,5 @@ -using MQTTnet.Protocol; +using System; +using MQTTnet.Protocol; namespace MQTTnet.Server { @@ -10,7 +11,14 @@ namespace MQTTnet.Server public bool IsRetainedMessage { get; set; } - public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } + public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; } + + [Obsolete("Use 'SubscriptionQualityOfServiceLevel' instead.")] + public MqttQualityOfServiceLevel QualityOfServiceLevel + { + get => SubscriptionQualityOfServiceLevel; + set => SubscriptionQualityOfServiceLevel = value; + } public bool IsDuplicate { get; set; } } diff --git a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs index 19ce1453..38c91830 100644 --- a/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs +++ b/Source/MQTTnet/Server/MqttServerOptionsBuilder.cs @@ -175,6 +175,18 @@ namespace MQTTnet.Server return this; } + public MqttServerOptionsBuilder WithClientMessageQueueInterceptor(IMqttServerClientMessageQueueInterceptor value) + { + _options.ClientMessageQueueInterceptor = value; + return this; + } + + public MqttServerOptionsBuilder WithClientMessageQueueInterceptor(Action value) + { + _options.ClientMessageQueueInterceptor = new MqttClientMessageQueueInterceptorDelegate(value); + return this; + } + public MqttServerOptionsBuilder WithSubscriptionInterceptor(IMqttServerSubscriptionInterceptor value) { _options.SubscriptionInterceptor = value; diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server_Tests.cs index 1ff7bfab..16db377e 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server_Tests.cs @@ -1415,5 +1415,68 @@ namespace MQTTnet.Tests Assert.AreEqual("a", undeliverd); } } + + [TestMethod] + public async Task Intercept_ClientMessageQueue() + { + using (var testEnvironment = new TestEnvironment(TestContext)) + { + await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder() + .WithClientMessageQueueInterceptor(c => c.ApplicationMessage.Topic = "a")); + + var topicAReceived = false; + var topicBReceived = false; + + var client = await testEnvironment.ConnectClientAsync(); + client.UseApplicationMessageReceivedHandler(c => + { + if (c.ApplicationMessage.Topic == "a") + { + topicAReceived = true; + } + else if (c.ApplicationMessage.Topic == "b") + { + topicBReceived = true; + } + }); + + await client.SubscribeAsync("a"); + await client.SubscribeAsync("b"); + + await client.PublishAsync("b"); + + await Task.Delay(500); + + Assert.IsTrue(topicAReceived); + Assert.IsFalse(topicBReceived); + } + } + + [TestMethod] + public async Task Intercept_ClientMessageQueue_Different_QoS_Of_Subscription_And_Message() + { + const string topic = "a"; + + using (var testEnvironment = new TestEnvironment(TestContext)) + { + await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder() + .WithClientMessageQueueInterceptor(c => { })); // Interceptor does nothing but has to be present. + + bool receivedMessage = false; + var client = await testEnvironment.ConnectClientAsync(); + client.UseApplicationMessageReceivedHandler(c => + { + receivedMessage = true; + }); + + await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce); + + await client.PublishAsync(new MqttApplicationMessage{ Topic = topic, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); + + await Task.Delay(500); + + Assert.IsTrue(receivedMessage); + } + } } }