Feature/mqttv5 subscriptions server (#1246)

* Add support for RetainHandling.

* Add support for No Local.

* Add support for _SubscriptionIdentifier_.

* Add more Unit Tests.

* Update docs.
This commit is contained in:
Christian
2021-09-23 20:30:43 +02:00
committed by GitHub
parent feb53e78c6
commit c756a4092f
83 changed files with 2032 additions and 1237 deletions
@@ -95,12 +95,12 @@ namespace MQTTnet.Extensions.Rpc
try
{
#if NET452
var promise = new TaskCompletionSource<byte[]>();
var awaitable = new TaskCompletionSource<byte[]>();
#else
var promise = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
var awaitable = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
#endif
if (!_waitingCalls.TryAdd(responseTopic, promise))
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
{
throw new InvalidOperationException();
}
@@ -112,9 +112,9 @@ namespace MQTTnet.Extensions.Rpc
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
using (cancellationToken.Register(() => { promise.TrySetCanceled(); }))
using (cancellationToken.Register(() => { awaitable.TrySetCanceled(); }))
{
return await promise.Task.ConfigureAwait(false);
return await awaitable.Task.ConfigureAwait(false);
}
}
finally
@@ -127,15 +127,15 @@ namespace MQTTnet.Extensions.Rpc
Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
{
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var promise))
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
{
return PlatformAbstractionLayer.CompletedTask;
}
#if NET452
Task.Run(() => promise.TrySetResult(eventArgs.ApplicationMessage.Payload));
Task.Run(() => awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload));
#else
promise.TrySetResult(eventArgs.ApplicationMessage.Payload);
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
#endif
// Set this message to handled to that other code can avoid execution etc.
@@ -6,13 +6,13 @@ namespace MQTTnet.Adapter
{
public sealed class MqttConnectingFailedException : MqttCommunicationException
{
public MqttConnectingFailedException(string message, Exception innerException, MqttClientAuthenticateResult authenticateResult)
public MqttConnectingFailedException(string message, Exception innerException, MqttClientConnectResult connectResult)
: base(message, innerException)
{
Result = authenticateResult;
Result = connectResult;
}
public MqttClientAuthenticateResult Result { get; }
public MqttClientConnectResult Result { get; }
public MqttClientConnectResultCode ResultCode => Result?.ResultCode ?? MqttClientConnectResultCode.UnspecifiedError;
}
@@ -1,83 +0,0 @@
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client.Connecting
{
// TODO: Consider renaming this to _MqttClientConnectResult_
public class MqttClientAuthenticateResult
{
/// <summary>
/// Gets or sets the result code.
/// Hint: MQTT 5 feature only.
/// </summary>
public MqttClientConnectResultCode ResultCode { get; set; }
public bool IsSessionPresent { get; set; }
public bool? WildcardSubscriptionAvailable { get; set; }
public bool? RetainAvailable { get; set; }
public string AssignedClientIdentifier { get; set; }
/// <summary>
/// Gets or sets the authentication method.
/// Hint: MQTT 5 feature only.
/// </summary>
public string AuthenticationMethod { get; set; }
/// <summary>
/// Gets or sets the authentication data.
/// Hint: MQTT 5 feature only.
/// </summary>
public byte[] AuthenticationData { get; set; }
public uint? MaximumPacketSize { get; set; }
/// <summary>
/// Gets or sets the reason string.
/// Hint: MQTT 5 feature only.
/// </summary>
public string ReasonString { get; set; }
public ushort? ReceiveMaximum { get; set; }
public MqttQualityOfServiceLevel MaximumQoS { get; set; }
/// <summary>
/// Gets or sets the response information.
/// Hint: MQTT 5 feature only.
/// </summary>
public string ResponseInformation { get; set; }
public ushort? TopicAliasMaximum { get; set; }
public string ServerReference { get; set; }
public ushort? ServerKeepAlive { get; set; }
public uint? SessionExpiryInterval { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the subscription identifiers are available or not.
/// Hint: MQTT 5 feature only.
/// </summary>
public bool? SubscriptionIdentifiersAvailable { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the shared subscriptions are available or not.
/// Hint: MQTT 5 feature only.
/// </summary>
public bool? SharedSubscriptionAvailable { get; set; }
/// <summary>
/// Gets or sets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only.
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
}
}
@@ -0,0 +1,115 @@
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client.Connecting
{
public sealed class MqttClientConnectResult
{
/// <summary>
/// Gets the result code.
/// MQTTv5 only.
/// </summary>
public MqttClientConnectResultCode ResultCode { get; internal set; }
/// <summary>
/// Gets a value indicating whether a session was already available or not.
/// MQTTv5 only.
/// </summary>
public bool IsSessionPresent { get; internal set; }
/// <summary>
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server.
/// MQTTv5 only.
/// </summary>
public bool WildcardSubscriptionAvailable { get; internal set; }
/// <summary>
/// Gets whether the server supports retained messages.
/// MQTTv5 only.
/// </summary>
public bool RetainAvailable { get; internal set; }
/// <summary>
/// Gets the client identifier which was chosen by the server.
/// MQTTv5 only.
/// </summary>
public string AssignedClientIdentifier { get; internal set; }
/// <summary>
/// Gets the authentication method.
/// MQTTv5 only.
/// </summary>
public string AuthenticationMethod { get; internal set; }
/// <summary>
/// Gets the authentication data.
/// MQTTv5 only.
/// </summary>
public byte[] AuthenticationData { get; internal set; }
public uint? MaximumPacketSize { get; internal set; }
/// <summary>
/// Gets the reason string.
/// MQTTv5 only.
/// </summary>
public string ReasonString { get; internal set; }
public ushort? ReceiveMaximum { get; internal set; }
/// <summary>
/// Gets the maximum QoS which is supported by the server.
/// MQTTv5 only.
/// </summary>
public MqttQualityOfServiceLevel MaximumQoS { get; internal set; }
/// <summary>
/// Gets the response information.
/// MQTTv5 only.
/// </summary>
public string ResponseInformation { get; internal set; }
/// <summary>
/// Gets the maximum value for a topic alias. 0 means not supported.
/// MQTTv5 only.
/// </summary>
public ushort TopicAliasMaximum { get; internal set; }
/// <summary>
/// Gets an alternate server which should be used instead of the current one.
/// MQTTv5 only.
/// </summary>
public string ServerReference { get; internal set; }
/// <summary>
/// Gets the keep alive interval which was chosen by the server instead of the
/// keep alive interval from the client CONNECT packet.
/// MQTTv5 only.
/// </summary>
public ushort? ServerKeepAlive { get; internal set; }
public uint? SessionExpiryInterval { get; internal set; }
/// <summary>
/// Gets a value indicating whether the subscription identifiers are available or not.
/// MQTTv5 only.
/// </summary>
public bool SubscriptionIdentifiersAvailable { get; internal set; }
/// <summary>
/// Gets a value indicating whether the shared subscriptions are available or not.
/// MQTTv5 only.
/// </summary>
public bool SharedSubscriptionAvailable { get; internal set; }
/// <summary>
/// Gets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// MQTTv5 only.
/// </summary>
public List<MqttUserProperty> UserProperties { get; internal set; }
}
}
@@ -4,15 +4,15 @@ namespace MQTTnet.Client.Connecting
{
public class MqttClientConnectedEventArgs : EventArgs
{
public MqttClientConnectedEventArgs(MqttClientAuthenticateResult authenticateResult)
public MqttClientConnectedEventArgs(MqttClientConnectResult connectResult)
{
AuthenticateResult = authenticateResult ?? throw new ArgumentNullException(nameof(authenticateResult));
ConnectResult = connectResult ?? throw new ArgumentNullException(nameof(connectResult));
}
/// <summary>
/// Gets the authentication result.
/// Hint: MQTT 5 feature only.
/// </summary>
public MqttClientAuthenticateResult AuthenticateResult { get; }
public MqttClientConnectResult ConnectResult { get; }
}
}
@@ -5,11 +5,11 @@ namespace MQTTnet.Client.Disconnecting
{
public sealed class MqttClientDisconnectedEventArgs : EventArgs
{
public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception, MqttClientAuthenticateResult authenticateResult, MqttClientDisconnectReason reason)
public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception, MqttClientConnectResult connectResult, MqttClientDisconnectReason reason)
{
ClientWasConnected = clientWasConnected;
Exception = exception;
AuthenticateResult = authenticateResult;
ConnectResult = connectResult;
Reason = reason;
ReasonCode = reason;
@@ -23,7 +23,7 @@ namespace MQTTnet.Client.Disconnecting
/// Gets the authentication result.
/// Hint: MQTT 5 feature only.
/// </summary>
public MqttClientAuthenticateResult AuthenticateResult { get; }
public MqttClientConnectResult ConnectResult { get; }
/// <summary>
/// Gets or sets the reason.
+1 -1
View File
@@ -28,7 +28,7 @@ namespace MQTTnet.Client
/// </summary>
IMqttClientDisconnectedHandler DisconnectedHandler { get; set; }
Task<MqttClientAuthenticateResult> ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken);
Task<MqttClientConnectResult> ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken);
Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken);
+13 -13
View File
@@ -62,7 +62,7 @@ namespace MQTTnet.Client
public IMqttClientOptions Options { get; private set; }
public async Task<MqttClientAuthenticateResult> ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken)
public async Task<MqttClientConnectResult> ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken)
{
if (options == null) throw new ArgumentNullException(nameof(options));
if (options.ChannelOptions == null) throw new ArgumentException("ChannelOptions are not set.");
@@ -76,7 +76,7 @@ namespace MQTTnet.Client
throw new InvalidOperationException("Not allowed to connect while connect/disconnect is pending.");
}
MqttClientAuthenticateResult authenticateResult = null;
MqttClientConnectResult connectResult = null;
try
{
@@ -102,7 +102,7 @@ namespace MQTTnet.Client
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
authenticateResult = await AuthenticateAsync(adapter, options.WillMessage, combined.Token).ConfigureAwait(false);
connectResult = await AuthenticateAsync(adapter, options.WillMessage, combined.Token).ConfigureAwait(false);
}
_lastPacketSentTimestamp = DateTime.UtcNow;
@@ -119,10 +119,10 @@ namespace MQTTnet.Client
var connectedHandler = ConnectedHandler;
if (connectedHandler != null)
{
await connectedHandler.HandleConnectedAsync(new MqttClientConnectedEventArgs(authenticateResult)).ConfigureAwait(false);
await connectedHandler.HandleConnectedAsync(new MqttClientConnectedEventArgs(connectResult)).ConfigureAwait(false);
}
return authenticateResult;
return connectResult;
}
catch (Exception exception)
{
@@ -130,7 +130,7 @@ namespace MQTTnet.Client
_logger.Error(exception, "Error while connecting with server.");
await DisconnectInternalAsync(null, exception, authenticateResult).ConfigureAwait(false);
await DisconnectInternalAsync(null, exception, connectResult).ConfigureAwait(false);
throw;
}
@@ -283,9 +283,9 @@ namespace MQTTnet.Client
base.Dispose(disposing);
}
async Task<MqttClientAuthenticateResult> AuthenticateAsync(IMqttChannelAdapter channelAdapter, MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken)
async Task<MqttClientConnectResult> AuthenticateAsync(IMqttChannelAdapter channelAdapter, MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken)
{
MqttClientAuthenticateResult result;
MqttClientConnectResult result;
try
{
@@ -324,19 +324,19 @@ namespace MQTTnet.Client
if (IsConnected) throw new MqttProtocolViolationException(message);
}
Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientAuthenticateResult authenticateResult)
Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult)
{
var clientWasConnected = IsConnected;
if (!DisconnectIsPendingOrFinished())
{
return DisconnectCoreAsync(sender, exception, authenticateResult, clientWasConnected);
return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected);
}
return PlatformAbstractionLayer.CompletedTask;
}
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientAuthenticateResult authenticateResult, bool clientWasConnected)
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
{
TryInitiateDisconnect();
@@ -380,7 +380,7 @@ namespace MQTTnet.Client
{
// This handler must be executed in a new thread because otherwise a dead lock may happen
// when trying to reconnect in that handler etc.
Task.Run(() => disconnectedHandler.HandleDisconnectedAsync(new MqttClientDisconnectedEventArgs(clientWasConnected, exception, authenticateResult, _disconnectReason))).RunInBackground(_logger);
Task.Run(() => disconnectedHandler.HandleDisconnectedAsync(new MqttClientDisconnectedEventArgs(clientWasConnected, exception, connectResult, _disconnectReason))).RunInBackground(_logger);
}
}
}
@@ -419,7 +419,7 @@ namespace MQTTnet.Client
packetIdentifier = packetWithIdentifier.PacketIdentifier;
}
using (var packetAwaiter = _packetDispatcher.AddAwaiter<TResponsePacket>(packetIdentifier))
using (var packetAwaiter = _packetDispatcher.AddAwaitable<TResponsePacket>(packetIdentifier))
{
try
{
@@ -112,7 +112,7 @@ namespace MQTTnet.Client
return client;
}
public static Task<MqttClientAuthenticateResult> ReconnectAsync(this IMqttClient client)
public static Task<MqttClientConnectResult> ReconnectAsync(this IMqttClient client)
{
if (client == null) throw new ArgumentNullException(nameof(client));
@@ -124,7 +124,7 @@ namespace MQTTnet.Client
return client.ConnectAsync(client.Options);
}
public static Task<MqttClientAuthenticateResult> ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken)
public static Task<MqttClientConnectResult> ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken)
{
if (client == null) throw new ArgumentNullException(nameof(client));
@@ -195,7 +195,7 @@ namespace MQTTnet.Client
return client.UnsubscribeAsync(options);
}
public static Task<MqttClientAuthenticateResult> ConnectAsync(this IMqttClient client, IMqttClientOptions options)
public static Task<MqttClientConnectResult> ConnectAsync(this IMqttClient client, IMqttClientOptions options)
{
if (client == null) throw new ArgumentNullException(nameof(client));
@@ -18,7 +18,7 @@ namespace MQTTnet.Client.Subscribing
/// The broker will return the subscription identifier associated with this PUBLISH packet and the PUBLISH packet to the client when need to forward PUBLISH packets matching this subscription to this client.
/// Hint: MQTT 5 feature only.
/// </summary>
public uint? SubscriptionIdentifier { get; set; }
public uint SubscriptionIdentifier { get; set; }
/// <summary>
/// Gets or sets the user properties.
@@ -2,12 +2,13 @@
using MQTTnet.Protocol;
using System;
using System.Collections.Generic;
using MQTTnet.Exceptions;
namespace MQTTnet.Client.Subscribing
{
public class MqttClientSubscribeOptionsBuilder
public sealed class MqttClientSubscribeOptionsBuilder
{
private readonly MqttClientSubscribeOptions _subscribeOptions = new MqttClientSubscribeOptions();
readonly MqttClientSubscribeOptions _subscribeOptions = new MqttClientSubscribeOptions();
/// <summary>
/// Adds the user property to the subscribe options.
@@ -31,8 +32,13 @@ namespace MQTTnet.Client.Subscribing
return this;
}
public MqttClientSubscribeOptionsBuilder WithSubscriptionIdentifier(uint? subscriptionIdentifier)
public MqttClientSubscribeOptionsBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier)
{
if (subscriptionIdentifier == 0)
{
throw new MqttProtocolViolationException("Subscription identifier cannot be 0.");
}
_subscribeOptions.SubscriptionIdentifier = subscriptionIdentifier;
return this;
}
@@ -40,9 +46,9 @@ namespace MQTTnet.Client.Subscribing
public MqttClientSubscribeOptionsBuilder WithTopicFilter(
string topic,
MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
bool? noLocal = null,
bool? retainAsPublished = null,
MqttRetainHandling? retainHandling = null)
bool noLocal = false,
bool retainAsPublished = false,
MqttRetainHandling retainHandling = MqttRetainHandling.SendAtSubscribe)
{
return WithTopicFilter(new MqttTopicFilter
{
@@ -8,25 +8,55 @@ namespace MQTTnet.Diagnostics
{
logger.Publish(MqttNetLogLevel.Verbose, message, parameters, null);
}
public static void Verbose(this IMqttNetScopedLogger logger, string message)
{
logger.Publish(MqttNetLogLevel.Verbose, message, null, null);
}
public static void Info(this IMqttNetScopedLogger logger, string message, params object[] parameters)
{
logger.Publish(MqttNetLogLevel.Info, message, parameters, null);
}
public static void Info(this IMqttNetScopedLogger logger, string message)
{
logger.Publish(MqttNetLogLevel.Info, message, null, null);
}
public static void Warning(this IMqttNetScopedLogger logger, Exception exception, string message, params object[] parameters)
{
logger.Publish(MqttNetLogLevel.Warning, message, parameters, exception);
}
public static void Warning(this IMqttNetScopedLogger logger, Exception exception, string message)
{
logger.Publish(MqttNetLogLevel.Warning, message, null, exception);
}
public static void Warning(this IMqttNetScopedLogger logger, string message, params object[] parameters)
{
logger.Publish(MqttNetLogLevel.Warning, message, parameters, null);
}
public static void Warning(this IMqttNetScopedLogger logger, string message)
{
logger.Publish(MqttNetLogLevel.Warning, message, null, null);
}
public static void Error(this IMqttNetScopedLogger logger, Exception exception, string message, params object[] parameters)
{
logger.Publish(MqttNetLogLevel.Error, message, parameters, exception);
}
public static void Error(this IMqttNetScopedLogger logger, Exception exception, string message)
{
logger.Publish(MqttNetLogLevel.Error, message, null, exception);
}
public static void Error(this IMqttNetScopedLogger logger, string message)
{
logger.Publish(MqttNetLogLevel.Error, message, null, null);
}
}
}
@@ -8,6 +8,7 @@ using MQTTnet.Client.Unsubscribing;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Server.Internal;
namespace MQTTnet.Formatter
{
@@ -15,13 +16,13 @@ namespace MQTTnet.Formatter
{
MqttApplicationMessage CreateApplicationMessage(MqttPublishPacket publishPacket);
MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket);
MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket);
MqttClientPublishResult CreateClientPublishResult(MqttPubAckPacket pubAckPacket);
MqttClientPublishResult CreateClientPublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket);
Client.Subscribing.MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket);
MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket);
MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket);
@@ -41,7 +42,7 @@ namespace MQTTnet.Formatter
MqttSubscribePacket CreateSubscribePacket(MqttClientSubscribeOptions options);
MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, Server.MqttClientSubscribeResult subscribeResult);
MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, SubscribeResult subscribeResult);
MqttUnsubscribePacket CreateUnsubscribePacket(MqttClientUnsubscribeOptions options);
@@ -11,7 +11,7 @@ using MQTTnet.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using MqttClientSubscribeResult = MQTTnet.Client.Subscribing.MqttClientSubscribeResult;
using MQTTnet.Server.Internal;
namespace MQTTnet.Formatter.V3
{
@@ -85,12 +85,12 @@ namespace MQTTnet.Formatter.V3
};
}
public MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
{
if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket));
MqttClientConnectResultCode resultCode;
switch (connAckPacket.ReturnCode.Value)
switch (connAckPacket.ReturnCode)
{
case MqttConnectReturnCode.ConnectionAccepted:
{
@@ -132,8 +132,10 @@ namespace MQTTnet.Formatter.V3
throw new MqttProtocolViolationException("Received unexpected return code.");
}
return new MqttClientAuthenticateResult
return new MqttClientConnectResult
{
RetainAvailable = true, // Always true because v3.1.1 does not have a way to "disable" that feature.
WildcardSubscriptionAvailable = true, // Always true because v3.1.1 does not have a way to "disable" that feature.
IsSessionPresent = connAckPacket.IsSessionPresent,
ResultCode = resultCode
};
@@ -205,7 +207,7 @@ namespace MQTTnet.Formatter.V3
return subscribePacket;
}
public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, Server.MqttClientSubscribeResult subscribeResult)
public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, SubscribeResult subscribeResult)
{
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
if (subscribeResult == null) throw new ArgumentNullException(nameof(subscribeResult));
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using MQTTnet.Adapter;
using MQTTnet.Exceptions;
@@ -407,7 +408,7 @@ namespace MQTTnet.Formatter.V3
protected virtual byte EncodeConnAckPacket(MqttConnAckPacket packet, IMqttPacketWriter packetWriter)
{
packetWriter.Write(0); // Reserved.
packetWriter.Write((byte)packet.ReturnCode.Value);
packetWriter.Write((byte)packet.ReturnCode);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck);
}
@@ -520,6 +521,21 @@ namespace MQTTnet.Formatter.V3
{
foreach (var topicFilter in packet.TopicFilters)
{
if (topicFilter.NoLocal)
{
throw new MqttProtocolViolationException("NoLocal is not supported in 3.1.1.");
}
if (topicFilter.RetainAsPublished)
{
throw new MqttProtocolViolationException("RetainAsPublished is not supported in 3.1.1.");
}
if (topicFilter.RetainHandling != MqttRetainHandling.SendAtSubscribe)
{
throw new MqttProtocolViolationException("RetainHandling is not supported in 3.1.1.");
}
packetWriter.WriteWithLengthPrefix(topicFilter.Topic);
packetWriter.Write((byte)topicFilter.QualityOfServiceLevel);
}
@@ -11,7 +11,7 @@ using MQTTnet.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using MqttClientSubscribeResult = MQTTnet.Client.Subscribing.MqttClientSubscribeResult;
using MQTTnet.Server.Internal;
namespace MQTTnet.Formatter.V5
{
@@ -115,16 +115,16 @@ namespace MQTTnet.Formatter.V5
};
}
public MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket)
{
if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket));
return new MqttClientAuthenticateResult
return new MqttClientConnectResult
{
IsSessionPresent = connAckPacket.IsSessionPresent,
ResultCode = (MqttClientConnectResultCode)(int)(connAckPacket.ReasonCode ?? 0),
WildcardSubscriptionAvailable = connAckPacket.Properties?.WildcardSubscriptionAvailable,
RetainAvailable = connAckPacket.Properties?.RetainAvailable,
ResultCode = (MqttClientConnectResultCode)(int)connAckPacket.ReasonCode,
WildcardSubscriptionAvailable = connAckPacket.Properties?.WildcardSubscriptionAvailable ?? true,
RetainAvailable = connAckPacket.Properties?.RetainAvailable ?? true,
AssignedClientIdentifier = connAckPacket.Properties?.AssignedClientIdentifier,
AuthenticationMethod = connAckPacket.Properties?.AuthenticationMethod,
AuthenticationData = connAckPacket.Properties?.AuthenticationData,
@@ -133,13 +133,13 @@ namespace MQTTnet.Formatter.V5
ReceiveMaximum = connAckPacket.Properties?.ReceiveMaximum,
MaximumQoS = connAckPacket.Properties?.MaximumQoS ?? MqttQualityOfServiceLevel.ExactlyOnce,
ResponseInformation = connAckPacket.Properties?.ResponseInformation,
TopicAliasMaximum = connAckPacket.Properties?.TopicAliasMaximum,
TopicAliasMaximum = connAckPacket.Properties?.TopicAliasMaximum ?? 0,
ServerReference = connAckPacket.Properties?.ServerReference,
ServerKeepAlive = connAckPacket.Properties?.ServerKeepAlive,
SessionExpiryInterval = connAckPacket.Properties?.SessionExpiryInterval,
SubscriptionIdentifiersAvailable = connAckPacket.Properties?.SubscriptionIdentifiersAvailable,
SharedSubscriptionAvailable = connAckPacket.Properties?.SharedSubscriptionAvailable,
UserProperties = connAckPacket.Properties?.UserProperties
SubscriptionIdentifiersAvailable = connAckPacket.Properties?.SubscriptionIdentifiersAvailable ?? true,
SharedSubscriptionAvailable = connAckPacket.Properties?.SharedSubscriptionAvailable ?? true,
UserProperties = connAckPacket.Properties?.UserProperties ?? new List<MqttUserProperty>()
};
}
@@ -180,12 +180,18 @@ namespace MQTTnet.Formatter.V5
ReasonCode = connectionValidatorContext.ReasonCode,
Properties = new MqttConnAckPacketProperties
{
RetainAvailable = true,
SubscriptionIdentifiersAvailable = true,
SharedSubscriptionAvailable = false,
TopicAliasMaximum = ushort.MaxValue,
WildcardSubscriptionAvailable = true,
UserProperties = connectionValidatorContext.ResponseUserProperties,
AuthenticationMethod = connectionValidatorContext.AuthenticationMethod,
AuthenticationData = connectionValidatorContext.ResponseAuthenticationData,
AssignedClientIdentifier = connectionValidatorContext.AssignedClientIdentifier,
ReasonString = connectionValidatorContext.ReasonString,
TopicAliasMaximum = ushort.MaxValue
ServerReference = connectionValidatorContext.ServerReference
}
};
}
@@ -242,7 +248,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, Server.MqttClientSubscribeResult subscribeResult)
public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, SubscribeResult subscribeResult)
{
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
if (subscribeResult == null) throw new ArgumentNullException(nameof(subscribeResult));
@@ -9,9 +9,9 @@ namespace MQTTnet.Formatter.V5
{
public class MqttV500PacketDecoder
{
private static readonly MqttPingReqPacket PingReqPacket = new MqttPingReqPacket();
static readonly MqttPingReqPacket PingReqPacket = new MqttPingReqPacket();
private static readonly MqttPingRespPacket PingRespPacket = new MqttPingRespPacket();
static readonly MqttPingRespPacket PingRespPacket = new MqttPingRespPacket();
public MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket)
{
@@ -45,7 +45,7 @@ namespace MQTTnet.Formatter.V5
}
}
private static MqttBasePacket DecodeConnectPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeConnectPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -214,7 +214,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeConnAckPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeConnAckPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -225,15 +225,17 @@ namespace MQTTnet.Formatter.V5
IsSessionPresent = (acknowledgeFlags & 0x1) > 0,
ReasonCode = (MqttConnectReasonCode)body.ReadByte()
};
// Set all default values according to specification. When they are missing the often
// indicate that a feature is available.
packet.Properties.RetainAvailable = true;
packet.Properties.SharedSubscriptionAvailable = true;
packet.Properties.SubscriptionIdentifiersAvailable = true;
packet.Properties.WildcardSubscriptionAvailable = true;
var propertiesReader = new MqttV500PropertiesReader(body);
while (propertiesReader.MoveNext())
{
if (packet.Properties == null)
{
packet.Properties = new MqttConnAckPacketProperties();
}
if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval)
{
packet.Properties.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval();
@@ -316,7 +318,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeDisconnectPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeDisconnectPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -363,7 +365,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeSubscribePacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeSubscribePacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -422,7 +424,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeSubAckPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeSubAckPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -467,7 +469,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeUnsubscribePacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeUnsubscribePacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -507,7 +509,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeUnsubAckPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeUnsubAckPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -552,17 +554,17 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodePingReqPacket()
static MqttBasePacket DecodePingReqPacket()
{
return PingReqPacket;
}
private static MqttBasePacket DecodePingRespPacket()
static MqttBasePacket DecodePingRespPacket()
{
return PingRespPacket;
}
private static MqttBasePacket DecodePublishPacket(byte header, IMqttPacketBodyReader body)
static MqttBasePacket DecodePublishPacket(byte header, IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -647,7 +649,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodePubAckPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodePubAckPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -694,7 +696,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodePubRecPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodePubRecPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -741,7 +743,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodePubRelPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodePubRelPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -788,7 +790,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodePubCompPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodePubCompPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -835,7 +837,7 @@ namespace MQTTnet.Formatter.V5
return packet;
}
private static MqttBasePacket DecodeAuthPacket(IMqttPacketBodyReader body)
static MqttBasePacket DecodeAuthPacket(IMqttPacketBodyReader body)
{
ThrowIfBodyIsEmpty(body);
@@ -888,7 +890,7 @@ namespace MQTTnet.Formatter.V5
}
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
private static void ThrowIfBodyIsEmpty(IMqttPacketBodyReader body)
static void ThrowIfBodyIsEmpty(IMqttPacketBodyReader body)
{
if (body == null || body.Length == 0)
{
@@ -173,12 +173,7 @@ namespace MQTTnet.Formatter.V5
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter));
if (!packet.ReasonCode.HasValue)
{
ThrowReasonCodeNotSetException();
}
byte connectAcknowledgeFlags = 0x0;
if (packet.IsSessionPresent)
{
@@ -186,7 +181,7 @@ namespace MQTTnet.Formatter.V5
}
packetWriter.Write(connectAcknowledgeFlags);
packetWriter.Write((byte)packet.ReasonCode.Value);
packetWriter.Write((byte)packet.ReasonCode);
var propertiesWriter = new MqttV500PropertiesWriter();
if (packet.Properties != null)
@@ -247,19 +242,18 @@ namespace MQTTnet.Formatter.V5
var propertiesWriter = new MqttV500PropertiesWriter();
if (packet.Properties != null)
{
if (packet.Properties.TopicAlias == 0)
{
throw new MqttProtocolViolationException("A Topic Alias of 0 is not permitted. A sender MUST NOT send a PUBLISH packet containing a Topic Alias which has the value 0 [MQTT-3.3.2-8].");
}
propertiesWriter.WriteContentType(packet.Properties.ContentType);
propertiesWriter.WriteCorrelationData(packet.Properties.CorrelationData);
propertiesWriter.WriteMessageExpiryInterval(packet.Properties.MessageExpiryInterval);
propertiesWriter.WritePayloadFormatIndicator(packet.Properties.PayloadFormatIndicator);
propertiesWriter.WriteResponseTopic(packet.Properties.ResponseTopic);
propertiesWriter.WriteSubscriptionIdentifiers(packet.Properties.SubscriptionIdentifiers);
propertiesWriter.WriteTopicAlias(packet.Properties.TopicAlias);
propertiesWriter.WriteUserProperties(packet.Properties.UserProperties);
if (packet.Properties.TopicAlias > 0)
{
propertiesWriter.WriteTopicAlias(packet.Properties.TopicAlias);
}
}
propertiesWriter.WriteTo(packetWriter);
@@ -411,7 +405,11 @@ namespace MQTTnet.Formatter.V5
var propertiesWriter = new MqttV500PropertiesWriter();
if (packet.Properties != null)
{
propertiesWriter.WriteSubscriptionIdentifier(packet.Properties.SubscriptionIdentifier);
if (packet.Properties.SubscriptionIdentifier > 0)
{
propertiesWriter.WriteSubscriptionIdentifier(packet.Properties.SubscriptionIdentifier);
}
propertiesWriter.WriteUserProperties(packet.Properties.UserProperties);
}
@@ -425,19 +423,19 @@ namespace MQTTnet.Formatter.V5
var options = (byte)topicFilter.QualityOfServiceLevel;
if (topicFilter.NoLocal == true)
if (topicFilter.NoLocal)
{
options |= 1 << 2;
}
if (topicFilter.RetainAsPublished == true)
if (topicFilter.RetainAsPublished)
{
options |= 1 << 3;
}
if (topicFilter.RetainHandling.HasValue)
if (topicFilter.RetainHandling != MqttRetainHandling.SendAtSubscribe)
{
options |= (byte)((byte)topicFilter.RetainHandling.Value << 4);
options |= (byte)((byte)topicFilter.RetainHandling << 4);
}
packetWriter.Write(options);
@@ -74,12 +74,12 @@ namespace MQTTnet.Formatter.V5
WriteAsFourByteInteger(MqttPropertyId.SessionExpiryInterval, value);
}
public void WriteSubscriptionIdentifier(uint? value)
public void WriteSubscriptionIdentifier(uint value)
{
WriteAsVariableLengthInteger(MqttPropertyId.SubscriptionIdentifier, value);
}
public void WriteSubscriptionIdentifiers(IEnumerable<uint> value)
public void WriteSubscriptionIdentifiers(ICollection<uint> value)
{
if (value == null)
{
@@ -148,9 +148,15 @@ namespace MQTTnet.Formatter.V5
WriteAsFourByteInteger(MqttPropertyId.MaximumPacketSize, value);
}
public void WriteRetainAvailable(bool? value)
public void WriteRetainAvailable(bool value)
{
Write(MqttPropertyId.RetainAvailable, value);
if (value)
{
// Absence of the flag means it is supported!
return;
}
Write(MqttPropertyId.RetainAvailable, false);
}
public void WriteAssignedClientIdentifier(string value)
@@ -168,14 +174,26 @@ namespace MQTTnet.Formatter.V5
Write(MqttPropertyId.WildcardSubscriptionAvailable, value);
}
public void WriteSubscriptionIdentifiersAvailable(bool? value)
public void WriteSubscriptionIdentifiersAvailable(bool value)
{
Write(MqttPropertyId.SubscriptionIdentifiersAvailable, value);
if (value)
{
// Absence of the flag means it is supported!
return;
}
Write(MqttPropertyId.SubscriptionIdentifiersAvailable, false);
}
public void WriteSharedSubscriptionAvailable(bool? value)
public void WriteSharedSubscriptionAvailable(bool value)
{
Write(MqttPropertyId.SharedSubscriptionAvailable, value);
if (value)
{
// Absence of the flag means it is supported!
return;
}
Write(MqttPropertyId.SharedSubscriptionAvailable, false);
}
public void WriteServerKeepAlive(ushort? value)
@@ -221,15 +239,10 @@ namespace MQTTnet.Formatter.V5
_packetWriter.Write(value.Value);
}
void WriteAsVariableLengthInteger(MqttPropertyId id, uint? value)
void WriteAsVariableLengthInteger(MqttPropertyId id, uint value)
{
if (!value.HasValue)
{
return;
}
_packetWriter.Write((byte)id);
_packetWriter.WriteVariableLengthInteger(value.Value);
_packetWriter.WriteVariableLengthInteger(value);
}
void WriteAsFourByteInteger(MqttPropertyId id, uint? value)
@@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Implementations
@@ -18,7 +19,16 @@ namespace MQTTnet.Implementations
public static void Sleep(TimeSpan timeout)
{
#if !NETSTANDARD1_3 && !WINDOWS_UWP
System.Threading.Thread.Sleep(timeout);
try
{
System.Threading.Thread.Sleep(timeout);
}
catch (ThreadAbortException)
{
// The ThreadAbortException is not actively catched in this project.
// So we use a one which is similar and will be catched properly.
throw new OperationCanceledException();
}
#else
Task.Delay(timeout).Wait();
#endif
+33
View File
@@ -7,6 +7,9 @@ using MQTTnet.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using MQTTnet.Client.Options;
using MQTTnet.Client.Subscribing;
using MQTTnet.Client.Unsubscribing;
namespace MQTTnet
{
@@ -120,5 +123,35 @@ namespace MQTTnet
return new MqttServer(serverAdapters, DefaultLogger);
}
public MqttClientOptionsBuilder CreateClientOptionsBuilder()
{
return new MqttClientOptionsBuilder();
}
public MqttServerOptionsBuilder CreateServerOptionsBuilder()
{
return new MqttServerOptionsBuilder();
}
public MqttClientSubscribeOptionsBuilder CreateSubscribeOptionsBuilder()
{
return new MqttClientSubscribeOptionsBuilder();
}
public MqttClientUnsubscribeOptionsBuilder CreateUnsubscribeOptionsBuilder()
{
return new MqttClientUnsubscribeOptionsBuilder();
}
public MqttTopicFilterBuilder CreateTopicFilterBuilder()
{
return new MqttTopicFilterBuilder();
}
public MqttApplicationMessageBuilder CreateApplicationMessageBuilder()
{
return new MqttApplicationMessageBuilder();
}
}
}
+4 -4
View File
@@ -29,22 +29,22 @@ namespace MQTTnet
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to not send messages originating on this client (noLocal) or not.
/// Gets or sets a value indicating whether the sender will not receive its own published application messages.
/// </summary>
/// Hint: MQTT 5 feature only.
public bool? NoLocal { get; set; }
public bool NoLocal { get; set; }
/// <summary>
/// Gets or sets a value indicating whether messages are retained as published or not.
/// Hint: MQTT 5 feature only.
/// </summary>
public bool? RetainAsPublished { get; set; }
public bool RetainAsPublished { get; set; }
/// <summary>
/// Gets or sets the retain handling.
/// Hint: MQTT 5 feature only.
/// </summary>
public MqttRetainHandling? RetainHandling { get; set; }
public MqttRetainHandling RetainHandling { get; set; }
public override string ToString()
{
+24 -3
View File
@@ -27,9 +27,9 @@ namespace MQTTnet
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator).
/// </summary>
string _topic;
bool? _noLocal;
bool? _retainAsPublished;
MqttRetainHandling? _retainHandling;
bool _noLocal;
bool _retainAsPublished;
MqttRetainHandling _retainHandling = MqttRetainHandling.SendAtSubscribe;
public MqttTopicFilterBuilder WithTopic(string topic)
{
@@ -61,19 +61,40 @@ namespace MQTTnet
return this;
}
[Obsolete("Please use overload with _bool_ instead.")]
public MqttTopicFilterBuilder WithNoLocal(bool? value = true)
{
_noLocal = value == true;
return this;
}
public MqttTopicFilterBuilder WithNoLocal(bool value = true)
{
_noLocal = value;
return this;
}
[Obsolete("Please use overload with _bool_ instead.")]
public MqttTopicFilterBuilder WithRetainAsPublished(bool? value = true)
{
_retainAsPublished = value == true;
return this;
}
public MqttTopicFilterBuilder WithRetainAsPublished(bool value = true)
{
_retainAsPublished = value;
return this;
}
[Obsolete("Please use overload with _MqttRetainHandling_ instead.")]
public MqttTopicFilterBuilder WithRetainHandling(MqttRetainHandling? value)
{
_retainHandling = value ?? MqttRetainHandling.SendAtSubscribe;
return this;
}
public MqttTopicFilterBuilder WithRetainHandling(MqttRetainHandling value)
{
_retainHandling = value;
return this;
@@ -3,9 +3,9 @@ using System;
namespace MQTTnet.PacketDispatcher
{
public interface IMqttPacketAwaiter : IDisposable
public interface IMqttPacketAwaitable : IDisposable
{
MqttPacketAwaiterPacketFilter PacketFilter { get; }
MqttPacketAwaitableFilter Filter { get; }
void Complete(MqttBasePacket packet);
@@ -6,14 +6,14 @@ using System.Threading.Tasks;
namespace MQTTnet.PacketDispatcher
{
public sealed class MqttPacketAwaiter<TPacket> : IMqttPacketAwaiter where TPacket : MqttBasePacket
public sealed class MqttPacketAwaitable<TPacket> : IMqttPacketAwaitable where TPacket : MqttBasePacket
{
readonly TaskCompletionSource<MqttBasePacket> _taskCompletionSource;
readonly MqttPacketDispatcher _owningPacketDispatcher;
public MqttPacketAwaiter(ushort packetIdentifier, MqttPacketDispatcher owningPacketDispatcher)
public MqttPacketAwaitable(ushort packetIdentifier, MqttPacketDispatcher owningPacketDispatcher)
{
PacketFilter = new MqttPacketAwaiterPacketFilter
Filter = new MqttPacketAwaitableFilter
{
Type = typeof(TPacket),
Identifier = packetIdentifier
@@ -27,7 +27,7 @@ namespace MQTTnet.PacketDispatcher
#endif
}
public MqttPacketAwaiterPacketFilter PacketFilter { get; }
public MqttPacketAwaitableFilter Filter { get; }
public async Task<TPacket> WaitOneAsync(TimeSpan timeout)
{
@@ -88,7 +88,7 @@ namespace MQTTnet.PacketDispatcher
public void Dispose()
{
_owningPacketDispatcher.RemoveAwaiter(this);
_owningPacketDispatcher.RemoveAwaitable(this);
}
}
}
@@ -2,7 +2,7 @@
namespace MQTTnet.PacketDispatcher
{
public sealed class MqttPacketAwaiterPacketFilter
public sealed class MqttPacketAwaitableFilter
{
public Type Type { get; set; }
@@ -6,33 +6,33 @@ namespace MQTTnet.PacketDispatcher
{
public sealed class MqttPacketDispatcher
{
readonly List<IMqttPacketAwaiter> _awaiters = new List<IMqttPacketAwaiter>();
readonly List<IMqttPacketAwaitable> _awaitables = new List<IMqttPacketAwaitable>();
public void FailAll(Exception exception)
{
if (exception == null) throw new ArgumentNullException(nameof(exception));
lock (_awaiters)
lock (_awaitables)
{
foreach (var awaiter in _awaiters)
foreach (var awaitable in _awaitables)
{
awaiter.Fail(exception);
awaitable.Fail(exception);
}
_awaiters.Clear();
_awaitables.Clear();
}
}
public void CancelAll()
{
lock (_awaiters)
lock (_awaitables)
{
foreach (var entry in _awaiters)
foreach (var awaitable in _awaitables)
{
entry.Cancel();
awaitable.Cancel();
}
_awaiters.Clear();
_awaitables.Clear();
}
}
@@ -47,54 +47,54 @@ namespace MQTTnet.PacketDispatcher
}
var packetType = packet.GetType();
var matchingAwaiters = new List<IMqttPacketAwaiter>();
var awaitables = new List<IMqttPacketAwaitable>();
lock (_awaiters)
lock (_awaitables)
{
for (var i = _awaiters.Count - 1; i >= 0; i--)
for (var i = _awaitables.Count - 1; i >= 0; i--)
{
var entry = _awaiters[i];
var entry = _awaitables[i];
// Note: The PingRespPacket will also arrive here and has NO identifier but there
// is code which waits for it. So the code must be able to deal with filters which
// are referring to the type only (identifier is 0)!
if (entry.PacketFilter.Type != packetType || entry.PacketFilter.Identifier != identifier)
if (entry.Filter.Type != packetType || entry.Filter.Identifier != identifier)
{
continue;
}
matchingAwaiters.Add(entry);
_awaiters.RemoveAt(i);
awaitables.Add(entry);
_awaitables.RemoveAt(i);
}
}
foreach (var matchingEntry in matchingAwaiters)
foreach (var matchingEntry in awaitables)
{
matchingEntry.Complete(packet);
}
return matchingAwaiters.Count > 0;
return awaitables.Count > 0;
}
public MqttPacketAwaiter<TResponsePacket> AddAwaiter<TResponsePacket>(ushort packetIdentifier) where TResponsePacket : MqttBasePacket
public MqttPacketAwaitable<TResponsePacket> AddAwaitable<TResponsePacket>(ushort packetIdentifier) where TResponsePacket : MqttBasePacket
{
var awaiter = new MqttPacketAwaiter<TResponsePacket>(packetIdentifier, this);
var awaitable = new MqttPacketAwaitable<TResponsePacket>(packetIdentifier, this);
lock (_awaiters)
lock (_awaitables)
{
_awaiters.Add(awaiter);
_awaitables.Add(awaitable);
}
return awaiter;
return awaitable;
}
public void RemoveAwaiter(IMqttPacketAwaiter awaiter)
public void RemoveAwaitable(IMqttPacketAwaitable awaitable)
{
if (awaiter == null) throw new ArgumentNullException(nameof(awaiter));
if (awaitable == null) throw new ArgumentNullException(nameof(awaitable));
lock (_awaiters)
lock (_awaitables)
{
_awaiters.Remove(awaiter);
_awaitables.Remove(awaitable);
}
}
}
+15 -14
View File
@@ -4,22 +4,23 @@ namespace MQTTnet.Packets
{
public sealed class MqttConnAckPacket : MqttBasePacket
{
public MqttConnectReturnCode? ReturnCode { get; set; }
#region Added in MQTTv3.1.1
public MqttConnectReturnCode ReturnCode { get; set; }
/// <summary>
/// Added in MQTT 3.1.1.
/// </summary>
public bool IsSessionPresent { get; set; }
/// <summary>
/// Added in MQTT 5.0.0.
/// </summary>
public MqttConnectReasonCode ReasonCode { get; set; }
#endregion
#region Added in MQTTv5.0.0
public MqttConnectReasonCode? ReasonCode { get; set; }
public MqttConnAckPacketProperties Properties { get; set; }
#endregion
/// <summary>
/// Added in MQTT 5.0.0.
/// </summary>
public MqttConnAckPacketProperties Properties { get; set; } = new MqttConnAckPacketProperties();
public override string ToString()
{
return string.Concat("ConnAck: [ReturnCode=", ReturnCode, "] [ReasonCode=", ReasonCode, "] [IsSessionPresent=", IsSessionPresent, "]");
@@ -11,23 +11,23 @@ namespace MQTTnet.Packets
public MqttQualityOfServiceLevel? MaximumQoS { get; set; }
public bool? RetainAvailable { get; set; }
public bool RetainAvailable { get; set; }
public uint? MaximumPacketSize { get; set; }
public string AssignedClientIdentifier { get; set; }
public ushort? TopicAliasMaximum { get; set; }
public ushort TopicAliasMaximum { get; set; }
public string ReasonString { get; set; }
public List<MqttUserProperty> UserProperties { get; set; }
public bool? WildcardSubscriptionAvailable { get; set; }
public bool WildcardSubscriptionAvailable { get; set; }
public bool? SubscriptionIdentifiersAvailable { get; set; }
public bool SubscriptionIdentifiersAvailable { get; set; }
public bool? SharedSubscriptionAvailable { get; set; }
public bool SharedSubscriptionAvailable { get; set; }
public ushort? ServerKeepAlive { get; set; }
@@ -9,12 +9,11 @@ namespace MQTTnet.Packets
public List<MqttTopicFilter> TopicFilters { get; set; } = new List<MqttTopicFilter>();
#region Added in MQTTv5
public MqttSubscribePacketProperties Properties { get; set; }
#endregion
/// <summary>
/// Added in MQTT V5.
/// </summary>
public MqttSubscribePacketProperties Properties { get; set; } = new MqttSubscribePacketProperties();
public override string ToString()
{
var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel));
@@ -4,8 +4,11 @@ namespace MQTTnet.Packets
{
public sealed class MqttSubscribePacketProperties
{
public uint? SubscriptionIdentifier { get; set; }
/// <summary>
/// It is a Protocol Error if the Subscription Identifier has a value of 0.
/// </summary>
public uint SubscriptionIdentifier { get; set; }
public List<MqttUserProperty> UserProperties { get; set; }
public List<MqttUserProperty> UserProperties { get; set; } = new List<MqttUserProperty>();
}
}
@@ -0,0 +1,9 @@
namespace MQTTnet.Server
{
public sealed class GetSubscribedMessagesFilter
{
public bool IsNewSubscription { get; set; }
public MqttTopicFilter TopicFilter { get; set; }
}
}
@@ -15,7 +15,5 @@ namespace MQTTnet.Server
Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage);
Task<IList<MqttApplicationMessage>> GetMessagesAsync();
Task<IList<MqttApplicationMessage>> GetSubscribedMessagesAsync(ICollection<MqttTopicFilter> topicFilters);
}
}
@@ -1,4 +1,5 @@
using MQTTnet.Protocol;
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
{
@@ -8,6 +9,10 @@ namespace MQTTnet.Server.Internal
public bool IsSubscribed { get; set; }
public bool RetainAsPublished { get; set; }
public List<uint> SubscriptionIdentifiers { get; set; }
/// <summary>
/// Gets or sets the quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message.
@@ -7,6 +7,7 @@ using MQTTnet.Client;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.PacketDispatcher;
@@ -22,7 +23,6 @@ namespace MQTTnet.Server.Internal
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
readonly MqttClientSessionsManager _sessionsManager;
readonly IMqttChannelAdapter _channelAdapter;
@@ -30,6 +30,7 @@ namespace MQTTnet.Server.Internal
readonly IMqttServerOptions _serverOptions;
readonly string _endpoint;
readonly MqttConnectPacket _connectPacket;
CancellationTokenSource _cancellationToken;
@@ -39,24 +40,22 @@ namespace MQTTnet.Server.Internal
MqttClientSession session,
IMqttServerOptions serverOptions,
MqttClientSessionsManager sessionsManager,
IMqttRetainedMessagesManager retainedMessagesManager,
IMqttNetLogger logger)
{
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
_endpoint = channelAdapter.Endpoint;
Session = session ?? throw new ArgumentNullException(nameof(session));
ConnectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.CreateScopedLogger(nameof(MqttClientConnection));
}
public string ClientId => ConnectPacket.ClientId;
public string ClientId => _connectPacket.ClientId;
public string Endpoint => _endpoint;
@@ -64,7 +63,7 @@ namespace MQTTnet.Server.Internal
public bool IsRunning { get; private set; }
public MqttConnectPacket ConnectPacket { get; }
public ushort KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
public bool IsReadingPacket => _channelAdapter.IsReadingPacket;
@@ -116,7 +115,7 @@ namespace MQTTnet.Server.Internal
{
_logger.Info("Client '{0}': Session started.", ClientId);
Session.WillMessage = ConnectPacket.WillMessage;
Session.WillMessage = _connectPacket.WillMessage;
using (var cancellationToken = new CancellationTokenSource())
{
@@ -177,19 +176,19 @@ namespace MQTTnet.Server.Internal
if (packet is MqttPublishPacket publishPacket)
{
await HandleIncomingPublishPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPubRelPacket pubRelPacket)
{
await HandleIncomingPubRelPacketAsync(pubRelPacket, cancellationToken).ConfigureAwait(false);
await HandleIncomingPubRelPacket(pubRelPacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttSubscribePacket subscribePacket)
{
await HandleIncomingSubscribePacketAsync(subscribePacket, cancellationToken).ConfigureAwait(false);
await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttUnsubscribePacket unsubscribePacket)
{
await HandleIncomingUnsubscribePacketAsync(unsubscribePacket, cancellationToken).ConfigureAwait(false);
await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPingReqPacket)
{
@@ -239,33 +238,23 @@ namespace MQTTnet.Server.Internal
{
while (!cancellationToken.IsCancellationRequested)
{
queuedApplicationMessage = await Session.ApplicationMessagesQueue.DequeueAsync(cancellationToken).ConfigureAwait(false);
if (queuedApplicationMessage == null)
{
return;
}
queuedApplicationMessage = await Session.ApplicationMessagesQueue.Dequeue(cancellationToken).ConfigureAwait(false);
// Also check the cancellation token here because the dequeue is blocking and may take some time.
if (cancellationToken.IsCancellationRequested)
{
return;
}
publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage);
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;
publishPacket = await InvokeClientMessageQueueInterceptor(publishPacket, queuedApplicationMessage).ConfigureAwait(false);
if (publishPacket == null)
if (queuedApplicationMessage == null)
{
// The interceptor has decided that the message is not relevant and will be fully ignored.
continue;
}
if (publishPacket.QualityOfServiceLevel > 0)
publishPacket = await CreatePublishPacket(queuedApplicationMessage).ConfigureAwait(false);
if (publishPacket == null)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
continue;
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
@@ -274,24 +263,24 @@ namespace MQTTnet.Server.Internal
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
using (var awaiter = _packetDispatcher.AddAwaiter<MqttPubAckPacket>(publishPacket.PacketIdentifier))
using (var awaitable = _packetDispatcher.AddAwaitable<MqttPubAckPacket>(publishPacket.PacketIdentifier))
{
await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
await awaiter.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
await awaitable.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
}
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
using (var awaiter1 = _packetDispatcher.AddAwaiter<MqttPubRecPacket>(publishPacket.PacketIdentifier))
using (var awaiter2 = _packetDispatcher.AddAwaiter<MqttPubCompPacket>(publishPacket.PacketIdentifier))
using (var awaitableRec = _packetDispatcher.AddAwaitable<MqttPubRecPacket>(publishPacket.PacketIdentifier))
using (var awaitableComp = _packetDispatcher.AddAwaitable<MqttPubCompPacket>(publishPacket.PacketIdentifier))
{
await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
var pubRecPacket = await awaiter1.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
var pubRecPacket = await awaitableRec.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
var pubRelPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubRelPacket(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
await SendPacketAsync(pubRelPacket, cancellationToken).ConfigureAwait(false);
await awaiter2.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
await awaitableComp.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
}
}
@@ -329,29 +318,48 @@ namespace MQTTnet.Server.Internal
}
}
async Task<MqttPublishPacket> CreatePublishPacket(MqttQueuedApplicationMessage queuedApplicationMessage)
{
var publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage);
publishPacket.QualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel;
if (_channelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
{
publishPacket.Properties.SubscriptionIdentifiers = queuedApplicationMessage.SubscriptionIdentifiers;
}
// Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9].
publishPacket.Retain = queuedApplicationMessage.IsRetainedMessage;
publishPacket = await InvokeClientMessageQueueInterceptor(publishPacket, queuedApplicationMessage).ConfigureAwait(false);
if (publishPacket == null)
{
// The interceptor has decided that the message is not relevant and will be fully ignored.
return null;
}
if (publishPacket.QualityOfServiceLevel > 0)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
}
return publishPacket;
}
void StopInternal()
{
_cancellationToken?.Cancel();
}
async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<MqttTopicFilter> topicFilters)
{
var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false);
foreach (var applicationMessage in retainedMessages)
{
Session.EnqueueApplicationMessage(applicationMessage, ClientId, true);
}
}
Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
Task HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{
var pubCompPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubCompPacket(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendPacketAsync(pubCompPacket, cancellationToken);
}
async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
{
var subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket, ConnectPacket).ConfigureAwait(false);
var subscribeResult = await Session.SubscriptionsManager.Subscribe(subscribePacket).ConfigureAwait(false);
var subAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateSubAckPacket(subscribePacket, subscribeResult);
await SendPacketAsync(subAckPacket, cancellationToken).ConfigureAwait(false);
@@ -361,19 +369,22 @@ namespace MQTTnet.Server.Internal
StopInternal();
return;
}
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
foreach (var retainedApplicationMessage in subscribeResult.RetainedApplicationMessages)
{
Session.ApplicationMessagesQueue.Enqueue(retainedApplicationMessage);
}
}
async Task HandleIncomingUnsubscribePacketAsync(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{
var reasonCodes = await Session.SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
var reasonCodes = await Session.SubscriptionsManager.Unsubscribe(unsubscribePacket).ConfigureAwait(false);
var unsubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateUnsubAckPacket(unsubscribePacket, reasonCodes);
await SendPacketAsync(unsubAckPacket, cancellationToken).ConfigureAwait(false);
}
Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
HandleTopicAlias(publishPacket);
@@ -1,34 +1,25 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Server.Status;
namespace MQTTnet.Server.Internal
{
public sealed class MqttClientSession
{
readonly IMqttNetScopedLogger _logger;
readonly DateTime _createdTimestamp = DateTime.UtcNow;
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
public MqttClientSession(
string clientId,
IDictionary<object, object> items,
MqttServerEventDispatcher eventDispatcher,
IMqttServerOptions serverOptions,
IMqttRetainedMessagesManager retainedMessagesManager,
IMqttNetLogger logger)
IMqttServerOptions serverOptions,
IMqttRetainedMessagesManager retainedMessagesManager)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
Items = items ?? throw new ArgumentNullException(nameof(items));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
SubscriptionsManager = new MqttClientSubscriptionsManager(this, eventDispatcher, serverOptions);
ApplicationMessagesQueue = new MqttClientSessionApplicationMessagesQueue(serverOptions);
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.CreateScopedLogger(nameof(MqttClientSession));
SubscriptionsManager = new MqttClientSubscriptionsManager(this, serverOptions, eventDispatcher, retainedMessagesManager);
ApplicationMessagesQueue = new MqttClientSessionApplicationMessagesQueue(serverOptions);
}
public string ClientId { get; }
@@ -45,42 +36,7 @@ namespace MQTTnet.Server.Internal
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary<object, object> Items { get; }
public bool EnqueueApplicationMessage(MqttApplicationMessage applicationMessage, string senderClientId, bool isRetainedApplicationMessage)
{
var checkSubscriptionsResult = SubscriptionsManager.CheckSubscriptions(applicationMessage.Topic, applicationMessage.QualityOfServiceLevel);
if (!checkSubscriptionsResult.IsSubscribed)
{
return false;
}
_logger.Verbose("Client '{0}': Queued application message with topic '{1}'.", ClientId, applicationMessage.Topic);
ApplicationMessagesQueue.Enqueue(applicationMessage, senderClientId, checkSubscriptionsResult.QualityOfServiceLevel, isRetainedApplicationMessage);
return true;
}
public async Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters)
{
if (topicFilters is null) throw new ArgumentNullException(nameof(topicFilters));
await SubscriptionsManager.SubscribeAsync(topicFilters).ConfigureAwait(false);
var matchingRetainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false);
foreach (var matchingRetainedMessage in matchingRetainedMessages)
{
EnqueueApplicationMessage(matchingRetainedMessage, null, true);
}
}
public Task UnsubscribeAsync(IEnumerable<string> topicFilters)
{
if (topicFilters is null) throw new ArgumentNullException(nameof(topicFilters));
return SubscriptionsManager.UnsubscribeAsync(topicFilters);
}
public void FillSessionStatus(MqttSessionStatus status)
{
status.ClientId = ClientId;
@@ -2,7 +2,6 @@
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
{
@@ -18,20 +17,7 @@ namespace MQTTnet.Server.Internal
}
public int Count => _messageQueue.Count;
public void Enqueue(MqttApplicationMessage applicationMessage, string senderClientId, MqttQualityOfServiceLevel qualityOfServiceLevel, bool isRetainedMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
Enqueue(new MqttQueuedApplicationMessage
{
ApplicationMessage = applicationMessage,
SenderClientId = senderClientId,
SubscriptionQualityOfServiceLevel = qualityOfServiceLevel,
IsRetainedMessage = isRetainedMessage
});
}
public void Enqueue(MqttQueuedApplicationMessage queuedApplicationMessage)
{
if (queuedApplicationMessage == null) throw new ArgumentNullException(nameof(queuedApplicationMessage));
@@ -55,7 +41,7 @@ namespace MQTTnet.Server.Internal
}
}
public async Task<MqttQueuedApplicationMessage> DequeueAsync(CancellationToken cancellationToken)
public async Task<MqttQueuedApplicationMessage> Dequeue(CancellationToken cancellationToken)
{
var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false);
if (!dequeueResult.IsSuccess)
@@ -21,8 +21,8 @@ namespace MQTTnet.Server.Internal
readonly BlockingCollection<MqttPendingApplicationMessage> _messageQueue = new BlockingCollection<MqttPendingApplicationMessage>();
readonly AsyncLock _createConnectionSyncRoot = new AsyncLock();
readonly Dictionary<string, MqttClientConnection> _connections = new Dictionary<string, MqttClientConnection>();
readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
readonly Dictionary<string, MqttClientConnection> _clientConnections = new Dictionary<string, MqttClientConnection>(4096);
readonly Dictionary<string, MqttClientSession> _clientSessions = new Dictionary<string, MqttClientSession>(4096);
readonly IDictionary<object, object> _serverSessionItems = new ConcurrentDictionary<object, object>();
@@ -80,7 +80,7 @@ namespace MQTTnet.Server.Internal
_logger.Warning("Client '{0}': First received packet was no 'CONNECT' packet [MQTT-3.1.0-1].", channelAdapter.Endpoint);
return null;
}
public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
{
MqttClientConnection clientConnection = null;
@@ -95,7 +95,7 @@ namespace MQTTnet.Server.Internal
}
MqttConnAckPacket connAckPacket;
var connectionValidatorContext = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success)
{
@@ -127,12 +127,12 @@ namespace MQTTnet.Server.Internal
{
if (clientConnection.ClientId != null)
{
// in case it is a takeover _connections already contains the new connection
// in case it is a takeover _clientConnections already contains the new connection
if (!clientConnection.IsTakenOver)
{
lock (_connections)
lock (_clientConnections)
{
_connections.Remove(clientConnection.ClientId);
_clientConnections.Remove(clientConnection.ClientId);
}
if (!_options.EnablePersistentSessions)
@@ -161,10 +161,10 @@ namespace MQTTnet.Server.Internal
public async Task CloseAllConnectionsAsync()
{
List<MqttClientConnection> connections;
lock (_connections)
lock (_clientConnections)
{
connections = _connections.Values.ToList();
_connections.Clear();
connections = _clientConnections.Values.ToList();
_clientConnections.Clear();
}
foreach (var connection in connections)
@@ -175,9 +175,9 @@ namespace MQTTnet.Server.Internal
public List<MqttClientConnection> GetConnections()
{
lock (_connections)
lock (_clientConnections)
{
return _connections.Values.ToList();
return _clientConnections.Values.ToList();
}
}
@@ -185,9 +185,9 @@ namespace MQTTnet.Server.Internal
{
var result = new List<IMqttClientStatus>();
lock (_connections)
lock (_clientConnections)
{
foreach (var connection in _connections.Values)
foreach (var connection in _clientConnections.Values)
{
var clientStatus = new MqttClientStatus(connection);
connection.FillClientStatus(clientStatus);
@@ -207,9 +207,9 @@ namespace MQTTnet.Server.Internal
{
var result = new List<IMqttSessionStatus>();
lock (_sessions)
lock (_clientSessions)
{
foreach (var session in _sessions.Values)
foreach (var session in _clientSessions.Values)
{
var sessionStatus = new MqttSessionStatus(session, this);
session.FillSessionStatus(sessionStatus);
@@ -228,33 +228,52 @@ namespace MQTTnet.Server.Internal
_messageQueue.Add(new MqttPendingApplicationMessage(applicationMessage, sender));
}
public Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters)
public async Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return GetSession(clientId).SubscribeAsync(topicFilters);
if (topicFilters is null) throw new ArgumentNullException(nameof(topicFilters));
var fakeSubscribePacket = new MqttSubscribePacket
{
TopicFilters = topicFilters.ToList()
};
var clientSession = GetClientSession(clientId);
var subscribeResult = await clientSession.SubscriptionsManager.Subscribe(fakeSubscribePacket).ConfigureAwait(false);
foreach (var retainedApplicationMessage in subscribeResult.RetainedApplicationMessages)
{
clientSession.ApplicationMessagesQueue.Enqueue(retainedApplicationMessage);
}
}
public Task UnsubscribeAsync(string clientId, IEnumerable<string> topicFilters)
public Task UnsubscribeAsync(string clientId, ICollection<string> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return GetSession(clientId).UnsubscribeAsync(topicFilters);
var fakeUnsubscribePacket = new MqttUnsubscribePacket
{
TopicFilters = topicFilters.ToList()
};
return GetClientSession(clientId).SubscriptionsManager.Unsubscribe(fakeUnsubscribePacket);
}
public async Task DeleteSessionAsync(string clientId)
{
MqttClientConnection connection;
lock (_connections)
lock (_clientConnections)
{
_connections.TryGetValue(clientId, out connection);
_clientConnections.TryGetValue(clientId, out connection);
}
lock (_sessions)
lock (_clientSessions)
{
_sessions.Remove(clientId);
_clientSessions.Remove(clientId);
}
if (connection != null)
@@ -272,12 +291,12 @@ namespace MQTTnet.Server.Internal
async Task TryProcessQueuedApplicationMessagesAsync(CancellationToken cancellationToken)
{
//make sure all queued messages are proccessed befor server stops
// Make sure all queued messages are proccessed befor server stops.
while (!cancellationToken.IsCancellationRequested || _messageQueue.Any())
{
try
{
await TryProcessNextQueuedApplicationMessageAsync().ConfigureAwait(false);
await TryProcessNextQueuedApplicationMessage().ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -289,14 +308,14 @@ namespace MQTTnet.Server.Internal
}
}
async Task TryProcessNextQueuedApplicationMessageAsync()
async Task TryProcessNextQueuedApplicationMessage()
{
try
{
MqttPendingApplicationMessage queuedApplicationMessage;
MqttPendingApplicationMessage pendingApplicationMessage;
try
{
queuedApplicationMessage = _messageQueue.Take();
pendingApplicationMessage = _messageQueue.Take();
}
catch (ArgumentNullException)
{
@@ -307,9 +326,9 @@ namespace MQTTnet.Server.Internal
return;
}
var clientConnection = queuedApplicationMessage.Sender;
var clientConnection = pendingApplicationMessage.Sender;
var senderClientId = clientConnection?.ClientId ?? _options.ClientId;
var applicationMessage = queuedApplicationMessage.ApplicationMessage;
var applicationMessage = pendingApplicationMessage.ApplicationMessage;
var interceptor = _options.ApplicationMessageInterceptor;
if (interceptor != null)
@@ -343,18 +362,41 @@ namespace MQTTnet.Server.Internal
var deliveryCount = 0;
List<MqttClientSession> sessions;
lock (_sessions)
lock (_clientSessions)
{
sessions = _sessions.Values.ToList();
sessions = _clientSessions.Values.ToList();
}
foreach (var clientSession in sessions)
{
var isSubscribed = clientSession.EnqueueApplicationMessage(applicationMessage, senderClientId, false);
if (isSubscribed)
var checkSubscriptionsResult = clientSession.SubscriptionsManager.CheckSubscriptions(
applicationMessage.Topic,
applicationMessage.QualityOfServiceLevel,
senderClientId);
if (!checkSubscriptionsResult.IsSubscribed)
{
deliveryCount++;
continue;
}
_logger.Verbose("Client '{0}': Queued application message with topic '{1}'.", clientSession.ClientId, applicationMessage.Topic);
var queuedApplicationMessage = new MqttQueuedApplicationMessage
{
ApplicationMessage = applicationMessage,
SubscriptionQualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel,
SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers
};
if (checkSubscriptionsResult.RetainAsPublished)
{
// Transfer the original retain state from the publisher.
// This is a MQTTv5 feature.
queuedApplicationMessage.IsRetainedMessage = applicationMessage.Retain;
}
clientSession.ApplicationMessagesQueue.Enqueue(queuedApplicationMessage);
deliveryCount++;
}
if (deliveryCount == 0)
@@ -413,9 +455,9 @@ namespace MQTTnet.Server.Internal
using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{
MqttClientSession session;
lock (_sessions)
lock (_clientSessions)
{
if (!_sessions.TryGetValue(connectPacket.ClientId, out session))
if (!_clientSessions.TryGetValue(connectPacket.ClientId, out session))
{
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
session = CreateSession(connectPacket.ClientId, sessionItems);
@@ -433,17 +475,17 @@ namespace MQTTnet.Server.Internal
}
}
_sessions[connectPacket.ClientId] = session;
_clientSessions[connectPacket.ClientId] = session;
}
MqttClientConnection existingConnection;
lock (_connections)
lock (_clientConnections)
{
_connections.TryGetValue(connectPacket.ClientId, out existingConnection);
_clientConnections.TryGetValue(connectPacket.ClientId, out existingConnection);
connection = CreateConnection(connectPacket, channelAdapter, session);
_connections[connectPacket.ClientId] = connection;
_clientConnections[connectPacket.ClientId] = connection;
}
if (existingConnection != null)
@@ -490,11 +532,11 @@ namespace MQTTnet.Server.Internal
return interceptorContext;
}
MqttClientSession GetSession(string clientId)
MqttClientSession GetClientSession(string clientId)
{
lock (_sessions)
lock (_clientSessions)
{
if (!_sessions.TryGetValue(clientId, out var session))
if (!_clientSessions.TryGetValue(clientId, out var session))
{
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
}
@@ -511,7 +553,6 @@ namespace MQTTnet.Server.Internal
session,
_options,
this,
_retainedMessagesManager,
_rootLogger);
}
@@ -522,8 +563,7 @@ namespace MQTTnet.Server.Internal
sessionItems,
_eventDispatcher,
_options,
_retainedMessagesManager,
_rootLogger);
_retainedMessagesManager);
}
}
}
@@ -14,34 +14,41 @@ namespace MQTTnet.Server.Internal
// Since writing is done by multiple threads (server API or connection thread), we cannot avoid locking
// completely by swapping references etc.
readonly ReaderWriterLockSlim _subscriptionsLock = new ReaderWriterLockSlim();
readonly Dictionary<string, MqttTopicFilter> _subscriptions = new Dictionary<string, MqttTopicFilter>();
readonly Dictionary<string, Subscription> _subscriptions = new Dictionary<string, Subscription>(4096);
readonly MqttClientSession _clientSession;
readonly IMqttServerOptions _options;
readonly MqttServerEventDispatcher _eventDispatcher;
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
public MqttClientSubscriptionsManager(MqttClientSession clientSession, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions serverOptions)
public MqttClientSubscriptionsManager(
MqttClientSession clientSession,
IMqttServerOptions serverOptions,
MqttServerEventDispatcher eventDispatcher,
IMqttRetainedMessagesManager retainedMessagesManager)
{
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession));
_options = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_eventDispatcher = eventDispatcher ?? throw new ArgumentNullException(nameof(eventDispatcher));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
}
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttSubscribePacket subscribePacket, MqttConnectPacket connectPacket)
public async Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket)
{
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket));
var result = new MqttClientSubscribeResult();
var retainedApplicationMessages = await _retainedMessagesManager.GetMessagesAsync().ConfigureAwait(false);
var result = new SubscribeResult();
foreach (var originalTopicFilter in subscribePacket.TopicFilters)
// The topic filters are order by its QoS so that the higher QoS will win over a
// lower one.
foreach (var originalTopicFilter in subscribePacket.TopicFilters.OrderByDescending(f => f.QualityOfServiceLevel))
{
var interceptorContext = await InterceptSubscribeAsync(originalTopicFilter).ConfigureAwait(false);
var interceptorContext = await InterceptSubscribe(originalTopicFilter).ConfigureAwait(false);
var finalTopicFilter = interceptorContext?.TopicFilter ?? originalTopicFilter;
var acceptSubscription = interceptorContext?.AcceptSubscription ?? true;
var closeConnection = interceptorContext?.CloseConnection ?? false;
if (finalTopicFilter == null || string.IsNullOrEmpty(finalTopicFilter.Topic) || !acceptSubscription)
if (string.IsNullOrEmpty(finalTopicFilter.Topic) || !acceptSubscription)
{
result.ReturnCodes.Add(MqttSubscribeReturnCode.Failure);
result.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError);
@@ -57,54 +64,22 @@ namespace MQTTnet.Server.Internal
result.CloseConnection = true;
}
if (!acceptSubscription || string.IsNullOrEmpty(finalTopicFilter?.Topic))
if (!acceptSubscription || string.IsNullOrEmpty(finalTopicFilter.Topic))
{
continue;
}
_subscriptionsLock.EnterWriteLock();
try
{
_subscriptions[finalTopicFilter.Topic] = finalTopicFilter;
}
finally
{
_subscriptionsLock.ExitWriteLock();
}
var subscription = CreateSubscription(finalTopicFilter, subscribePacket);
await _eventDispatcher.SafeNotifyClientSubscribedTopicAsync(_clientSession.ClientId, finalTopicFilter).ConfigureAwait(false);
FilterRetainedApplicationMessages(retainedApplicationMessages, subscription, result);
}
return result;
}
public async Task SubscribeAsync(IEnumerable<MqttTopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
foreach (var topicFilter in topicFilters)
{
var interceptorContext = await InterceptSubscribeAsync(topicFilter).ConfigureAwait(false);
if (interceptorContext != null && !interceptorContext.AcceptSubscription)
{
continue;
}
_subscriptionsLock.EnterWriteLock();
try
{
_subscriptions[topicFilter.Topic] = topicFilter;
}
finally
{
_subscriptionsLock.ExitWriteLock();
}
await _eventDispatcher.SafeNotifyClientSubscribedTopicAsync(_clientSession.ClientId, topicFilter).ConfigureAwait(false);
}
}
public async Task<List<MqttUnsubscribeReasonCode>> UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket)
public async Task<List<MqttUnsubscribeReasonCode>> Unsubscribe(MqttUnsubscribePacket unsubscribePacket)
{
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
@@ -112,7 +87,7 @@ namespace MQTTnet.Server.Internal
foreach (var topicFilter in unsubscribePacket.TopicFilters)
{
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
var interceptorContext = await InterceptUnsubscribe(topicFilter).ConfigureAwait(false);
if (interceptorContext != null && !interceptorContext.AcceptUnsubscription)
{
reasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError);
@@ -140,52 +115,50 @@ namespace MQTTnet.Server.Internal
return reasonCodes;
}
public async Task UnsubscribeAsync(IEnumerable<string> topicFilters)
public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel, string senderClientId)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
foreach (var topicFilter in topicFilters)
{
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
if (interceptorContext != null && !interceptorContext.AcceptUnsubscription)
{
continue;
}
_subscriptionsLock.EnterWriteLock();
try
{
_subscriptions.Remove(topicFilter);
}
finally
{
_subscriptionsLock.ExitWriteLock();
}
}
}
public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel)
{
Dictionary<string, MqttTopicFilter> subscriptionsCopy;
List<Subscription> subscriptions;
_subscriptionsLock.EnterReadLock();
try
{
subscriptionsCopy = new Dictionary<string, MqttTopicFilter>(_subscriptions);
subscriptions = _subscriptions.Values.ToList();
}
finally
{
_subscriptionsLock.ExitReadLock();
}
var senderIsReceiver = string.Equals(senderClientId, _clientSession.ClientId);
var qosLevels = new HashSet<MqttQualityOfServiceLevel>();
foreach (var subscription in subscriptionsCopy)
var subscriptionIdentifiers = new HashSet<uint>();
var retainAsPublished = false;
foreach (var subscription in subscriptions)
{
if (!MqttTopicFilterComparer.IsMatch(topic, subscription.Key))
if (subscription.NoLocal && senderIsReceiver)
{
// This is a MQTTv5 feature!
continue;
}
if (subscription.RetainAsPublished)
{
// This is a MQTTv5 feature!
retainAsPublished = true;
}
if (!MqttTopicFilterComparer.IsMatch(topic, subscription.Topic))
{
continue;
}
qosLevels.Add(subscription.Value.QualityOfServiceLevel);
qosLevels.Add(subscription.QualityOfServiceLevel);
if (subscription.Identifier > 0)
{
subscriptionIdentifiers.Add(subscription.Identifier);
}
}
if (qosLevels.Count == 0)
@@ -193,7 +166,84 @@ namespace MQTTnet.Server.Internal
return CheckSubscriptionsResult.NotSubscribed;
}
return CreateSubscriptionResult(qosLevel, qosLevels);
return new CheckSubscriptionsResult
{
IsSubscribed = true,
RetainAsPublished = retainAsPublished,
SubscriptionIdentifiers = subscriptionIdentifiers.ToList(),
QualityOfServiceLevel = GetEffectiveQoS(qosLevel, qosLevels)
};
}
Subscription CreateSubscription(MqttTopicFilter topicFilter, MqttSubscribePacket subscribePacket)
{
var subscription = new Subscription
{
Topic = topicFilter.Topic,
NoLocal = topicFilter.NoLocal,
RetainHandling = topicFilter.RetainHandling,
RetainAsPublished = topicFilter.RetainAsPublished,
QualityOfServiceLevel = topicFilter.QualityOfServiceLevel,
Identifier = subscribePacket.Properties?.SubscriptionIdentifier ?? 0
};
_subscriptionsLock.EnterWriteLock();
try
{
subscription.IsNewSubscription = !_subscriptions.ContainsKey(topicFilter.Topic);
_subscriptions[topicFilter.Topic] = subscription;
}
finally
{
_subscriptionsLock.ExitWriteLock();
}
return subscription;
}
static void FilterRetainedApplicationMessages(IList<MqttApplicationMessage> retainedApplicationMessages, Subscription subscription, SubscribeResult subscribeResult)
{
for (var i = retainedApplicationMessages.Count - 1; i >= 0; i--)
{
var retainedApplicationMessage = retainedApplicationMessages[i];
if (retainedApplicationMessage == null)
{
continue;
}
if (subscription.RetainHandling == MqttRetainHandling.DoNotSendOnSubscribe)
{
// This is a MQTT V5+ feature.
continue;
}
if (subscription.RetainHandling == MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly && !subscription.IsNewSubscription)
{
// This is a MQTT V5+ feature.
continue;
}
if (!MqttTopicFilterComparer.IsMatch(retainedApplicationMessage.Topic, subscription.Topic))
{
continue;
}
var queuedApplicationMessage = new MqttQueuedApplicationMessage
{
ApplicationMessage = retainedApplicationMessage,
IsRetainedMessage = true,
SubscriptionQualityOfServiceLevel = subscription.QualityOfServiceLevel
};
if (subscription.Identifier > 0)
{
queuedApplicationMessage.SubscriptionIdentifiers = new List<uint> { subscription.Identifier };
}
subscribeResult.RetainedApplicationMessages.Add(queuedApplicationMessage);
retainedApplicationMessages[i] = null;
}
}
static MqttSubscribeReturnCode ConvertToSubscribeReturnCode(MqttQualityOfServiceLevel qualityOfServiceLevel)
@@ -206,7 +256,7 @@ namespace MQTTnet.Server.Internal
return (MqttSubscribeReasonCode)(int)qualityOfServiceLevel;
}
async Task<MqttSubscriptionInterceptorContext> InterceptSubscribeAsync(MqttTopicFilter topicFilter)
async Task<MqttSubscriptionInterceptorContext> InterceptSubscribe(MqttTopicFilter topicFilter)
{
var interceptor = _options.SubscriptionInterceptor;
if (interceptor == null)
@@ -226,7 +276,7 @@ namespace MQTTnet.Server.Internal
return context;
}
async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribeAsync(string topicFilter)
async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribe(string topicFilter)
{
var interceptor = _options.UnsubscriptionInterceptor;
if (interceptor == null)
@@ -246,7 +296,7 @@ namespace MQTTnet.Server.Internal
return context;
}
static CheckSubscriptionsResult CreateSubscriptionResult(MqttQualityOfServiceLevel qosLevel, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels)
static MqttQualityOfServiceLevel GetEffectiveQoS(MqttQualityOfServiceLevel qosLevel, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels)
{
MqttQualityOfServiceLevel effectiveQoS;
if (subscribedQoSLevels.Contains(qosLevel))
@@ -262,11 +312,7 @@ namespace MQTTnet.Server.Internal
effectiveQoS = subscribedQoSLevels.Max();
}
return new CheckSubscriptionsResult
{
IsSubscribed = true,
QualityOfServiceLevel = effectiveQoS
};
return effectiveQoS;
}
}
}
@@ -12,11 +12,11 @@ namespace MQTTnet.Server.Internal
public sealed class MqttRetainedMessagesManager : IMqttRetainedMessagesManager
{
readonly AsyncLock _storageAccessLock = new AsyncLock();
readonly Dictionary<string, MqttApplicationMessage> _messages = new Dictionary<string, MqttApplicationMessage>();
readonly Dictionary<string, MqttApplicationMessage> _messages = new Dictionary<string, MqttApplicationMessage>(4096);
IMqttNetScopedLogger _logger;
IMqttServerOptions _options;
// TODO: Get rid of the logger here!
public Task Start(IMqttServerOptions options, IMqttNetLogger logger)
{
@@ -116,36 +116,7 @@ namespace MQTTnet.Server.Internal
_logger.Error(exception, "Unhandled exception while handling retained messages.");
}
}
public Task<IList<MqttApplicationMessage>> GetSubscribedMessagesAsync(ICollection<MqttTopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
var matchingRetainedMessages = new List<MqttApplicationMessage>();
List<MqttApplicationMessage> retainedMessages;
lock (_messages)
{
retainedMessages = _messages.Values.ToList();
}
foreach (var retainedMessage in retainedMessages)
{
foreach (var topicFilter in topicFilters)
{
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
{
continue;
}
matchingRetainedMessages.Add(retainedMessage);
break;
}
}
return Task.FromResult((IList<MqttApplicationMessage>)matchingRetainedMessages);
}
public Task<IList<MqttApplicationMessage>> GetMessagesAsync()
{
lock (_messages)
@@ -76,7 +76,7 @@ namespace MQTTnet.Server.Internal
return;
}
if (connection.ConnectPacket.KeepAlivePeriod == 0)
if (connection.KeepAlivePeriod == 0)
{
// The keep alive feature is not used by the current connection.
return;
@@ -92,7 +92,7 @@ namespace MQTTnet.Server.Internal
// Values described here: [MQTT-3.1.2-24].
// If the client sends 5 sec. the server will allow up to 7.5 seconds.
// If the client sends 1 sec. the server will allow up to 1.5 seconds.
var maxDurationWithoutPacket = connection.ConnectPacket.KeepAlivePeriod * 1.5D;
var maxDurationWithoutPacket = connection.KeepAlivePeriod * 1.5D;
var secondsWithoutPackage = (now - connection.Statistics.LastPacketReceivedTimestamp).TotalSeconds;
if (secondsWithoutPackage < maxDurationWithoutPacket)
@@ -0,0 +1,16 @@
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
{
public sealed class SubscribeResult
{
public List<MqttSubscribeReturnCode> ReturnCodes { get; } = new List<MqttSubscribeReturnCode>(128);
public List<MqttSubscribeReasonCode> ReasonCodes { get; } = new List<MqttSubscribeReasonCode>(128);
public List<MqttQueuedApplicationMessage> RetainedApplicationMessages { get; } = new List<MqttQueuedApplicationMessage>(1024);
public bool CloseConnection { get; set; }
}
}
@@ -0,0 +1,21 @@
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
{
public sealed class Subscription
{
public string Topic { get; set; }
public bool NoLocal { get; set; }
public MqttRetainHandling RetainHandling { get; set; }
public bool RetainAsPublished { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public uint Identifier { get; set; }
public bool IsNewSubscription { get; set; }
}
}
@@ -1,14 +0,0 @@
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class MqttClientSubscribeResult
{
public List<MqttSubscribeReturnCode> ReturnCodes { get; } = new List<MqttSubscribeReturnCode>();
public List<MqttSubscribeReasonCode> ReasonCodes { get; } = new List<MqttSubscribeReasonCode>();
public bool CloseConnection { get; set; }
}
}
@@ -17,7 +17,7 @@ namespace MQTTnet.Server
public MqttConnectionValidatorContext(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
{
_connectPacket = connectPacket;
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
_clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
}
@@ -35,9 +35,9 @@ namespace MQTTnet.Server
public MqttProtocolVersion ProtocolVersion => _clientAdapter.PacketFormatterAdapter.ProtocolVersion;
public string Username => _connectPacket?.Username;
public string Username => _connectPacket.Username;
public byte[] RawPassword => _connectPacket?.Password;
public byte[] RawPassword => _connectPacket.Password;
public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
@@ -45,7 +45,7 @@ namespace MQTTnet.Server
/// Gets or sets the will delay interval.
/// This is the time between the client disconnect and the time the will message will be sent.
/// </summary>
public MqttApplicationMessage WillMessage => _connectPacket?.WillMessage;
public MqttApplicationMessage WillMessage => _connectPacket.WillMessage;
/// <summary>
/// Gets or sets a value indicating whether clean sessions are used or not.
@@ -55,7 +55,7 @@ namespace MQTTnet.Server
/// It can also connect as a durable client using a persistent connection.
/// In this mode, the broker will store subscription information, and undelivered messages for the client.
/// </summary>
public bool? CleanSession => _connectPacket?.CleanSession;
public bool? CleanSession => _connectPacket.CleanSession;
/// <summary>
/// Gets or sets the keep alive period.
@@ -64,7 +64,7 @@ namespace MQTTnet.Server
/// This message exchange confirms that the connection is open and working.
/// This period is known as the keep alive period.
/// </summary>
public ushort? KeepAlivePeriod => _connectPacket?.KeepAlivePeriod;
public ushort? KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
/// <summary>
/// Gets or sets the user properties.
@@ -73,57 +73,57 @@ namespace MQTTnet.Server
/// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only.
/// </summary>
public List<MqttUserProperty> UserProperties => _connectPacket?.Properties?.UserProperties;
public List<MqttUserProperty> UserProperties => _connectPacket.Properties?.UserProperties;
/// <summary>
/// Gets or sets the authentication data.
/// Hint: MQTT 5 feature only.
/// </summary>
public byte[] AuthenticationData => _connectPacket?.Properties?.AuthenticationData;
public byte[] AuthenticationData => _connectPacket.Properties?.AuthenticationData;
/// <summary>
/// Gets or sets the authentication method.
/// Hint: MQTT 5 feature only.
/// </summary>
public string AuthenticationMethod => _connectPacket?.Properties?.AuthenticationMethod;
public string AuthenticationMethod => _connectPacket.Properties?.AuthenticationMethod;
public uint? MaximumPacketSize => _connectPacket?.Properties?.MaximumPacketSize;
public uint? MaximumPacketSize => _connectPacket.Properties?.MaximumPacketSize;
/// <summary>
/// Gets or sets the receive maximum.
/// This gives the maximum length of the receive messages.
/// </summary>
public ushort? ReceiveMaximum => _connectPacket?.Properties?.ReceiveMaximum;
public ushort? ReceiveMaximum => _connectPacket.Properties?.ReceiveMaximum;
/// <summary>
/// Gets or sets the topic alias maximum.
/// This gives the maximum length of the topic alias.
/// </summary>
public ushort? TopicAliasMaximum => _connectPacket?.Properties?.TopicAliasMaximum;
public ushort TopicAliasMaximum => _connectPacket.Properties?.TopicAliasMaximum ?? 0;
/// <summary>
/// Gets the request problem information.
/// Hint: MQTT 5 feature only.
/// </summary>
public bool? RequestProblemInformation => _connectPacket?.Properties?.RequestProblemInformation;
public bool? RequestProblemInformation => _connectPacket.Properties?.RequestProblemInformation;
/// <summary>
/// Gets the request response information.
/// Hint: MQTT 5 feature only.
/// </summary>
public bool? RequestResponseInformation => _connectPacket?.Properties?.RequestResponseInformation;
public bool? RequestResponseInformation => _connectPacket.Properties?.RequestResponseInformation;
/// <summary>
/// Gets the session expiry interval.
/// The time after a session expires when it's not actively used.
/// </summary>
public uint? SessionExpiryInterval => _connectPacket?.Properties?.SessionExpiryInterval;
public uint? SessionExpiryInterval => _connectPacket.Properties?.SessionExpiryInterval;
/// <summary>
/// Gets or sets the will delay interval.
/// This is the time between the client disconnect and the time the will message will be sent.
/// </summary>
public uint? WillDelayInterval => _connectPacket?.Properties?.WillDelayInterval;
public uint? WillDelayInterval => _connectPacket.Properties?.WillDelayInterval;
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
@@ -144,7 +144,7 @@ namespace MQTTnet.Server
/// Gets or sets the reason code. When a MQTTv3 client connects the enum value must be one which is
/// also supported in MQTTv3. Otherwise the connection attempt will fail because not all codes can be
/// converted properly.
/// Hint: MQTT 5 feature only.
/// MQTTv5 only.
/// </summary>
public MqttConnectReasonCode ReasonCode { get; set; } = MqttConnectReasonCode.Success;
@@ -159,12 +159,23 @@ namespace MQTTnet.Server
/// <summary>
/// Gets or sets the response authentication data.
/// Hint: MQTT 5 feature only.
/// MQTTv5 only.
/// </summary>
public byte[] ResponseAuthenticationData { get; set; }
/// <summary>
/// Gets or sets the assigned client identifier.
/// MQTTv5 only.
/// </summary>
public string AssignedClientIdentifier { get; set; }
public string ReasonString { get; set; }
/// <summary>
/// Gets or sets the server reference. This can be used together with i.e. "Server Moved" to send
/// a different server address to the client.
/// MQTTv5 only.
/// </summary>
public string ServerReference { get; set; }
}
}
@@ -1,4 +1,4 @@
using System;
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Server
@@ -11,6 +11,10 @@ namespace MQTTnet.Server
public bool IsRetainedMessage { get; set; }
public List<uint> SubscriptionIdentifiers { get; set; }
public bool IsDuplicate { get; set; }
/// <summary>
/// Gets or sets the subscription quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message.
@@ -20,14 +24,5 @@ namespace MQTTnet.Server
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary>
public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; }
[Obsolete("Use 'SubscriptionQualityOfServiceLevel' instead.")]
public MqttQualityOfServiceLevel QualityOfServiceLevel
{
get => SubscriptionQualityOfServiceLevel;
set => SubscriptionQualityOfServiceLevel = value;
}
public bool IsDuplicate { get; set; }
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ using MQTTnet.Server.Internal;
namespace MQTTnet.Server
{
public class MqttServerOptions : IMqttServerOptions
public sealed class MqttServerOptions : IMqttServerOptions
{
public MqttServerTcpEndpointOptions DefaultEndpointOptions { get; } = new MqttServerTcpEndpointOptions();