This commit is contained in:
Christian
2022-02-20 21:10:46 +01:00
committed by GitHub
parent 47e674e4d3
commit 2ce9b94ec9
608 changed files with 22364 additions and 19420 deletions
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Server
{
public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs
{
/// <summary>
/// Gets the application message which was not consumed by any client.
/// </summary>
public MqttApplicationMessage ApplicationMessage { get; internal set; }
/// <summary>
/// Gets the ID of the client which has sent the affected application message.
/// </summary>
public string SenderId { get; internal set; }
}
}
@@ -1,9 +1,13 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Formatter;
namespace MQTTnet.Server
{
public sealed class MqttServerClientConnectedEventArgs : EventArgs
public sealed class ClientConnectedEventArgs : EventArgs
{
/// <summary>
/// Gets the client identifier of the connected client.
@@ -1,8 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Server
{
public sealed class MqttServerClientDisconnectedEventArgs : EventArgs
public sealed class ClientDisconnectedEventArgs : EventArgs
{
/// <summary>
/// Gets the client identifier.
@@ -1,8 +1,13 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public sealed class MqttServerClientSubscribedTopicEventArgs : EventArgs
public sealed class ClientSubscribedTopicEventArgs : EventArgs
{
/// <summary>
/// Gets the client identifier.
@@ -1,8 +1,12 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Server
{
public sealed class MqttServerClientUnsubscribedTopicEventArgs : EventArgs
public sealed class ClientUnsubscribedTopicEventArgs : EventArgs
{
/// <summary>
/// Gets the client identifier.
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public sealed class InterceptingPacketEventArgs : EventArgs
{
/// <summary>
/// Gets the client ID which has sent the packet or will receive the packet.
/// </summary>
public string ClientId { get; internal set; }
/// <summary>
/// Gets the endpoint of the sending or receiving client.
/// </summary>
public string Endpoint { get; internal set; }
/// <summary>
/// Gets or sets the MQTT packet which was received or will be sent.
/// </summary>
public MqttPacket Packet { get; set; }
/// <summary>
/// Gets or sets whether the packet should be processed or not.
/// </summary>
public bool ProcessPacket { get; set; } = true;
/// <summary>
/// Gets the cancellation token from the connection managing thread.
/// Use this in further event processing.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
}
}
@@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace MQTTnet.Server
{
public sealed class InterceptingPublishEventArgs : EventArgs
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public MqttApplicationMessage ApplicationMessage { get; set; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; internal set; }
/// <summary>
/// Gets the response which will be sent to the client via the PUBACK etc. packets.
/// </summary>
public PublishResponse Response { get; } = new PublishResponse();
/// <summary>
/// Gets or sets whether the publish should be processed internally.
/// </summary>
public bool ProcessPublish { get; set; } = true;
public bool CloseConnection { get; set; }
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
}
}
@@ -0,0 +1,69 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public sealed class InterceptingSubscriptionEventArgs : EventArgs
{
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
/// <summary>
/// Gets or sets whether the broker should close the client connection.
/// </summary>
public bool CloseConnection { get; set; }
/// <summary>
/// Gets or sets whether the broker should create an internal subscription for the client.
/// The broker can also avoid this and return "success" to the client.
/// This feature allows using the MQTT Broker as the Frontend and another system as the backend.
/// </summary>
public bool ProcessSubscription { get; set; } = true;
/// <summary>
/// Gets or sets the reason string which will be sent to the client in the SUBACK packet.
/// </summary>
public string ReasonString { get; set; }
/// <summary>
/// Gets the response which will be sent to the client via the SUBACK packet.
/// </summary>
public SubscribeResponse Response { get; } = new SubscribeResponse();
/// <summary>
/// Gets the current client session.
/// </summary>
public MqttSessionStatus Session { get; internal set; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; internal set; }
/// <summary>
/// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public MqttTopicFilter TopicFilter { get; set; }
/// <summary>
/// Gets or sets the user properties.
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
}
}
@@ -0,0 +1,54 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace MQTTnet.Server
{
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
/// <summary>
/// Gets or sets the MQTT topic.
/// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected client.
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator).
/// </summary>
public string Topic { get; internal set; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; internal set; }
/// <summary>
/// Gets the response which will be sent to the client via the UNSUBACK pocket.
/// </summary>
public UnsubscribeResponse Response { get; } = new UnsubscribeResponse();
/// <summary>
/// Gets or sets whether the broker should remove an internal subscription for the client.
/// The broker can also avoid this and return "success" to the client.
/// This feature allows using the MQTT Broker as the Frontend and another system as the backend.
/// </summary>
public bool ProcessUnsubscription { get; set; } = true;
/// <summary>
/// Gets or sets whether the broker should close the client connection.
/// </summary>
public bool CloseConnection { get; set; }
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
}
}
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
namespace MQTTnet.Server
{
public sealed class LoadingRetainedMessagesEventArgs : EventArgs
{
public List<MqttApplicationMessage> LoadedRetainedMessages { get; set; } = new List<MqttApplicationMessage>();
}
}
@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public sealed class PreparingSessionEventArgs : EventArgs
{
public string Id { get; internal set; }
// TODO: Allow adding of packets to the queue etc.
/*
* The Session State in the Server consists of:
· The existence of a Session, even if the rest of the Session State is empty.
· The Clients subscriptions, including any Subscription Identifiers.
· QoS 1 and QoS 2 messages which have been sent to the Client, but have not been completely acknowledged.
· QoS 1 and QoS 2 messages pending transmission to the Client and OPTIONALLY QoS 0 messages pending transmission to the Client.
· QoS 2 messages which have been received from the Client, but have not been completely acknowledged.The Will Message and the Will Delay Interval
· If the Session is currently not connected, the time at which the Session will end and Session State will be discarded.
*/
public bool IsExistingSession { get; set; }
public List<MqttSubscription> Subscriptions { get; } = new List<MqttSubscription>();
public List<MqttPublishPacket> PublishPackets { get; } = new List<MqttPublishPacket>();
public IDictionary<object, object> Items { get; set; }
/// <summary>
/// Gets the last will message.
/// In MQTT, you use the last will message feature to notify other clients about an ungracefully disconnected client.
/// </summary>
// TODO: Use single properties. No entire will message.
MqttApplicationMessage WillMessage { get; set; }
/// <summary>
/// Gets the will delay interval.
/// This is the time between the client disconnect and the time the will message will be sent.
/// </summary>
uint? WillDelayInterval { get; set; }
DateTime? SessionExpiryTimestamp { get; set; }
}
}
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
namespace MQTTnet.Server
{
public sealed class RetainedMessageChangedEventArgs : EventArgs
{
public string ClientId { get; internal set; }
public MqttApplicationMessage ChangedRetainedMessage { get; internal set; }
public List<MqttApplicationMessage> StoredRetainedMessages { get; internal set; }
}
}
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Server
{
public sealed class SessionDeletedEventArgs : EventArgs
{
/// <summary>
/// Gets the ID of the session.
/// </summary>
public string Id { get; internal set; }
}
}
@@ -0,0 +1,184 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class ValidatingConnectionEventArgs : EventArgs
{
readonly IMqttChannelAdapter _clientAdapter;
readonly MqttConnectPacket _connectPacket;
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
{
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
_clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
}
/// <summary>
/// Gets or sets the assigned client identifier.
/// MQTTv5 only.
/// </summary>
public string AssignedClientIdentifier { get; set; }
/// <summary>
/// Gets or sets the authentication data.
/// Hint: MQTT 5 feature only.
/// </summary>
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
/// <summary>
/// Gets or sets the authentication method.
/// Hint: MQTT 5 feature only.
/// </summary>
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
/// <summary>
/// Gets or sets a value indicating whether clean sessions are used or not.
/// When a client connects to a broker it can connect using either a non persistent connection (clean session) or a
/// persistent connection.
/// With a non persistent connection the broker doesn't store any subscription information or undelivered messages for
/// the client.
/// This mode is ideal when the client only publishes messages.
/// 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 X509Certificate2 ClientCertificate => _clientAdapter.ClientCertificate;
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId => _connectPacket.ClientId;
public string Endpoint => _clientAdapter.Endpoint;
public bool IsSecureConnection => _clientAdapter.IsSecureConnection;
/// <summary>
/// Gets or sets the keep alive period.
/// The connection is normally left open by the client so that is can send and receive data at any time.
/// If no data flows over an open connection for a certain time period then the client will generate a PINGREQ and
/// expect to receive a PINGRESP from the broker.
/// 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;
/// <summary>
/// A value of 0 indicates that the value is not used.
/// </summary>
public uint MaximumPacketSize => _connectPacket.MaximumPacketSize;
public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
public MqttProtocolVersion ProtocolVersion => _clientAdapter.PacketFormatterAdapter.ProtocolVersion;
public byte[] RawPassword => _connectPacket.Password;
/// <summary>
/// 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.
/// MQTTv5 only.
/// </summary>
public MqttConnectReasonCode ReasonCode { get; set; } = MqttConnectReasonCode.Success;
public string ReasonString { get; set; }
/// <summary>
/// Gets or sets the receive maximum.
/// This gives the maximum length of the receive messages.
/// A value of 0 indicates that the value is not used.
/// </summary>
public ushort ReceiveMaximum => _connectPacket.ReceiveMaximum;
/// <summary>
/// Gets the request problem information.
/// Hint: MQTT 5 feature only.
/// </summary>
public bool RequestProblemInformation => _connectPacket.RequestProblemInformation;
/// <summary>
/// Gets the request response information.
/// Hint: MQTT 5 feature only.
/// </summary>
public bool RequestResponseInformation => _connectPacket.RequestResponseInformation;
/// <summary>
/// Gets or sets the response authentication data.
/// MQTTv5 only.
/// </summary>
public byte[] ResponseAuthenticationData { get; set; }
/// <summary>
/// Gets or sets the response 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> ResponseUserProperties { 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; }
/// <summary>
/// Gets the session expiry interval.
/// The time after a session expires when it's not actively used.
/// A value of 0 means no expiation.
/// </summary>
public uint SessionExpiryInterval => _connectPacket.SessionExpiryInterval;
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; internal set; }
/// <summary>
/// Gets or sets the topic alias maximum.
/// This gives the maximum length of the topic alias.
/// A value of 0 indicates that the value is not used.
/// </summary>
public ushort TopicAliasMaximum => _connectPacket.TopicAliasMaximum;
public string Username => _connectPacket.Username;
/// <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 => _connectPacket.UserProperties;
/// <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.
/// A value of 0 indicates that the value is not used.
/// </summary>
public uint WillDelayInterval => _connectPacket.WillDelayInterval;
}
}
@@ -1,9 +0,0 @@
namespace MQTTnet.Server
{
public sealed class GetSubscribedMessagesFilter
{
public bool IsNewSubscription { get; set; }
public MqttTopicFilter TopicFilter { get; set; }
}
}
@@ -1,15 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttClientSession
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
string ClientId { get; }
Task StopAsync();
}
}
@@ -1,20 +0,0 @@
using MQTTnet.Diagnostics;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Diagnostics.Logger;
namespace MQTTnet.Server
{
public interface IMqttRetainedMessagesManager
{
Task Start(IMqttServerOptions options, IMqttNetLogger logger);
Task LoadMessagesAsync();
Task ClearMessagesAsync();
Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage);
Task<IList<MqttApplicationMessage>> GetMessagesAsync();
}
}
-33
View File
@@ -1,33 +0,0 @@
using System;
using MQTTnet.Server.Status;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServer : IApplicationMessageReceiver, IApplicationMessagePublisher, IDisposable
{
bool IsStarted { get; }
IMqttServerStartedHandler StartedHandler { get; set; }
IMqttServerStoppedHandler StoppedHandler { get; set; }
IMqttServerClientConnectedHandler ClientConnectedHandler { get; set; }
IMqttServerClientDisconnectedHandler ClientDisconnectedHandler { get; set; }
IMqttServerClientSubscribedTopicHandler ClientSubscribedTopicHandler { get; set; }
IMqttServerClientUnsubscribedTopicHandler ClientUnsubscribedTopicHandler { get; set; }
IMqttServerOptions Options { get; }
Task<IList<IMqttClientStatus>> GetClientStatusAsync();
Task<IList<IMqttSessionStatus>> GetSessionStatusAsync();
Task<IList<MqttApplicationMessage>> GetRetainedApplicationMessagesAsync();
Task ClearRetainedApplicationMessagesAsync();
Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters);
Task UnsubscribeAsync(string clientId, ICollection<string> topicFilters);
Task StartAsync(IMqttServerOptions options);
Task StopAsync();
}
}
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerApplicationMessageInterceptor
{
Task InterceptApplicationMessagePublishAsync(MqttApplicationMessageInterceptorContext context);
}
}
@@ -1,7 +0,0 @@
namespace MQTTnet.Server
{
public interface IMqttServerCertificateCredentials
{
string Password { get; }
}
}
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerClientConnectedHandler
{
Task HandleClientConnectedAsync(MqttServerClientConnectedEventArgs eventArgs);
}
}
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerClientDisconnectedHandler
{
Task HandleClientDisconnectedAsync(MqttServerClientDisconnectedEventArgs eventArgs);
}
}
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerClientMessageQueueInterceptor
{
Task InterceptClientMessageQueueEnqueueAsync(MqttClientMessageQueueInterceptorContext context);
}
}
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerClientSubscribedTopicHandler
{
Task HandleClientSubscribedTopicAsync(MqttServerClientSubscribedTopicEventArgs eventArgs);
}
}
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerClientUnsubscribedTopicHandler
{
Task HandleClientUnsubscribedTopicAsync(MqttServerClientUnsubscribedTopicEventArgs eventArgs);
}
}
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerConnectionValidator
{
Task ValidateConnectionAsync(MqttConnectionValidatorContext context);
}
}
@@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Diagnostics.Logger;
namespace MQTTnet.Server
{
public interface IMqttServerFactory
{
IList<Func<IMqttFactory, IMqttServerAdapter>> DefaultServerAdapters { get; }
IMqttServer CreateMqttServer();
IMqttServer CreateMqttServer(IMqttNetLogger logger);
IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters);
IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger);
}
}
@@ -1,35 +0,0 @@
using System;
namespace MQTTnet.Server
{
public interface IMqttServerOptions
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
string ClientId { get; }
bool EnablePersistentSessions { get; }
int MaxPendingMessagesPerClient { get; }
MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; }
TimeSpan DefaultCommunicationTimeout { get; }
TimeSpan KeepAliveMonitorInterval { get; }
IMqttServerConnectionValidator ConnectionValidator { get; }
IMqttServerSubscriptionInterceptor SubscriptionInterceptor { get; }
IMqttServerUnsubscriptionInterceptor UnsubscriptionInterceptor { get; }
IMqttServerApplicationMessageInterceptor ApplicationMessageInterceptor { get; }
IMqttServerClientMessageQueueInterceptor ClientMessageQueueInterceptor { get; }
IMqttServerApplicationMessageInterceptor UndeliveredMessageInterceptor { get; }
MqttServerTcpEndpointOptions DefaultEndpointOptions { get; }
MqttServerTlsTcpEndpointOptions TlsEndpointOptions { get; }
IMqttServerStorage Storage { get; }
IMqttRetainedMessagesManager RetainedMessagesManager { get; }
}
}
@@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
namespace MQTTnet.Server
{
public interface IMqttServerPersistedSession
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
string ClientId { get; }
IDictionary<object, object> Items { get; }
IList<MqttTopicFilter> Subscriptions { get; }
/// <summary>
/// Gets the last will message.
/// In MQTT, you use the last will message feature to notify other clients about an ungracefully disconnected client.
/// </summary>
MqttApplicationMessage WillMessage { get; }
/// <summary>
/// Gets the will delay interval.
/// This is the time between the client disconnect and the time the will message will be sent.
/// </summary>
uint? WillDelayInterval { get; }
DateTime? SessionExpiryTimestamp { get; }
IList<MqttQueuedApplicationMessage> PendingApplicationMessages { get; }
}
}
@@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerPersistedSessionsStorage
{
Task<IList<IMqttServerPersistedSession>> LoadPersistedSessionsAsync();
}
}
@@ -1,10 +0,0 @@
using System;
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerStartedHandler
{
Task HandleServerStartedAsync(EventArgs eventArgs);
}
}
@@ -1,10 +0,0 @@
using System;
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerStoppedHandler
{
Task HandleServerStoppedAsync(EventArgs eventArgs);
}
}
@@ -1,12 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerStorage
{
Task SaveRetainedMessagesAsync(IList<MqttApplicationMessage> messages);
Task<IList<MqttApplicationMessage>> LoadRetainedMessagesAsync();
}
}
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerSubscriptionInterceptor
{
Task InterceptSubscriptionAsync(MqttSubscriptionInterceptorContext context);
}
}
@@ -1,9 +0,0 @@
using System.Threading.Tasks;
namespace MQTTnet.Server
{
public interface IMqttServerUnsubscriptionInterceptor
{
Task InterceptUnsubscriptionAsync(MqttUnsubscriptionInterceptorContext context);
}
}
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Server
{
public sealed class InjectedMqttApplicationMessage
{
public InjectedMqttApplicationMessage(MqttApplicationMessage applicationMessage)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
public string SenderClientId { get; set; }
public MqttApplicationMessage ApplicationMessage { get; set; }
}
}
@@ -1,11 +1,15 @@
using System.Collections.Generic;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
namespace MQTTnet.Server
{
public struct CheckSubscriptionsResult
public sealed class CheckSubscriptionsResult
{
public static CheckSubscriptionsResult NotSubscribed = new CheckSubscriptionsResult();
public static CheckSubscriptionsResult NotSubscribed { get; } = new CheckSubscriptionsResult();
public bool IsSubscribed { get; set; }
@@ -13,14 +17,6 @@ namespace MQTTnet.Server.Internal
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.
/// There are 3 QoS levels in MQTT:
/// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At least once (1): Message gets delivered at least once (one time or more often).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary>
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
}
}
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace MQTTnet.Server
{
public interface ISubscriptionChangedNotification
{
void OnSubscriptionsAdded(MqttSession clientSession, List<string> subscriptionsTopics);
void OnSubscriptionsRemoved(MqttSession clientSession, List<string> subscriptionTopics);
}
}
@@ -0,0 +1,533 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.PacketDispatcher;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class MqttClient
{
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
readonly MqttClientSessionsManager _sessionsManager;
readonly MqttNetSourceLogger _logger;
readonly MqttServerOptions _serverOptions;
readonly MqttServerEventContainer _eventContainer;
readonly MqttConnectPacket _connectPacket;
CancellationTokenSource _cancellationToken;
public MqttClient(
MqttConnectPacket connectPacket,
IMqttChannelAdapter channelAdapter,
MqttSession session,
MqttServerOptions serverOptions,
MqttServerEventContainer eventContainer,
MqttClientSessionsManager sessionsManager,
IMqttNetLogger logger)
{
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_eventContainer = eventContainer;
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
ChannelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
Endpoint = channelAdapter.Endpoint;
Session = session ?? throw new ArgumentNullException(nameof(session));
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.WithSource(nameof(MqttClient));
}
public string Id => _connectPacket.ClientId;
public string Endpoint { get; }
public IMqttChannelAdapter ChannelAdapter { get; }
public MqttClientStatistics Statistics { get; } = new MqttClientStatistics();
public bool IsRunning { get; private set; }
public ushort KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
public MqttSession Session { get; }
public bool IsTakenOver { get; set; }
public bool IsCleanDisconnect { get; private set; }
public async Task StopAsync(MqttDisconnectReasonCode reason)
{
IsRunning = false;
if (reason == MqttDisconnectReasonCode.SessionTakenOver || reason == MqttDisconnectReasonCode.KeepAliveTimeout)
{
// Is is very important to send the DISCONNECT packet here BEFORE cancelling the
// token because the entire connection is closed (disposed) as soon as the cancellation
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
// at the client!
await TrySendDisconnectPacket(reason).ConfigureAwait(false);
}
StopInternal();
}
public void ResetStatistics()
{
ChannelAdapter.ResetStatistics();
}
public async Task RunAsync()
{
_logger.Info("Client '{0}': Session started.", Id);
Session.LatestConnectPacket = _connectPacket;
Session.WillMessageSent = false;
using (var cancellationToken = new CancellationTokenSource())
{
_cancellationToken = cancellationToken;
try
{
Task.Run(() => SendPacketsLoop(cancellationToken.Token), cancellationToken.Token).RunInBackground(_logger);
IsRunning = true;
await ReceivePackagesLoop(cancellationToken.Token).ConfigureAwait(false);
}
finally
{
IsRunning = false;
_cancellationToken = null;
cancellationToken.Cancel();
}
}
_packetDispatcher.CancelAll();
if (!IsTakenOver && !IsCleanDisconnect && Session.LatestConnectPacket.WillFlag && !Session.WillMessageSent)
{
var willPublishPacket = _packetFactories.Publish.Create(Session.LatestConnectPacket);
var willApplicationMessage = _applicationMessageFactory.Create(willPublishPacket);
_= _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
Session.WillMessageSent = true;
_logger.Info("Client '{0}': Published will message.", Id);
}
_logger.Info("Client '{0}': Connection stopped.", Id);
}
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
{
packet = await InterceptPacketAsync(packet, cancellationToken).ConfigureAwait(false);
if (packet == null)
{
// The interceptor has decided that this packet will not used at all.
// This might break the protocol but the user wants that.
return;
}
await ChannelAdapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false);
Statistics.HandleSentPacket(packet);
}
async Task<MqttPacket> InterceptPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
{
if (!_eventContainer.InterceptingOutboundPacketEvent.HasHandlers)
{
return packet;
}
var interceptingPacketEventArgs = new InterceptingPacketEventArgs
{
ClientId = Id,
Endpoint = Endpoint,
Packet = packet,
CancellationToken = cancellationToken
};
await _eventContainer.InterceptingOutboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
if (!interceptingPacketEventArgs.ProcessPacket || packet == null)
{
return null;
}
return interceptingPacketEventArgs.Packet;
}
async Task ReceivePackagesLoop(CancellationToken cancellationToken)
{
try
{
// We do not listen for the cancellation token here because the internal buffer might still
// contain data to be read even if the TCP connection was already dropped. So we rely on an
// own exception in the reading loop!
while (!cancellationToken.IsCancellationRequested)
{
var packet = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
if (packet == null)
{
return;
}
var processPacket = true;
if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
{
var interceptingPacketEventArgs = new InterceptingPacketEventArgs
{
ClientId = Id,
Endpoint = Endpoint,
Packet = packet,
CancellationToken = cancellationToken
};
await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
packet = interceptingPacketEventArgs.Packet;
processPacket = interceptingPacketEventArgs.ProcessPacket;
}
if (!processPacket || packet == null)
{
// Restart the receiving process to get the next packet ignoring the current one..
continue;
}
Statistics.HandleReceivedPacket(packet);
if (packet is MqttPublishPacket publishPacket)
{
await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPubAckPacket pubAckPacket)
{
Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier);
}
else if (packet is MqttPubCompPacket pubCompPacket)
{
Session.AcknowledgePublishPacket(pubCompPacket.PacketIdentifier);
}
else if (packet is MqttPubRecPacket pubRecPacket)
{
HandleIncomingPubRecPacket(pubRecPacket);
}
else if (packet is MqttPubRelPacket pubRelPacket)
{
HandleIncomingPubRelPacket(pubRelPacket);
}
else if (packet is MqttSubscribePacket subscribePacket)
{
await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttUnsubscribePacket unsubscribePacket)
{
await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPingReqPacket)
{
// See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
//await SendPacketAsync(MqttPingRespPacket.Instance, cancellationToken).ConfigureAwait(false);
Session.EnqueuePacket(new MqttPacketBusItem(MqttPingRespPacket.Instance));
}
else if (packet is MqttPingRespPacket)
{
throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only.");
}
else if (packet is MqttDisconnectPacket)
{
IsCleanDisconnect = true;
return;
}
else
{
if (!_packetDispatcher.TryDispatch(packet))
{
throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
}
}
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", Id);
}
else
{
_logger.Error(exception, "Client '{0}': Error while receiving client packets.", Id);
}
}
}
async Task SendPacketsLoop(CancellationToken cancellationToken)
{
MqttPacketBusItem packetBusItem = null;
try
{
while (!cancellationToken.IsCancellationRequested)
{
packetBusItem = await Session.DequeuePacketAsync(cancellationToken).ConfigureAwait(false);
// Also check the cancellation token here because the dequeue is blocking and may take some time.
if (cancellationToken.IsCancellationRequested)
{
return;
}
try
{
await SendPacketAsync(packetBusItem.Packet, cancellationToken).ConfigureAwait(false);
packetBusItem.MarkAsDelivered();
}
catch (OperationCanceledException)
{
packetBusItem.MarkAsCancelled();
}
catch (Exception exception)
{
packetBusItem.MarkAsFailed(exception);
}
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
if (exception is MqttCommunicationTimedOutException)
{
_logger.Warning(exception, "Client '{0}': Sending publish packet failed: Timeout.", Id);
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Client '{0}': Sending publish packet failed: Communication exception.", Id);
}
else
{
_logger.Error(exception, "Client '{0}': Sending publish packet failed.", Id);
}
if (packetBusItem?.Packet is MqttPublishPacket publishPacket)
{
if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
publishPacket.Dup = true;
Session.EnqueuePacket(new MqttPacketBusItem(publishPacket));
}
}
StopInternal();
}
}
void StopInternal()
{
try
{
_cancellationToken?.Cancel();
}
catch (ObjectDisposedException)
{
// This can happen when connections are created and dropped very quickly.
// It is not an issue if the cancellation token cannot be cancelled multiple times.
}
}
void HandleIncomingPubRecPacket(MqttPubRecPacket pubRecPacket)
{
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
Session.EnqueuePacket(new MqttPacketBusItem(pubRelPacket));
}
void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket)
{
var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
Session.EnqueuePacket(new MqttPacketBusItem(pubCompPacket));
}
async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
{
var subscribeResult = await Session.SubscriptionsManager.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false);
var subAckPacket = _packetFactories.SubAck.Create(subscribePacket, subscribeResult);
Session.EnqueuePacket(new MqttPacketBusItem(subAckPacket));
if (subscribeResult.CloseConnection)
{
StopInternal();
return;
}
if (subscribeResult.RetainedMessages != null)
{
foreach (var retainedApplicationMessage in subscribeResult.RetainedMessages)
{
var publishPacket = _packetFactories.Publish.Create(retainedApplicationMessage.ApplicationMessage);
Session.EnqueuePacket(new MqttPacketBusItem(publishPacket));
}
}
}
async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{
var unsubscribeResult = await Session.SubscriptionsManager.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false);
var unsubAckPacket = _packetFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult);
Session.EnqueuePacket(new MqttPacketBusItem(unsubAckPacket));
if (unsubscribeResult.CloseConnection)
{
StopInternal();
}
}
async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
HandleTopicAlias(publishPacket);
InterceptingPublishEventArgs interceptingPublishEventArgs = null;
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
var closeConnection = false;
var processPublish = true;
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
{
interceptingPublishEventArgs = new InterceptingPublishEventArgs
{
ClientId = Id,
ApplicationMessage = applicationMessage,
SessionItems = Session.Items,
ProcessPublish = true,
CloseConnection = false,
CancellationToken = cancellationToken
};
if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
{
// This can happen if a topic alias us used but the topic is
// unknown to the server.
interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
interceptingPublishEventArgs.ProcessPublish = false;
}
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
closeConnection = interceptingPublishEventArgs.CloseConnection;
processPublish = interceptingPublishEventArgs.ProcessPublish;
}
if (closeConnection)
{
await StopAsync(MqttDisconnectReasonCode.UnspecifiedError);
return;
}
if (processPublish && applicationMessage != null)
{
await _sessionsManager.DispatchApplicationMessage(Id, applicationMessage).ConfigureAwait(false);
}
switch (publishPacket.QualityOfServiceLevel)
{
case MqttQualityOfServiceLevel.AtMostOnce:
{
// Do nothing since QoS 0 has no ack at all!
break;
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
var pubAckPacket = _packetFactories.PubAck.Create(publishPacket, interceptingPublishEventArgs);
Session.EnqueuePacket(new MqttPacketBusItem(pubAckPacket));
break;
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
var pubRecPacket = _packetFactories.PubRec.Create(publishPacket, interceptingPublishEventArgs);
Session.EnqueuePacket(new MqttPacketBusItem(pubRecPacket));
break;
}
default:
{
throw new MqttCommunicationException("Received a not supported QoS level.");
}
}
}
void HandleTopicAlias(MqttPublishPacket publishPacket)
{
if (publishPacket.TopicAlias == 0)
{
return;
}
lock (_topicAlias)
{
if (!string.IsNullOrEmpty(publishPacket.Topic))
{
_topicAlias[publishPacket.TopicAlias] = publishPacket.Topic;
}
else
{
if (_topicAlias.TryGetValue(publishPacket.TopicAlias, out var topic))
{
publishPacket.Topic = topic;
}
else
{
_logger.Warning("Client '{0}': Received invalid topic alias ({1}).", Id, publishPacket.TopicAlias);
}
}
}
}
async Task TrySendDisconnectPacket(MqttDisconnectReasonCode reasonCode)
{
try
{
var disconnectPacket = _packetFactories.Disconnect.Create(reasonCode);
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
{
await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
}
}
catch (Exception exception)
{
_logger.Warning(exception, "Client '{0}': Error while sending DISCONNECT packet (ReasonCode = {1}).", Id, reasonCode);
}
}
}
}
@@ -1,495 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.PacketDispatcher;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server.Status;
namespace MQTTnet.Server.Internal
{
public sealed class MqttClientConnection
{
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
readonly MqttClientSessionsManager _sessionsManager;
readonly IMqttChannelAdapter _channelAdapter;
readonly MqttNetSourceLogger _logger;
readonly IMqttServerOptions _serverOptions;
readonly string _endpoint;
readonly MqttConnectPacket _connectPacket;
CancellationTokenSource _cancellationToken;
public MqttClientConnection(
MqttConnectPacket connectPacket,
IMqttChannelAdapter channelAdapter,
MqttClientSession session,
IMqttServerOptions serverOptions,
MqttClientSessionsManager sessionsManager,
IMqttNetLogger logger)
{
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
_endpoint = channelAdapter.Endpoint;
Session = session ?? throw new ArgumentNullException(nameof(session));
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.WithSource(nameof(MqttClientConnection));
}
public string ClientId => _connectPacket.ClientId;
public string Endpoint => _endpoint;
public MqttClientConnectionStatistics Statistics { get; } = new MqttClientConnectionStatistics();
public bool IsRunning { get; private set; }
public ushort KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
public bool IsReadingPacket => _channelAdapter.IsReadingPacket;
public MqttClientSession Session { get; }
public bool IsTakenOver { get; set; }
public bool IsCleanDisconnect { get; private set; }
public async Task StopAsync(MqttClientDisconnectReason reason)
{
IsRunning = false;
if (reason == MqttClientDisconnectReason.SessionTakenOver || reason == MqttClientDisconnectReason.KeepAliveTimeout)
{
// Is is very important to send the DISCONNECT packet here BEFORE cancelling the
// token because the entire connection is closed (disposed) as soon as the cancellation
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
// at the client!
await TrySendDisconnectPacket(reason).ConfigureAwait(false);
}
StopInternal();
}
public void ResetStatistics()
{
_channelAdapter.ResetStatistics();
}
public void FillClientStatus(MqttClientStatus clientStatus)
{
clientStatus.ClientId = ClientId;
clientStatus.Endpoint = _endpoint;
clientStatus.ProtocolVersion = _channelAdapter.PacketFormatterAdapter.ProtocolVersion;
clientStatus.BytesSent = _channelAdapter.BytesSent;
clientStatus.BytesReceived = _channelAdapter.BytesReceived;
Statistics.FillClientStatus(clientStatus);
}
public async Task RunAsync()
{
_logger.Info("Client '{0}': Session started.", ClientId);
Session.WillMessage = _connectPacket.WillMessage;
using (var cancellationToken = new CancellationTokenSource())
{
_cancellationToken = cancellationToken;
try
{
Task.Run(() => SendPacketsLoop(cancellationToken.Token), cancellationToken.Token).RunInBackground(_logger);
Session.IsCleanSession = false;
IsRunning = true;
await ReceivePackagesLoop(cancellationToken.Token).ConfigureAwait(false);
}
finally
{
IsRunning = false;
cancellationToken.Cancel();
_cancellationToken = null;
}
}
_packetDispatcher.CancelAll();
if (!IsTakenOver && !IsCleanDisconnect && Session.WillMessage != null)
{
_sessionsManager.DispatchApplicationMessage(Session.WillMessage, this);
Session.WillMessage = null;
}
_logger.Info("Client '{0}': Connection stopped.", ClientId);
}
Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
{
return _channelAdapter.SendPacketAsync(packet, cancellationToken).ContinueWith(task => { Statistics.HandleSentPacket(packet); }, cancellationToken);
}
async Task ReceivePackagesLoop(CancellationToken cancellationToken)
{
try
{
// We do not listen for the cancellation token here because the internal buffer might still
// contain data to be read even if the TCP connection was already dropped. So we rely on an
// own exception in the reading loop!
while (!cancellationToken.IsCancellationRequested)
{
var packet = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
if (packet == null)
{
// The client has closed the connection gracefully.
return;
}
Statistics.HandleReceivedPacket(packet);
if (packet is MqttPublishPacket publishPacket)
{
await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPubRelPacket pubRelPacket)
{
await HandleIncomingPubRelPacket(pubRelPacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttSubscribePacket subscribePacket)
{
await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttUnsubscribePacket unsubscribePacket)
{
await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPingReqPacket)
{
// See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
await SendPacketAsync(MqttPingRespPacket.Instance, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPingRespPacket)
{
throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only.");
}
else if (packet is MqttDisconnectPacket)
{
IsCleanDisconnect = true;
return;
}
else
{
if (!_packetDispatcher.TryDispatch(packet))
{
throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
}
}
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId);
}
else
{
_logger.Error(exception, "Client '{0}': Error while receiving client packets.", ClientId);
}
}
}
async Task SendPacketsLoop(CancellationToken cancellationToken)
{
MqttQueuedApplicationMessage queuedApplicationMessage = null;
MqttPublishPacket publishPacket = null;
try
{
while (!cancellationToken.IsCancellationRequested)
{
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;
}
if (queuedApplicationMessage == null)
{
continue;
}
publishPacket = await CreatePublishPacket(queuedApplicationMessage).ConfigureAwait(false);
if (publishPacket == null)
{
continue;
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
using (var awaitable = _packetDispatcher.AddAwaitable<MqttPubAckPacket>(publishPacket.PacketIdentifier))
{
await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
await awaitable.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
}
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
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 awaitableRec.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
var pubRelPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubRelPacket(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
await SendPacketAsync(pubRelPacket, cancellationToken).ConfigureAwait(false);
await awaitableComp.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
}
}
_logger.Verbose("Client '{0}': Queued application message sent.", ClientId);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
if (exception is MqttCommunicationTimedOutException)
{
_logger.Warning(exception, "Client '{0}': Sending publish packet failed: Timeout.", ClientId);
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Client '{0}': Sending publish packet failed: Communication exception.", ClientId);
}
else
{
_logger.Error(exception, "Client '{0}': Sending publish packet failed.", ClientId);
}
if (publishPacket?.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
if (queuedApplicationMessage != null)
{
queuedApplicationMessage.IsDuplicate = true;
Session.ApplicationMessagesQueue.Enqueue(queuedApplicationMessage);
}
}
StopInternal();
}
}
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();
}
Task HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{
var pubCompPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubCompPacket(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendPacketAsync(pubCompPacket, cancellationToken);
}
async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
{
var subscribeResult = await Session.SubscriptionsManager.Subscribe(subscribePacket).ConfigureAwait(false);
var subAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateSubAckPacket(subscribePacket, subscribeResult);
await SendPacketAsync(subAckPacket, cancellationToken).ConfigureAwait(false);
if (subscribeResult.CloseConnection)
{
StopInternal();
return;
}
foreach (var retainedApplicationMessage in subscribeResult.RetainedApplicationMessages)
{
Session.ApplicationMessagesQueue.Enqueue(retainedApplicationMessage);
}
}
async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{
var reasonCodes = await Session.SubscriptionsManager.Unsubscribe(unsubscribePacket).ConfigureAwait(false);
var unsubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateUnsubAckPacket(unsubscribePacket, reasonCodes);
await SendPacketAsync(unsubAckPacket, cancellationToken).ConfigureAwait(false);
}
Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
HandleTopicAlias(publishPacket);
var applicationMessage = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket);
_sessionsManager.DispatchApplicationMessage(applicationMessage, this);
switch (publishPacket.QualityOfServiceLevel)
{
case MqttQualityOfServiceLevel.AtMostOnce:
{
return PlatformAbstractionLayer.CompletedTask;
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
var pubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubAckPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendPacketAsync(pubAckPacket, cancellationToken);
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
var pubRecPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubRecPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendPacketAsync(pubRecPacket, cancellationToken);
}
default:
{
throw new MqttCommunicationException("Received a not supported QoS level.");
}
}
}
void HandleTopicAlias(MqttPublishPacket publishPacket)
{
if (publishPacket.Properties?.TopicAlias == null)
{
return;
}
var topicAlias = publishPacket.Properties.TopicAlias.Value;
lock (_topicAlias)
{
if (!string.IsNullOrEmpty(publishPacket.Topic))
{
_topicAlias[topicAlias] = publishPacket.Topic;
}
else
{
if (_topicAlias.TryGetValue(topicAlias, out var topic))
{
publishPacket.Topic = topic;
}
else
{
}
}
}
}
async Task TrySendDisconnectPacket(MqttClientDisconnectReason reason)
{
try
{
var disconnectOptions = new MqttClientDisconnectOptions
{
ReasonCode = reason,
ReasonString = reason.ToString()
};
var disconnectPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateDisconnectPacket(disconnectOptions);
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
{
await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
}
}
catch (Exception exception)
{
_logger.Warning(exception, "Client '{{0}}': Error while sending DISCONNECT packet (Reason = {1}).", ClientId, reason);
}
}
async Task<MqttPublishPacket> InvokeClientMessageQueueInterceptor(MqttPublishPacket publishPacket, MqttQueuedApplicationMessage queuedApplicationMessage)
{
if (_serverOptions.ClientMessageQueueInterceptor == null)
{
return publishPacket;
}
var context = new MqttClientMessageQueueInterceptorContext
{
SenderClientId = queuedApplicationMessage.SenderClientId,
ReceiverClientId = ClientId,
ApplicationMessage = queuedApplicationMessage.ApplicationMessage,
SubscriptionQualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel
};
if (_serverOptions.ClientMessageQueueInterceptor != null)
{
await _serverOptions.ClientMessageQueueInterceptor.InterceptClientMessageQueueEnqueueAsync(context).ConfigureAwait(false);
}
if (!context.AcceptEnqueue || context.ApplicationMessage == null)
{
return null;
}
publishPacket.Topic = context.ApplicationMessage.Topic;
publishPacket.Payload = context.ApplicationMessage.Payload;
publishPacket.QualityOfServiceLevel = context.SubscriptionQualityOfServiceLevel;
return publishPacket;
}
}
}
@@ -1,99 +0,0 @@
using System;
using System.Threading;
using MQTTnet.Packets;
using MQTTnet.Server.Status;
namespace MQTTnet.Server.Internal
{
public sealed class MqttClientConnectionStatistics
{
readonly DateTime _connectedTimestamp;
DateTime _lastNonKeepAlivePacketReceivedTimestamp;
DateTime _lastPacketReceivedTimestamp;
DateTime _lastPacketSentTimestamp;
// Start with 1 because the CONNACK packet is not counted here.
long _receivedPacketsCount = 1;
// Start with 1 because the CONNECT packet is not counted here.
long _sentPacketsCount = 1;
long _receivedApplicationMessagesCount;
long _sentApplicationMessagesCount;
public MqttClientConnectionStatistics()
{
_connectedTimestamp = DateTime.UtcNow;
_lastPacketReceivedTimestamp = _connectedTimestamp;
_lastPacketSentTimestamp = _connectedTimestamp;
_lastNonKeepAlivePacketReceivedTimestamp = _connectedTimestamp;
}
/// <summary>
/// Timestamp of the last package that has been received from the client ("sent" from the client's perspective)
/// </summary>
public DateTime LastPacketSentTimestamp => _lastPacketSentTimestamp;
/// <summary>
/// Timestamp of the last package that has been sent to the client ("received" from the client's perspective)
/// </summary>
public DateTime LastPacketReceivedTimestamp => _lastPacketReceivedTimestamp;
public void HandleReceivedPacket(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
// This class is tracking all values from Clients perspective!
_lastPacketSentTimestamp = DateTime.UtcNow;
Interlocked.Increment(ref _sentPacketsCount);
if (packet is MqttPublishPacket)
{
Interlocked.Increment(ref _sentApplicationMessagesCount);
}
if (!(packet is MqttPingReqPacket || packet is MqttPingRespPacket))
{
_lastNonKeepAlivePacketReceivedTimestamp = _lastPacketReceivedTimestamp;
}
}
public void HandleSentPacket(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
// This class is tracking all values from Clients perspective!
_lastPacketReceivedTimestamp = DateTime.UtcNow;
Interlocked.Increment(ref _receivedPacketsCount);
if (packet is MqttPublishPacket)
{
Interlocked.Increment(ref _receivedApplicationMessagesCount);
}
}
public void FillClientStatus(MqttClientStatus clientStatus)
{
if (clientStatus == null) throw new ArgumentNullException(nameof(clientStatus));
clientStatus.ConnectedTimestamp = _connectedTimestamp;
clientStatus.ReceivedPacketsCount = Interlocked.Read(ref _receivedPacketsCount);
clientStatus.SentPacketsCount = Interlocked.Read(ref _sentPacketsCount);
clientStatus.ReceivedApplicationMessagesCount = Interlocked.Read(ref _receivedApplicationMessagesCount);
clientStatus.SentApplicationMessagesCount = Interlocked.Read(ref _sentApplicationMessagesCount);
clientStatus.LastPacketReceivedTimestamp = _lastPacketReceivedTimestamp;
clientStatus.LastPacketSentTimestamp = _lastPacketSentTimestamp;
clientStatus.LastNonKeepAlivePacketReceivedTimestamp = _lastNonKeepAlivePacketReceivedTimestamp;
}
}
}
@@ -1,60 +0,0 @@
using System;
using System.Collections.Generic;
using MQTTnet.Server.Status;
namespace MQTTnet.Server.Internal
{
public sealed class MqttClientSession : IDisposable
{
readonly DateTime _createdTimestamp = DateTime.UtcNow;
public MqttClientSession(
string clientId,
IDictionary<object, object> items,
MqttServerEventDispatcher eventDispatcher,
IMqttServerOptions serverOptions,
IMqttRetainedMessagesManager retainedMessagesManager,
bool isPersistent
)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
Items = items ?? throw new ArgumentNullException(nameof(items));
IsPersistent = isPersistent;
SubscriptionsManager = new MqttClientSubscriptionsManager(this, serverOptions, eventDispatcher, retainedMessagesManager);
ApplicationMessagesQueue = new MqttClientSessionApplicationMessagesQueue(serverOptions);
}
public string ClientId { get; }
public bool IsCleanSession { get; set; } = true;
/// <summary>
/// Session should persist if CleanSession was set to false (Mqtt3) or if SessionExpiryInterval != 0 (Mqtt5)
/// </summary>
public bool IsPersistent { get; set; }
public MqttApplicationMessage WillMessage { get; set; }
public MqttClientSubscriptionsManager SubscriptionsManager { get; }
public MqttClientSessionApplicationMessagesQueue ApplicationMessagesQueue { get; }
/// <summary>
/// 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 void FillSessionStatus(MqttSessionStatus status)
{
status.ClientId = ClientId;
status.CreatedTimestamp = _createdTimestamp;
status.PendingApplicationMessagesCount = ApplicationMessagesQueue.Count;
status.Items = Items;
}
public void Dispose()
{
ApplicationMessagesQueue?.Dispose();
}
}
}
@@ -1,65 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.Server.Internal
{
public sealed class MqttClientSessionApplicationMessagesQueue : IDisposable
{
readonly AsyncQueue<MqttQueuedApplicationMessage> _messageQueue = new AsyncQueue<MqttQueuedApplicationMessage>();
readonly IMqttServerOptions _options;
public MqttClientSessionApplicationMessagesQueue(IMqttServerOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
}
public int Count => _messageQueue.Count;
public void Enqueue(MqttQueuedApplicationMessage queuedApplicationMessage)
{
if (queuedApplicationMessage == null) throw new ArgumentNullException(nameof(queuedApplicationMessage));
lock (_messageQueue)
{
if (_messageQueue.Count >= _options.MaxPendingMessagesPerClient)
{
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
{
return;
}
if (_options.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
{
_messageQueue.TryDequeue();
}
}
_messageQueue.Enqueue(queuedApplicationMessage);
}
}
public async Task<MqttQueuedApplicationMessage> Dequeue(CancellationToken cancellationToken)
{
var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false);
if (!dequeueResult.IsSuccess)
{
return null;
}
return dequeueResult.Item;
}
public void Clear()
{
_messageQueue.Clear();
}
public void Dispose()
{
_messageQueue?.Dispose();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,95 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public sealed class MqttClientStatistics
{
// Start with 1 because the CONNACK packet is not counted here.
long _receivedPacketsCount = 1;
// Start with 1 because the CONNECT packet is not counted here.
long _sentPacketsCount = 1;
long _receivedApplicationMessagesCount;
long _sentApplicationMessagesCount;
public MqttClientStatistics()
{
ConnectedTimestamp = DateTime.UtcNow;
LastPacketReceivedTimestamp = ConnectedTimestamp;
LastPacketSentTimestamp = ConnectedTimestamp;
LastNonKeepAlivePacketReceivedTimestamp = ConnectedTimestamp;
}
public DateTime ConnectedTimestamp { get; }
/// <summary>
/// Timestamp of the last package that has been sent to the client ("received" from the client's perspective)
/// </summary>
public DateTime LastPacketReceivedTimestamp { get; private set; }
/// <summary>
/// Timestamp of the last package that has been received from the client ("sent" from the client's perspective)
/// </summary>
public DateTime LastPacketSentTimestamp { get; private set; }
public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; private set; }
public long SentApplicationMessagesCount => Interlocked.Read(ref _sentApplicationMessagesCount);
public long ReceivedApplicationMessagesCount => Interlocked.Read(ref _receivedApplicationMessagesCount);
public long SentPacketsCount => Interlocked.Read(ref _sentPacketsCount);
public long ReceivedPacketsCount => Interlocked.Read(ref _receivedPacketsCount);
public void HandleReceivedPacket(MqttPacket packet)
{
if (packet == null)
{
throw new ArgumentNullException(nameof(packet));
}
// This class is tracking all values from Clients perspective!
LastPacketSentTimestamp = DateTime.UtcNow;
Interlocked.Increment(ref _sentPacketsCount);
if (packet is MqttPublishPacket)
{
Interlocked.Increment(ref _sentApplicationMessagesCount);
}
if (!(packet is MqttPingReqPacket || packet is MqttPingRespPacket))
{
LastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp;
}
}
public void HandleSentPacket(MqttPacket packet)
{
if (packet == null)
{
throw new ArgumentNullException(nameof(packet));
}
// This class is tracking all values from Clients perspective!
LastPacketReceivedTimestamp = DateTime.UtcNow;
Interlocked.Increment(ref _receivedPacketsCount);
if (packet is MqttPublishPacket)
{
Interlocked.Increment(ref _receivedApplicationMessagesCount);
}
}
}
}
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -6,135 +10,86 @@ using System.Threading.Tasks;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
namespace MQTTnet.Server
{
public sealed class MqttClientSubscriptionsManager
public sealed class MqttClientSubscriptionsManager : IDisposable
{
// We use a reader writer lock because the subscriptions are only read most of the time.
// 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, Subscription> _subscriptions = new Dictionary<string, Subscription>(4096);
readonly MqttClientSession _clientSession;
readonly IMqttServerOptions _options;
readonly MqttServerEventDispatcher _eventDispatcher;
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
static readonly List<uint> EmptySubscriptionIdentifiers = new List<uint>();
readonly MqttServerEventContainer _eventContainer;
readonly Dictionary<ulong, HashSet<MqttSubscription>> _noWildcardSubscriptionsByTopicHash = new Dictionary<ulong, HashSet<MqttSubscription>>();
readonly MqttRetainedMessagesManager _retainedMessagesManager;
readonly MqttSession _session;
// Callback to maintain list of subscriber clients
readonly ISubscriptionChangedNotification _subscriptionChangedNotification;
// Subscriptions are stored in various dictionaries and use a "topic hash"; see the MqttSubscription object for a detailed explanation.
// The additional lock is important to coordinate complex update logic with multiple steps, checks and interceptors.
readonly Dictionary<string, MqttSubscription> _subscriptions = new Dictionary<string, MqttSubscription>();
// Use subscription lock to maintain consistency across subscriptions and topic hash dictionaries
readonly SemaphoreSlim _subscriptionsLock = new SemaphoreSlim(1);
readonly Dictionary<ulong, TopicHashMaskSubscriptions> _wildcardSubscriptionsByTopicHash = new Dictionary<ulong, TopicHashMaskSubscriptions>();
public MqttClientSubscriptionsManager(
MqttClientSession clientSession,
IMqttServerOptions serverOptions,
MqttServerEventDispatcher eventDispatcher,
IMqttRetainedMessagesManager retainedMessagesManager)
MqttSession session,
MqttServerEventContainer eventContainer,
MqttRetainedMessagesManager retainedMessagesManager,
ISubscriptionChangedNotification subscriptionChangedNotification)
{
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession));
_options = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_eventDispatcher = eventDispatcher ?? throw new ArgumentNullException(nameof(eventDispatcher));
_session = session ?? throw new ArgumentNullException(nameof(session));
_eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
_subscriptionChangedNotification = subscriptionChangedNotification;
}
public async Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket)
public CheckSubscriptionsResult CheckSubscriptions(string topic, ulong topicHash, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
{
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
var possibleSubscriptions = new List<MqttSubscription>();
var retainedApplicationMessages = await _retainedMessagesManager.GetMessagesAsync().ConfigureAwait(false);
var result = new SubscribeResult();
// 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 InterceptSubscribe(originalTopicFilter).ConfigureAwait(false);
var finalTopicFilter = interceptorContext?.TopicFilter ?? originalTopicFilter;
var acceptSubscription = interceptorContext?.AcceptSubscription ?? true;
var closeConnection = interceptorContext?.CloseConnection ?? false;
if (string.IsNullOrEmpty(finalTopicFilter.Topic) || !acceptSubscription)
{
result.ReturnCodes.Add(MqttSubscribeReturnCode.Failure);
result.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError);
}
else
{
result.ReturnCodes.Add(ConvertToSubscribeReturnCode(finalTopicFilter.QualityOfServiceLevel));
result.ReasonCodes.Add(ConvertToSubscribeReasonCode(finalTopicFilter.QualityOfServiceLevel));
}
if (closeConnection)
{
result.CloseConnection = true;
}
if (!acceptSubscription || string.IsNullOrEmpty(finalTopicFilter.Topic))
{
continue;
}
var subscription = CreateSubscription(finalTopicFilter, subscribePacket);
await _eventDispatcher.SafeNotifyClientSubscribedTopicAsync(_clientSession.ClientId, finalTopicFilter).ConfigureAwait(false);
FilterRetainedApplicationMessages(retainedApplicationMessages, subscription, result);
}
return result;
}
public async Task<List<MqttUnsubscribeReasonCode>> Unsubscribe(MqttUnsubscribePacket unsubscribePacket)
{
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
var reasonCodes = new List<MqttUnsubscribeReasonCode>();
foreach (var topicFilter in unsubscribePacket.TopicFilters)
{
var interceptorContext = await InterceptUnsubscribe(topicFilter).ConfigureAwait(false);
if (interceptorContext != null && !interceptorContext.AcceptUnsubscription)
{
reasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError);
continue;
}
_subscriptionsLock.EnterWriteLock();
try
{
reasonCodes.Add(_subscriptions.Remove(topicFilter)
? MqttUnsubscribeReasonCode.Success
: MqttUnsubscribeReasonCode.NoSubscriptionExisted);
}
finally
{
_subscriptionsLock.ExitWriteLock();
}
}
foreach (var topicFilter in unsubscribePacket.TopicFilters)
{
await _eventDispatcher.SafeNotifyClientUnsubscribedTopicAsync(_clientSession.ClientId, topicFilter).ConfigureAwait(false);
}
return reasonCodes;
}
public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel, string senderClientId)
{
List<Subscription> subscriptions;
_subscriptionsLock.EnterReadLock();
// Check for possible subscriptions. They might have collisions but this is fine.
_subscriptionsLock.Wait();
try
{
subscriptions = _subscriptions.Values.ToList();
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions))
{
possibleSubscriptions.AddRange(noWildcardSubscriptions.ToList());
}
foreach (var wcs in _wildcardSubscriptionsByTopicHash)
{
var wildcardSubscriptions = wcs.Value;
var subscriptionHash = wcs.Key;
var subscriptionHashMask = wildcardSubscriptions.HashMask;
if ((topicHash & subscriptionHashMask) == subscriptionHash)
{
possibleSubscriptions.AddRange(wildcardSubscriptions.Subscriptions.ToList());
}
}
}
finally
{
_subscriptionsLock.ExitReadLock();
_subscriptionsLock.Release();
}
// The pre check has evaluated that nothing is subscribed.
// If there were some possible candidates they get checked below
// again to avoid collisions.
if (possibleSubscriptions.Count == 0)
{
return CheckSubscriptionsResult.NotSubscribed;
}
var senderIsReceiver = string.Equals(senderClientId, _clientSession.ClientId);
var senderIsReceiver = string.Equals(senderClientId, _session.Id);
var maxQoSLevel = -1; // Not subscribed.
var qosLevels = new HashSet<MqttQualityOfServiceLevel>();
var subscriptionIdentifiers = new HashSet<uint>();
HashSet<uint> subscriptionIdentifiers = null;
var retainAsPublished = false;
foreach (var subscription in subscriptions)
foreach (var subscription in possibleSubscriptions)
{
if (subscription.NoLocal && senderIsReceiver)
{
@@ -142,177 +97,437 @@ namespace MQTTnet.Server.Internal
continue;
}
if (MqttTopicFilterComparer.Compare(topic, subscription.Topic) != MqttTopicFilterCompareResult.IsMatch)
{
continue;
}
if (subscription.RetainAsPublished)
{
// This is a MQTTv5 feature!
retainAsPublished = true;
}
if (!MqttTopicFilterComparer.IsMatch(topic, subscription.Topic))
if ((int)subscription.GrantedQualityOfServiceLevel > maxQoSLevel)
{
continue;
maxQoSLevel = (int)subscription.GrantedQualityOfServiceLevel;
}
qosLevels.Add(subscription.QualityOfServiceLevel);
if (subscription.Identifier > 0)
{
if (subscriptionIdentifiers == null)
{
subscriptionIdentifiers = new HashSet<uint>();
}
subscriptionIdentifiers.Add(subscription.Identifier);
}
}
if (qosLevels.Count == 0)
if (maxQoSLevel == -1)
{
return CheckSubscriptionsResult.NotSubscribed;
}
return new CheckSubscriptionsResult
var result = new CheckSubscriptionsResult
{
IsSubscribed = true,
RetainAsPublished = retainAsPublished,
SubscriptionIdentifiers = subscriptionIdentifiers.ToList(),
QualityOfServiceLevel = GetEffectiveQoS(qosLevel, qosLevels)
SubscriptionIdentifiers = subscriptionIdentifiers?.ToList() ?? EmptySubscriptionIdentifiers,
// Start with the same QoS as the publisher.
QualityOfServiceLevel = applicationMessageQoSLevel
};
// Now downgrade if required.
//
// If a subscribing Client has been granted maximum QoS 1 for a particular Topic Filter, then a QoS 0 Application Message matching the filter is delivered
// to the Client at QoS 0. This means that at most one copy of the message is received by the Client. On the other hand, a QoS 2 Message published to
// the same topic is downgraded by the Server to QoS 1 for delivery to the Client, so that Client might receive duplicate copies of the Message.
// Subscribing to a Topic Filter at QoS 2 is equivalent to saying "I would like to receive Messages matching this filter at the QoS with which they were published".
// This means a publisher is responsible for determining the maximum QoS a Message can be delivered at, but a subscriber is able to require that the Server
// downgrades the QoS to one more suitable for its usage.
if (maxQoSLevel < (int)applicationMessageQoSLevel)
{
result.QualityOfServiceLevel = (MqttQualityOfServiceLevel)maxQoSLevel;
}
return result;
}
Subscription CreateSubscription(MqttTopicFilter topicFilter, MqttSubscribePacket subscribePacket)
public void Dispose()
{
var subscription = new Subscription
_subscriptionsLock.Dispose();
}
public async Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
{
if (subscribePacket == null)
{
Topic = topicFilter.Topic,
NoLocal = topicFilter.NoLocal,
RetainHandling = topicFilter.RetainHandling,
RetainAsPublished = topicFilter.RetainAsPublished,
QualityOfServiceLevel = topicFilter.QualityOfServiceLevel,
Identifier = subscribePacket.Properties?.SubscriptionIdentifier ?? 0
throw new ArgumentNullException(nameof(subscribePacket));
}
var retainedApplicationMessages = await _retainedMessagesManager.GetMessages().ConfigureAwait(false);
var result = new SubscribeResult
{
ReasonCodes = new List<MqttSubscribeReasonCode>(subscribePacket.TopicFilters.Count)
};
_subscriptionsLock.EnterWriteLock();
var addedSubscriptions = new List<string>();
// 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 subscriptionEventArgs = await InterceptSubscribe(originalTopicFilter, cancellationToken).ConfigureAwait(false);
var finalTopicFilter = subscriptionEventArgs.TopicFilter;
var processSubscription = subscriptionEventArgs.ProcessSubscription && subscriptionEventArgs.Response.ReasonCode <= MqttSubscribeReasonCode.GrantedQoS2;
result.UserProperties = subscriptionEventArgs.UserProperties;
result.ReasonString = subscriptionEventArgs.ReasonString;
result.ReasonCodes.Add(subscriptionEventArgs.Response.ReasonCode);
if (subscriptionEventArgs.CloseConnection)
{
// When any of the interceptor calls leads to a connection close the connection
// must be closed. So do not revert to false!
result.CloseConnection = true;
}
if (!processSubscription || string.IsNullOrEmpty(finalTopicFilter.Topic))
{
continue;
}
var createSubscriptionResult = CreateSubscription(finalTopicFilter, subscribePacket.SubscriptionIdentifier, subscriptionEventArgs.Response.ReasonCode);
addedSubscriptions.Add(finalTopicFilter.Topic);
if (_eventContainer.ClientSubscribedTopicEvent.HasHandlers)
{
var eventArgs = new ClientSubscribedTopicEventArgs
{
ClientId = _session.Id,
TopicFilter = finalTopicFilter
};
await _eventContainer.ClientSubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
FilterRetainedApplicationMessages(retainedApplicationMessages, createSubscriptionResult, result);
}
_subscriptionChangedNotification?.OnSubscriptionsAdded(_session, addedSubscriptions);
return result;
}
public async Task<MqttUnsubscribeResult> Unsubscribe(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{
if (unsubscribePacket == null)
{
throw new ArgumentNullException(nameof(unsubscribePacket));
}
var result = new MqttUnsubscribeResult();
var removedSubscriptions = new List<string>();
await _subscriptionsLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
subscription.IsNewSubscription = !_subscriptions.ContainsKey(topicFilter.Topic);
_subscriptions[topicFilter.Topic] = subscription;
foreach (var topicFilter in unsubscribePacket.TopicFilters)
{
_subscriptions.TryGetValue(topicFilter, out var existingSubscription);
var interceptorContext = await InterceptUnsubscribe(topicFilter, existingSubscription, cancellationToken).ConfigureAwait(false);
var acceptUnsubscription = interceptorContext.Response.ReasonCode == MqttUnsubscribeReasonCode.Success;
result.ReasonCodes.Add(interceptorContext.Response.ReasonCode);
if (interceptorContext.CloseConnection)
{
// When any of the interceptor calls leads to a connection close the connection
// must be closed. So do not revert to false!
result.CloseConnection = true;
}
if (!acceptUnsubscription)
{
continue;
}
if (interceptorContext.ProcessUnsubscription)
{
_subscriptions.Remove(topicFilter);
// must remove subscription object from topic hash dictionary also
if (existingSubscription.TopicHasWildcard)
{
if (_wildcardSubscriptionsByTopicHash.TryGetValue(existingSubscription.TopicHash, out var subs))
{
subs.Subscriptions.Remove(existingSubscription);
if (subs.Subscriptions.Count == 0)
{
_wildcardSubscriptionsByTopicHash.Remove(existingSubscription.TopicHash);
}
}
}
else
{
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(existingSubscription.TopicHash, out var subs))
{
subs.Remove(existingSubscription);
if (subs.Count == 0)
{
_noWildcardSubscriptionsByTopicHash.Remove(existingSubscription.TopicHash);
}
}
}
removedSubscriptions.Add(topicFilter);
}
}
}
finally
{
_subscriptionsLock.ExitWriteLock();
_subscriptionsLock.Release();
if (_subscriptionChangedNotification != null)
{
_subscriptionChangedNotification.OnSubscriptionsRemoved(_session, removedSubscriptions);
}
}
return subscription;
if (_eventContainer.ClientUnsubscribedTopicEvent.HasHandlers)
{
foreach (var topicFilter in unsubscribePacket.TopicFilters)
{
var eventArgs = new ClientUnsubscribedTopicEventArgs
{
ClientId = _session.Id,
TopicFilter = topicFilter
};
await _eventContainer.ClientUnsubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
return result;
}
static void FilterRetainedApplicationMessages(IList<MqttApplicationMessage> retainedApplicationMessages, Subscription subscription, SubscribeResult subscribeResult)
CreateSubscriptionResult CreateSubscription(MqttTopicFilter topicFilter, uint subscriptionIdentifier, MqttSubscribeReasonCode reasonCode)
{
for (var i = retainedApplicationMessages.Count - 1; i >= 0; i--)
MqttQualityOfServiceLevel grantedQualityOfServiceLevel;
if (reasonCode == MqttSubscribeReasonCode.GrantedQoS0)
{
var retainedApplicationMessage = retainedApplicationMessages[i];
grantedQualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce;
}
else if (reasonCode == MqttSubscribeReasonCode.GrantedQoS1)
{
grantedQualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce;
}
else if (reasonCode == MqttSubscribeReasonCode.GrantedQoS2)
{
grantedQualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce;
}
else
{
throw new InvalidOperationException();
}
var subscription = new MqttSubscription(
topicFilter.Topic,
topicFilter.NoLocal,
topicFilter.RetainHandling,
topicFilter.RetainAsPublished,
grantedQualityOfServiceLevel,
subscriptionIdentifier);
bool isNewSubscription;
// Add to subscriptions and maintain topic hash dictionaries
_subscriptionsLock.Wait();
try
{
MqttSubscription.CalculateTopicHash(topicFilter.Topic, out var topicHash, out var topicHashMask, out var hasWildcard);
if (_subscriptions.TryGetValue(topicFilter.Topic, out var existingSubscription))
{
// must remove object from topic hash dictionary first
if (hasWildcard)
{
if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subs))
{
subs.Subscriptions.Remove(existingSubscription);
// no need to remove empty entry because we'll be adding subscription again below
}
}
else
{
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
{
subscriptions.Remove(existingSubscription);
// no need to remove empty entry because we'll be adding subscription again below
}
}
}
isNewSubscription = existingSubscription == null;
_subscriptions[topicFilter.Topic] = subscription;
// Add or re-add to topic hash dictionary
if (hasWildcard)
{
if (!_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
{
subscriptions = new TopicHashMaskSubscriptions(topicHashMask);
_wildcardSubscriptionsByTopicHash.Add(topicHash, subscriptions);
}
subscriptions.Subscriptions.Add(subscription);
}
else
{
if (!_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
{
subscriptions = new HashSet<MqttSubscription>();
_noWildcardSubscriptionsByTopicHash.Add(topicHash, subscriptions);
}
subscriptions.Add(subscription);
}
}
finally
{
_subscriptionsLock.Release();
}
return new CreateSubscriptionResult
{
IsNewSubscription = isNewSubscription,
Subscription = subscription
};
}
static void FilterRetainedApplicationMessages(
IList<MqttApplicationMessage> retainedApplicationMessages,
CreateSubscriptionResult createSubscriptionResult,
SubscribeResult subscribeResult)
{
for (var index = retainedApplicationMessages.Count - 1; index >= 0; index--)
{
var retainedApplicationMessage = retainedApplicationMessages[index];
if (retainedApplicationMessage == null)
{
continue;
}
if (subscription.RetainHandling == MqttRetainHandling.DoNotSendOnSubscribe)
if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.DoNotSendOnSubscribe)
{
// This is a MQTT V5+ feature.
continue;
}
if (subscription.RetainHandling == MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly && !subscription.IsNewSubscription)
if (createSubscriptionResult.Subscription.RetainHandling == MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly && !createSubscriptionResult.IsNewSubscription)
{
// This is a MQTT V5+ feature.
continue;
}
if (!MqttTopicFilterComparer.IsMatch(retainedApplicationMessage.Topic, subscription.Topic))
if (MqttTopicFilterComparer.Compare(retainedApplicationMessage.Topic, createSubscriptionResult.Subscription.Topic) != MqttTopicFilterCompareResult.IsMatch)
{
continue;
}
var queuedApplicationMessage = new MqttQueuedApplicationMessage
var retainedMessageMatch = new MqttRetainedMessageMatch
{
ApplicationMessage = retainedApplicationMessage,
IsRetainedMessage = true,
SubscriptionQualityOfServiceLevel = subscription.QualityOfServiceLevel
SubscriptionQualityOfServiceLevel = createSubscriptionResult.Subscription.GrantedQualityOfServiceLevel
};
if (subscription.Identifier > 0)
if (subscribeResult.RetainedMessages == null)
{
queuedApplicationMessage.SubscriptionIdentifiers = new List<uint> { subscription.Identifier };
subscribeResult.RetainedMessages = new List<MqttRetainedMessageMatch>();
}
subscribeResult.RetainedApplicationMessages.Add(queuedApplicationMessage);
subscribeResult.RetainedMessages.Add(retainedMessageMatch);
retainedApplicationMessages[i] = null;
// Clear the retained message from the list because the client should receive every message only
// one time even if multiple subscriptions affect them.
retainedApplicationMessages[index] = null;
}
}
static MqttSubscribeReturnCode ConvertToSubscribeReturnCode(MqttQualityOfServiceLevel qualityOfServiceLevel)
async Task<InterceptingSubscriptionEventArgs> InterceptSubscribe(MqttTopicFilter topicFilter, CancellationToken cancellationToken)
{
return (MqttSubscribeReturnCode)(int)qualityOfServiceLevel;
}
static MqttSubscribeReasonCode ConvertToSubscribeReasonCode(MqttQualityOfServiceLevel qualityOfServiceLevel)
{
return (MqttSubscribeReasonCode)(int)qualityOfServiceLevel;
}
async Task<MqttSubscriptionInterceptorContext> InterceptSubscribe(MqttTopicFilter topicFilter)
{
var interceptor = _options.SubscriptionInterceptor;
if (interceptor == null)
var eventArgs = new InterceptingSubscriptionEventArgs
{
return null;
}
var context = new MqttSubscriptionInterceptorContext
{
ClientId = _clientSession.ClientId,
ClientId = _session.Id,
TopicFilter = topicFilter,
SessionItems = _clientSession.Items
SessionItems = _session.Items,
Session = new MqttSessionStatus(_session),
CancellationToken = cancellationToken
};
await interceptor.InterceptSubscriptionAsync(context).ConfigureAwait(false);
return context;
}
async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribe(string topicFilter)
{
var interceptor = _options.UnsubscriptionInterceptor;
if (interceptor == null)
if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
return null;
eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.GrantedQoS0;
}
else if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.GrantedQoS1;
}
else if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.GrantedQoS2;
}
var context = new MqttUnsubscriptionInterceptorContext
if (topicFilter.Topic.StartsWith("$share/"))
{
ClientId = _clientSession.ClientId,
Topic = topicFilter,
SessionItems = _clientSession.Items
};
await interceptor.InterceptUnsubscriptionAsync(context).ConfigureAwait(false);
return context;
}
static MqttQualityOfServiceLevel GetEffectiveQoS(MqttQualityOfServiceLevel qosLevel, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels)
{
MqttQualityOfServiceLevel effectiveQoS;
if (subscribedQoSLevels.Contains(qosLevel))
{
effectiveQoS = qosLevel;
}
else if (subscribedQoSLevels.Count == 1)
{
effectiveQoS = subscribedQoSLevels.First();
eventArgs.Response.ReasonCode = MqttSubscribeReasonCode.SharedSubscriptionsNotSupported;
}
else
{
effectiveQoS = subscribedQoSLevels.Max();
await _eventContainer.InterceptingSubscriptionEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
return effectiveQoS;
return eventArgs;
}
async Task<InterceptingUnsubscriptionEventArgs> InterceptUnsubscribe(string topicFilter, MqttSubscription mqttSubscription, CancellationToken cancellationToken)
{
var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs
{
ClientId = _session.Id,
Topic = topicFilter,
SessionItems = _session.Items,
CancellationToken = cancellationToken
};
if (mqttSubscription == null)
{
clientUnsubscribingTopicEventArgs.Response.ReasonCode = MqttUnsubscribeReasonCode.NoSubscriptionExisted;
}
else
{
clientUnsubscribingTopicEventArgs.Response.ReasonCode = MqttUnsubscribeReasonCode.Success;
}
await _eventContainer.InterceptingUnsubscriptionEvent.InvokeAsync(clientUnsubscribingTopicEventArgs).ConfigureAwait(false);
return clientUnsubscribingTopicEventArgs;
}
public sealed class CreateSubscriptionResult
{
public bool IsNewSubscription { get; set; }
public MqttSubscription Subscription { get; set; }
}
}
}
@@ -1,51 +1,48 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Server.Internal
namespace MQTTnet.Server
{
public sealed class MqttRetainedMessagesManager : IMqttRetainedMessagesManager
public sealed class MqttRetainedMessagesManager
{
readonly AsyncLock _storageAccessLock = new AsyncLock();
readonly Dictionary<string, MqttApplicationMessage> _messages = new Dictionary<string, MqttApplicationMessage>(4096);
readonly AsyncLock _storageAccessLock = new AsyncLock();
MqttNetSourceLogger _logger;
IMqttServerOptions _options;
// TODO: Get rid of the logger here!
public Task Start(IMqttServerOptions options, IMqttNetLogger logger)
readonly MqttServerEventContainer _eventContainer;
readonly MqttNetSourceLogger _logger;
public MqttRetainedMessagesManager(MqttServerEventContainer eventContainer, IMqttNetLogger logger)
{
_eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer));
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.WithSource(nameof(MqttRetainedMessagesManager));
_options = options ?? throw new ArgumentNullException(nameof(options));
return PlatformAbstractionLayer.CompletedTask;
}
public async Task LoadMessagesAsync()
public async Task Start()
{
if (_options.Storage == null)
{
return;
}
try
{
var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync().ConfigureAwait(false);
var eventArgs = new LoadingRetainedMessagesEventArgs();
await _eventContainer.LoadingRetainedMessagesEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
lock (_messages)
{
_messages.Clear();
if (retainedMessages != null)
if (eventArgs.LoadedRetainedMessages != null)
{
foreach (var retainedMessage in retainedMessages)
foreach (var retainedMessage in eventArgs.LoadedRetainedMessages)
{
_messages[retainedMessage.Topic] = retainedMessage;
}
@@ -58,9 +55,12 @@ namespace MQTTnet.Server.Internal
}
}
public async Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage)
public async Task UpdateMessage(string clientId, MqttApplicationMessage applicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
if (applicationMessage == null)
{
throw new ArgumentNullException(nameof(applicationMessage));
}
try
{
@@ -103,12 +103,16 @@ namespace MQTTnet.Server.Internal
if (saveIsRequired)
{
if (_options.Storage != null)
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
var eventArgs = new RetainedMessageChangedEventArgs
{
await _options.Storage.SaveRetainedMessagesAsync(messagesForSave).ConfigureAwait(false);
}
ClientId = clientId,
ChangedRetainedMessage = applicationMessage,
StoredRetainedMessages = messagesForSave
};
await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
}
@@ -118,27 +122,25 @@ namespace MQTTnet.Server.Internal
}
}
public Task<IList<MqttApplicationMessage>> GetMessagesAsync()
public Task<IList<MqttApplicationMessage>> GetMessages()
{
lock (_messages)
{
return Task.FromResult((IList<MqttApplicationMessage>)_messages.Values.ToList());
var result = new List<MqttApplicationMessage>(_messages.Values);
return Task.FromResult((IList<MqttApplicationMessage>)result);
}
}
public async Task ClearMessagesAsync()
public async Task ClearMessages()
{
lock (_messages)
{
_messages.Clear();
}
if (_options.Storage != null)
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{
await _options.Storage.SaveRetainedMessagesAsync(new List<MqttApplicationMessage>()).ConfigureAwait(false);
}
await _eventContainer.RetainedMessagesClearedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
}
}
}
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Internal;
namespace MQTTnet.Server
{
public sealed class MqttServerEventContainer
{
public AsyncEvent<InterceptingSubscriptionEventArgs> InterceptingSubscriptionEvent { get; } = new AsyncEvent<InterceptingSubscriptionEventArgs>();
public AsyncEvent<InterceptingUnsubscriptionEventArgs> InterceptingUnsubscriptionEvent { get; } = new AsyncEvent<InterceptingUnsubscriptionEventArgs>();
public AsyncEvent<InterceptingPublishEventArgs> InterceptingPublishEvent { get; } = new AsyncEvent<InterceptingPublishEventArgs>();
public AsyncEvent<ValidatingConnectionEventArgs> ValidatingConnectionEvent { get; } = new AsyncEvent<ValidatingConnectionEventArgs>();
public AsyncEvent<ClientConnectedEventArgs> ClientConnectedEvent { get; } = new AsyncEvent<ClientConnectedEventArgs>();
public AsyncEvent<ClientDisconnectedEventArgs> ClientDisconnectedEvent { get; } = new AsyncEvent<ClientDisconnectedEventArgs>();
public AsyncEvent<ClientSubscribedTopicEventArgs> ClientSubscribedTopicEvent { get; } = new AsyncEvent<ClientSubscribedTopicEventArgs>();
public AsyncEvent<ClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopicEvent { get; } = new AsyncEvent<ClientUnsubscribedTopicEventArgs>();
public AsyncEvent<EventArgs> PreparingSessionEvent { get; } = new AsyncEvent<EventArgs>();
public AsyncEvent<SessionDeletedEventArgs> SessionDeletedEvent { get; } = new AsyncEvent<SessionDeletedEventArgs>();
public AsyncEvent<ApplicationMessageNotConsumedEventArgs> ApplicationMessageNotConsumedEvent { get; } = new AsyncEvent<ApplicationMessageNotConsumedEventArgs>();
public AsyncEvent<RetainedMessageChangedEventArgs> RetainedMessageChangedEvent { get; } = new AsyncEvent<RetainedMessageChangedEventArgs>();
public AsyncEvent<LoadingRetainedMessagesEventArgs> LoadingRetainedMessagesEvent { get; } = new AsyncEvent<LoadingRetainedMessagesEventArgs>();
public AsyncEvent<EventArgs> RetainedMessagesClearedEvent { get; } = new AsyncEvent<EventArgs>();
public AsyncEvent<InterceptingPacketEventArgs> InterceptingInboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
public AsyncEvent<InterceptingPacketEventArgs> InterceptingOutboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
public AsyncEvent<EventArgs> StartedEvent { get; } = new AsyncEvent<EventArgs>();
public AsyncEvent<EventArgs> StoppedEvent { get; } = new AsyncEvent<EventArgs>();
}
}
@@ -1,141 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Packets;
namespace MQTTnet.Server.Internal
{
public sealed class MqttServerEventDispatcher
{
readonly MqttNetSourceLogger _logger;
public MqttServerEventDispatcher(IMqttNetLogger logger)
{
if (logger is null) throw new ArgumentNullException(nameof(logger));
_logger = logger.WithSource(nameof(MqttServerEventDispatcher));
}
public IMqttServerClientConnectedHandler ClientConnectedHandler { get; set; }
public IMqttServerClientDisconnectedHandler ClientDisconnectedHandler { get; set; }
public IMqttServerClientSubscribedTopicHandler ClientSubscribedTopicHandler { get; set; }
public IMqttServerClientUnsubscribedTopicHandler ClientUnsubscribedTopicHandler { get; set; }
public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; }
public async Task SafeNotifyClientConnectedAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
{
try
{
var handler = ClientConnectedHandler;
if (handler == null)
{
return;
}
await handler.HandleClientConnectedAsync(new MqttServerClientConnectedEventArgs
{
ClientId = connectPacket.ClientId,
UserName = connectPacket.Username,
ProtocolVersion = channelAdapter.PacketFormatterAdapter.ProtocolVersion,
Endpoint = channelAdapter.Endpoint
}).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, "Error while handling custom 'ClientConnected' event.");
}
}
public async Task SafeNotifyClientDisconnectedAsync(string clientId, MqttClientDisconnectType disconnectType, string endpoint)
{
try
{
var handler = ClientDisconnectedHandler;
if (handler == null)
{
return;
}
await handler.HandleClientDisconnectedAsync(new MqttServerClientDisconnectedEventArgs
{
ClientId = clientId,
DisconnectType = disconnectType,
Endpoint = endpoint
}).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, "Error while handling custom 'ClientDisconnected' event.");
}
}
public async Task SafeNotifyClientSubscribedTopicAsync(string clientId, MqttTopicFilter topicFilter)
{
try
{
var handler = ClientSubscribedTopicHandler;
if (handler == null)
{
return;
}
await handler.HandleClientSubscribedTopicAsync(new MqttServerClientSubscribedTopicEventArgs
{
ClientId = clientId,
TopicFilter = topicFilter
}).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, "Error while handling custom 'ClientSubscribedTopic' event.");
}
}
public async Task SafeNotifyClientUnsubscribedTopicAsync(string clientId, string topicFilter)
{
try
{
var handler = ClientUnsubscribedTopicHandler;
if (handler == null)
{
return;
}
await handler.HandleClientUnsubscribedTopicAsync(new MqttServerClientUnsubscribedTopicEventArgs
{
ClientId = clientId,
TopicFilter = topicFilter
}).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, "Error while handling custom 'ClientUnsubscribedTopic' event.");
}
}
public async Task SafeNotifyApplicationMessageReceivedAsync(string senderClientId, MqttApplicationMessage applicationMessage)
{
try
{
var handler = ApplicationMessageReceivedHandler;
if (handler == null)
{
return;
}
await handler.HandleApplicationMessageReceivedAsync(new MqttApplicationMessageReceivedEventArgs(senderClientId, applicationMessage, null, null)).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, "Error while handling custom 'ApplicationMessageReceived' event.");
}
}
}
}
@@ -1,21 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Diagnostics;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
namespace MQTTnet.Server
{
public sealed class MqttServerKeepAliveMonitor
{
readonly IMqttServerOptions _options;
readonly MqttServerOptions _options;
readonly MqttClientSessionsManager _sessionsManager;
readonly MqttNetSourceLogger _logger;
public MqttServerKeepAliveMonitor(IMqttServerOptions options, MqttClientSessionsManager sessionsManager, IMqttNetLogger logger)
public MqttServerKeepAliveMonitor(MqttServerOptions options, MqttClientSessionsManager sessionsManager, IMqttNetLogger logger)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
@@ -41,7 +44,7 @@ namespace MQTTnet.Server.Internal
while (!cancellationToken.IsCancellationRequested)
{
TryMaintainConnections();
TryProcessClients();
PlatformAbstractionLayer.Sleep(_options.KeepAliveMonitorInterval);
}
}
@@ -58,16 +61,16 @@ namespace MQTTnet.Server.Internal
}
}
void TryMaintainConnections()
void TryProcessClients()
{
var now = DateTime.UtcNow;
foreach (var connection in _sessionsManager.GetConnections())
foreach (var client in _sessionsManager.GetClients())
{
TryMaintainConnection(connection, now);
TryProcessClient(client, now);
}
}
void TryMaintainConnection(MqttClientConnection connection, DateTime now)
void TryProcessClient(MqttClient connection, DateTime now)
{
try
{
@@ -83,7 +86,7 @@ namespace MQTTnet.Server.Internal
return;
}
if (connection.IsReadingPacket)
if (connection.ChannelAdapter.IsReadingPacket)
{
// The connection is currently reading a (large) packet. So it is obviously
// doing something and thus "connected".
@@ -102,18 +105,18 @@ namespace MQTTnet.Server.Internal
return;
}
_logger.Warning("Client '{0}': Did not receive any packet or keep alive signal.", connection.ClientId);
_logger.Warning("Client '{0}': Did not receive any packet or keep alive signal.", connection.Id);
// Execute the disconnection in background so that the keep alive monitor can continue
// with checking other connections.
// We do not need to wait for the task so no await is needed.
// Also the internal state of the connection must be swapped to "Finalizing" because the
// next iteration of the keep alive timer happens.
var _ = connection.StopAsync(MqttClientDisconnectReason.KeepAliveTimeout);
var _ = connection.StopAsync(MqttDisconnectReasonCode.KeepAliveTimeout);
}
catch (Exception exception)
{
_logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts.", connection.ClientId);
_logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts.", connection.Id);
}
}
}
@@ -0,0 +1,161 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class MqttSession : IDisposable
{
readonly MqttClientSessionsManager _clientSessionsManager;
readonly MqttPacketBus _packetBus = new MqttPacketBus();
readonly MqttServerOptions _serverOptions;
readonly Dictionary<ushort, MqttPublishPacket> _unacknowledgedPublishPackets = new Dictionary<ushort, MqttPublishPacket>();
// Bookkeeping to know if this is a subscribing client; lazy intialize later.
HashSet<string> _subscribedTopics;
public MqttSession(
string clientId,
bool isPersistent,
IDictionary items,
MqttServerOptions serverOptions,
MqttServerEventContainer eventContainer,
MqttRetainedMessagesManager retainedMessagesManager,
MqttClientSessionsManager clientSessionsManager)
{
Id = clientId ?? throw new ArgumentNullException(nameof(clientId));
IsPersistent = isPersistent;
Items = items ?? throw new ArgumentNullException(nameof(items));
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager));
SubscriptionsManager = new MqttClientSubscriptionsManager(this, eventContainer, retainedMessagesManager, clientSessionsManager);
}
public DateTime CreatedTimestamp { get; } = DateTime.UtcNow;
public bool HasSubscribedTopics => _subscribedTopics != null && _subscribedTopics.Count > 0;
public string Id { get; }
/// <summary>
/// Session should persist if CleanSession was set to false (Mqtt3) or if SessionExpiryInterval != 0 (Mqtt5)
/// </summary>
public bool IsPersistent { get; set; }
public IDictionary Items { get; }
public MqttConnectPacket LatestConnectPacket { get; set; }
public MqttPacketIdentifierProvider PacketIdentifierProvider { get; } = new MqttPacketIdentifierProvider();
public long PendingDataPacketsCount => _packetBus.PartitionItemsCount(MqttPacketBusPartition.Data);
public MqttClientSubscriptionsManager SubscriptionsManager { get; }
public bool WillMessageSent { get; set; }
public void AcknowledgePublishPacket(ushort packetIdentifier)
{
_unacknowledgedPublishPackets.Remove(packetIdentifier);
}
public void AddSubscribedTopic(string topic)
{
if (_subscribedTopics == null)
{
_subscribedTopics = new HashSet<string>();
}
_subscribedTopics.Add(topic);
}
public Task DeleteAsync()
{
return _clientSessionsManager.DeleteSessionAsync(Id);
}
public Task<MqttPacketBusItem> DequeuePacketAsync(CancellationToken cancellationToken)
{
return _packetBus.DequeueItemAsync(cancellationToken);
}
public void Dispose()
{
_packetBus?.Dispose();
SubscriptionsManager.Dispose();
}
public void EnqueuePacket(MqttPacketBusItem packetBusItem)
{
if (packetBusItem == null)
{
throw new ArgumentNullException(nameof(packetBusItem));
}
if (_packetBus.ItemsCount >= _serverOptions.MaxPendingMessagesPerClient)
{
if (_serverOptions.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
{
return;
}
if (_serverOptions.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage)
{
// Only drop from the data partition. Dropping from control partition might break the connection
// because the client does not receive PINGREQ packets etc. any longer.
_packetBus.DropFirstItem(MqttPacketBusPartition.Data);
}
}
if (packetBusItem.Packet is MqttPublishPacket publishPacket)
{
if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
_unacknowledgedPublishPackets[publishPacket.PacketIdentifier] = publishPacket;
}
_packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Data);
}
else if (packetBusItem.Packet is MqttPingReqPacket || packetBusItem.Packet is MqttPingRespPacket)
{
_packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Health);
}
else
{
_packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Control);
}
}
public void Recover()
{
// TODO: Keep the bus and only insert pending items again.
// TODO: Check if packet identifier must be restarted or not.
_packetBus.Clear();
foreach (var publishPacket in _unacknowledgedPublishPackets.Values.ToList())
{
EnqueuePacket(new MqttPacketBusItem(publishPacket));
}
}
public void RemoveSubscribedTopic(string topic)
{
_subscribedTopics?.Remove(topic);
}
}
}
@@ -0,0 +1,309 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
/*
* The MqttSubscription object stores subscription parameters and calculates
* topic hashes.
*
* Use of Topic Hashes to improve message processing performance
* =============================================================
*
* Motivation
* -----------
* In a typical use case for MQTT there may be many publishers (sensors or
* other devices in the field) and few subscribers (monitoring all or many topics).
* Each publisher may have one or more topic(s) to publish and therefore both, the
* number of publishers and the number of topics may be large.
*
* Maintaining subscribers in a separate container
* -----------------------------------------------
* Instead of placing all sessions into a single _sessions container, subscribers
* are added into another _subscriberSessions container (if a client is a
* subscriber and a publisher then the client is present in both containers). The
* cost is some additional bookkeeping work upon subscription where each client
* session needs to maintain a list of subscribed topics.
*
* When the client subscribes to the first topic, then the session manager adds
* the client to the _subscriberSessions container, and when the client
* unsubscribes from the last topic then the session manager removes the client
* from the container. Now, when an application message arrives, only the list of
* subscribers need processing instead of looping through potentially thousands of
* publishers.
*
* Improving subscriber topic lookup
* ---------------------------------
* For each subscriber, it needs to be determined whether an application message
* matches any topic the subscriber has subscribed to. There may only be few
* subscribers but there may be many subscribed topics, including wildcard topics.
*
* The implemented approach uses a topic hash and a hash mask calculated on the
* subscribed topic and the published topic (the application message topic) to
* find candidates for a match, with the existing match logic evaluating a reduced
* number of candidates.
*
* For each subscription, the topic hash and a hash mask is stored with the
* subscription, and for each application message received, the hash is calculated
* for the published topic before attempting to find matching subscriptions. The
* hash calculation itself is simple and does not have a large performance impact.
*
* We'll first explain how topic hashes and hash masks are constructed and then how
* they are used.
*
* Topic hash
* ----------
* Topic hashes are stored as 64-bit numbers. Each byte within the 64-bit number
* relates to one MQTT topic level. A checksum is calculated for each topic level
* by iterating over the characters within the topic level (cast to byte) and the
* result is stored into the corresponding byte of the 64-bit number. If a topic
* level contains a wildcard character, then 0x00 is stored instead of the
* checksum.
*
* If there are less than 8 levels then the rest of the 64-bit number is filled
* with 0xff. If there are more than 8 levels then topics where the first 8 MQTT
* topic levels are identical will have the same hash value.
*
* This is the topic hash for the MQTT topic below: 0x655D4AF1FFFFFF
*
* client1/building1/level1/sensor1 (empty) (empty) (empty) (empty)
* \_____/ \_______/ \____/ \_____/ \_____/ \_____/ \_____/ \_____/
* | | | | | | | |
* 0x65 0x5D 0x4A 0xF1 0xFF 0xFF 0xFF 0xFF
*
* This is the topic hash for an MQTT topic containing a wildcard: 0x655D00F1FFFFFF
*
* client1/building1/ + /sensor1 (empty) (empty) (empty) (empty)
* \_____/ \_______/ \_/ \_____/ \_____/ \_____/ \_____/ \_____/
* | | | | | | | |
* 0x65 0x5D 0 0xF1 0xFF 0xFF 0xFF 0xFF
*
* For topics that contain the multi level wildcard # at the end, the topic hash
* is filled with 0x00: 0x65004A00000000
*
* client1/ + /level1/ # (empty) (empty) (empty) (empty)
* \_____/ \_/ \____/ \_/ \_____/ \_____/ \_____/ \_____/
* | | | | | | | |
* 0x65 0 0x4A 0 0 0 0 0
*
*
* Topic hash mask
* ---------------
* The hash mask simply contains 0xFF for non-wildcard topic levels and 0x00 for
* wildcard topic levels. Here are the topic hash masks for the examples above.
*
* client1/building1/level1/sensor1 (empty) (empty) (empty) (empty)
* \_____/ \_______/ \____/ \_____/ \_____/ \_____/ \_____/ \_____/
* | | | | | | | |
* 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
*
* client1/building1/ + /sensor1 (empty) (empty) (empty) (empty)
* \_____/ \_______/ \_/ \_____/ \_____/ \_____/ \_____/ \_____/
* | | | | | | | |
* 0xFF 0xFF 0 0xFF 0xFF 0xFF 0xFF 0xFF
*
* client1/ + /level1/ # (empty) (empty) (empty) (empty)
* \_____/ \_/ \____/ \_/ \_____/ \_____/ \_____/ \_____/
* | | | | | | | |
* 0xFF 0 0xFF 0 0 0 0 0
*
*
* Topic hash and hash mask properties
* -----------------------------------
* The following properties of topic hashes and hash masks can be exploited to
* find potentially matching subscribed topics for a given published topic.
*
* (1) If a subscribed topic does not contain wildcards then the topic hash of the
* subscribed topic must be equal to the topic hash of the published topic,
* otherwise the subscribed topic cannot be a candidate for a match.
*
* (2) If a subscribed topic contains wildcards then the hash of the published
* topic masked with the subscribed topic's hash mask must be equal to the hash of
* the subscribed topic. I.e. a subscribed topic is a candidate for a match if:
* (publishedTopicHash & subscribedTopicHashMask) == subscribedTopicHash
*
* (3) If a subscribed topic contains wildcards then any potentially matching
* published topic must have a hash value that is greater than or equal to the
* hash value of the subscribed topic (because the subscribed topic contains
* zeroes in wildcard positions).
*
* Match finding
* -------------
* The subscription manager maintains two separate dictionaries to assist finding
* matches using topic hashes: a _noWildcardSubscriptionsByTopicHash dictionary
* containing all subscriptions that do not have wildcards, and a
* _wildcardSubscriptionsByTopicHash dictionary containing subscriptions with
* wildcards.
*
* For subscriptions without wildcards, all potential candidates for a match are
* obtained by a single look-up (exploiting point 1 above).
*
* For subscriptions with wildcards, the subscription manager loops through the
* wildcard subscriptions and selects candidates that satisfy condition
* (publishedTopicHash & subscribedTopicMask) == subscribedTopicHash (point 2).
* The loop could exit early if wildcard subscriptions were stored into a sorted
* dictionary (utilizing point 3), but, after testing, there does not seem to be
* any real benefit doing so.
*
* Other considerations
* --------------------
* Characters in the topic string are cast to byte and any additional bytes in a
* multi-byte character are disregarded. Best guess is that this does not impact
* performance in practice.
*
* Instead of one-byte checksums per topic level, one-word checksums per topic
* level could be used. If most topics contained four levels or less then hash
* buckets would be shallower.
*
* For very large numbers of topics, performing a parallel search may help further.
*
* To also handle a larger number of subscribers, it may be beneficial to maintain
* a subscribers-by-subscription-topic dictionary.
*/
public sealed class MqttSubscription
{
public MqttSubscription(
string topic,
bool noLocal,
MqttRetainHandling retainHandling,
bool retainAsPublished,
MqttQualityOfServiceLevel qualityOfServiceLevel,
uint identifier)
{
Topic = topic;
NoLocal = noLocal;
RetainHandling = retainHandling;
RetainAsPublished = retainAsPublished;
GrantedQualityOfServiceLevel = qualityOfServiceLevel;
Identifier = identifier;
CalculateTopicHash(Topic, out var hash, out var hashMask, out var hasWildcard);
TopicHash = hash;
TopicHashMask = hashMask;
TopicHasWildcard = hasWildcard;
}
public MqttQualityOfServiceLevel GrantedQualityOfServiceLevel { get; }
public uint Identifier { get; }
public bool NoLocal { get; }
public bool RetainAsPublished { get; }
public MqttRetainHandling RetainHandling { get; }
public string Topic { get; }
public ulong TopicHash { get; }
public ulong TopicHashMask { get; }
public bool TopicHasWildcard { get; }
public static void CalculateTopicHash(string topic, out ulong resultHash, out ulong resultHashMask, out bool resultHasWildcard)
{
// calculate topic hash
ulong hash = 0;
ulong hashMaskInverted = 0;
ulong levelBitMask = 0;
ulong fillLevelBitMask = 0;
var hasWildcard = false;
byte checkSum = 0;
var level = 0;
var i = 0;
while (i < topic.Length)
{
var c = topic[i];
if (c == MqttTopicFilterComparer.LevelSeparator)
{
// done with this level
hash <<= 8;
hash |= checkSum;
hashMaskInverted <<= 8;
hashMaskInverted |= levelBitMask;
checkSum = 0;
levelBitMask = 0;
++level;
if (level >= 8)
{
break;
}
}
else if (c == MqttTopicFilterComparer.SingleLevelWildcard)
{
levelBitMask = 0xff;
hasWildcard = true;
}
else if (c == MqttTopicFilterComparer.MultiLevelWildcard)
{
// checksum is zero for a valid topic
levelBitMask = 0xff;
// fill rest with this fillLevelBitMask
fillLevelBitMask = 0xff;
hasWildcard = true;
break;
}
else
{
// The checksum should be designed to reduce the hash bucket depth for the expected
// fairly regularly named MQTT topics that don't differ much,
// i.e. "room1/sensor1"
// "room1/sensor2"
// "room1/sensor3"
// etc.
if ((c & 1) == 0)
{
checkSum += (byte)c;
}
else
{
checkSum ^= (byte)(c >> 1);
}
}
++i;
}
// Shift hash left and leave zeroes to fill ulong
if (level < 8)
{
hash <<= 8;
hash |= checkSum;
hashMaskInverted <<= 8;
hashMaskInverted |= levelBitMask;
++level;
while (level < 8)
{
hash <<= 8;
hashMaskInverted <<= 8;
hashMaskInverted |= fillLevelBitMask;
++level;
}
}
if (!hasWildcard)
{
while (i < topic.Length)
{
var c = topic[i];
if (c == MqttTopicFilterComparer.SingleLevelWildcard || c == MqttTopicFilterComparer.MultiLevelWildcard)
{
hasWildcard = true;
break;
}
++i;
}
}
resultHash = hash;
resultHashMask = ~hashMaskInverted;
resultHasWildcard = hasWildcard;
}
}
}
@@ -1,128 +0,0 @@
using System;
namespace MQTTnet.Server.Internal
{
public static class MqttTopicFilterComparer
{
const char LevelSeparator = '/';
const char MultiLevelWildcard = '#';
const char SingleLevelWildcard = '+';
public static bool IsMatch(string topic, string filter)
{
if (topic == null) throw new ArgumentNullException(nameof(topic));
if (filter == null) throw new ArgumentNullException(nameof(filter));
var sPos = 0;
var sLen = filter.Length;
var tPos = 0;
var tLen = topic.Length;
while (sPos < sLen && tPos < tLen)
{
if (filter[sPos] == topic[tPos])
{
if (tPos == tLen - 1)
{
// Check for e.g. foo matching foo/#
if (sPos == sLen - 3
&& filter[sPos + 1] == LevelSeparator
&& filter[sPos + 2] == MultiLevelWildcard)
{
return true;
}
// Check for e.g. foo/ matching foo/#
if (sPos == sLen - 2
&& filter[sPos] == LevelSeparator
&& filter[sPos + 1] == MultiLevelWildcard)
{
return true;
}
}
sPos++;
tPos++;
if (sPos == sLen && tPos == tLen)
{
return true;
}
if (tPos == tLen && sPos == sLen - 1 && filter[sPos] == SingleLevelWildcard)
{
if (sPos > 0 && filter[sPos - 1] != LevelSeparator)
{
// Invalid filter string
return false;
}
return true;
}
}
else
{
if (filter[sPos] == SingleLevelWildcard)
{
// Check for bad "+foo" or "a/+foo" subscription
if (sPos > 0 && filter[sPos - 1] != LevelSeparator)
{
// Invalid filter string
return false;
}
// Check for bad "foo+" or "foo+/a" subscription
if (sPos < sLen - 1 && filter[sPos + 1] != LevelSeparator)
{
// Invalid filter string
return false;
}
sPos++;
while (tPos < tLen && topic[tPos] != LevelSeparator)
{
tPos++;
}
if (tPos == tLen && sPos == sLen)
{
return true;
}
}
else if (filter[sPos] == MultiLevelWildcard)
{
if (sPos > 0 && filter[sPos - 1] != LevelSeparator)
{
// Invalid filter string
return false;
}
if (sPos + 1 != sLen)
{
// Invalid filter string
return false;
}
return true;
}
else
{
// Check for e.g. foo/bar matching foo/+/#
if (sPos > 0
&& sPos + 2 == sLen
&& tPos == tLen
&& filter[sPos - 1] == SingleLevelWildcard
&& filter[sPos] == LevelSeparator
&& filter[sPos + 1] == MultiLevelWildcard)
{
return true;
}
return false;
}
}
}
return false;
}
}
}
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class MqttUnsubscribeResult
{
public List<MqttUnsubscribeReasonCode> ReasonCodes { get; } = new List<MqttUnsubscribeReasonCode>(128);
public bool CloseConnection { get; set; }
}
}
@@ -1,16 +1,23 @@
using System.Collections.Generic;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
namespace MQTTnet.Server
{
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; }
public List<MqttSubscribeReasonCode> ReasonCodes { get; set; }
public string ReasonString { get; set; }
public List<MqttRetainedMessageMatch> RetainedMessages { get; set; }
public List<MqttUserProperty> UserProperties { get; set; }
}
}
}
@@ -1,21 +0,0 @@
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; }
}
}
@@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace MQTTnet.Server
{
/// <summary>
/// Helper class that stores the topic hash mask common to all contained Subscriptions for direct access.
/// </summary>
public sealed class TopicHashMaskSubscriptions
{
public TopicHashMaskSubscriptions(ulong hashMask)
{
HashMask = hashMask;
}
public ulong HashMask { get; }
public HashSet<MqttSubscription> Subscriptions { get; } = new HashSet<MqttSubscription>();
}
}
@@ -1,24 +0,0 @@
using System.Collections.Generic;
namespace MQTTnet.Server
{
public sealed class MqttApplicationMessageInterceptorContext
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public MqttApplicationMessage ApplicationMessage { get; set; }
/// <summary>
/// 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> SessionItems { get; internal set; }
public bool AcceptPublish { get; set; } = true;
public bool CloseConnection { get; set; }
}
}
@@ -1,4 +1,8 @@
namespace MQTTnet.Server
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Server
{
public enum MqttClientDisconnectType
{
@@ -1,25 +0,0 @@
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class MqttClientMessageQueueInterceptorContext
{
public string SenderClientId { get; internal set; }
public string ReceiverClientId { get; internal set; }
public MqttApplicationMessage ApplicationMessage { get; set; }
public bool AcceptEnqueue { get; set; } = true;
/// <summary>
/// Gets or sets the supscription 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.
/// There are 3 QoS levels in MQTT:
/// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At least once (1): Message gets delivered at least once (one time or more often).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary>
public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; }
}
}
@@ -1,32 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public sealed class MqttClientMessageQueueInterceptorDelegate : IMqttServerClientMessageQueueInterceptor
{
readonly Func<MqttClientMessageQueueInterceptorContext, Task> _callback;
public MqttClientMessageQueueInterceptorDelegate(Action<MqttClientMessageQueueInterceptorContext> callback)
{
if (callback == null) throw new ArgumentNullException(nameof(callback));
_callback = context =>
{
callback(context);
return PlatformAbstractionLayer.CompletedTask;
};
}
public MqttClientMessageQueueInterceptorDelegate(Func<MqttClientMessageQueueInterceptorContext, Task> callback)
{
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
}
public Task InterceptClientMessageQueueEnqueueAsync(MqttClientMessageQueueInterceptorContext context)
{
return _callback(context);
}
}
}
@@ -1,181 +0,0 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class MqttConnectionValidatorContext
{
readonly MqttConnectPacket _connectPacket;
readonly IMqttChannelAdapter _clientAdapter;
public MqttConnectionValidatorContext(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
{
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
_clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId => _connectPacket.ClientId;
public string Endpoint => _clientAdapter.Endpoint;
public bool IsSecureConnection => _clientAdapter.IsSecureConnection;
public X509Certificate2 ClientCertificate => _clientAdapter.ClientCertificate;
public MqttProtocolVersion ProtocolVersion => _clientAdapter.PacketFormatterAdapter.ProtocolVersion;
public string Username => _connectPacket.Username;
public byte[] RawPassword => _connectPacket.Password;
public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
/// <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 MqttApplicationMessage WillMessage => _connectPacket.WillMessage;
/// <summary>
/// Gets or sets a value indicating whether clean sessions are used or not.
/// When a client connects to a broker it can connect using either a non persistent connection (clean session) or a persistent connection.
/// With a non persistent connection the broker doesn't store any subscription information or undelivered messages for the client.
/// This mode is ideal when the client only publishes messages.
/// 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;
/// <summary>
/// Gets or sets the keep alive period.
/// The connection is normally left open by the client so that is can send and receive data at any time.
/// If no data flows over an open connection for a certain time period then the client will generate a PINGREQ and expect to receive a PINGRESP from the broker.
/// 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;
/// <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 => _connectPacket.Properties?.UserProperties;
/// <summary>
/// Gets or sets the authentication data.
/// Hint: MQTT 5 feature only.
/// </summary>
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 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;
/// <summary>
/// Gets or sets the topic alias maximum.
/// This gives the maximum length of the topic alias.
/// </summary>
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;
/// <summary>
/// Gets the request response information.
/// Hint: MQTT 5 feature only.
/// </summary>
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;
/// <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;
/// <summary>
/// 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> SessionItems { get; internal set; }
/// <summary>
/// This is used for MQTTv3 only.
/// </summary>
[Obsolete("Use ReasonCode instead. It is MQTTv5 only but will be converted to a valid ReturnCode.")]
public MqttConnectReturnCode ReturnCode
{
get => new MqttConnectReasonCodeConverter().ToConnectReturnCode(ReasonCode);
set => ReasonCode = new MqttConnectReasonCodeConverter().ToConnectReasonCode(value);
}
/// <summary>
/// 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.
/// MQTTv5 only.
/// </summary>
public MqttConnectReasonCode ReasonCode { get; set; } = MqttConnectReasonCode.Success;
/// <summary>
/// Gets or sets the response 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> ResponseUserProperties { get; set; }
/// <summary>
/// Gets or sets the response authentication data.
/// 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,17 +0,0 @@
using MQTTnet.Server.Internal;
namespace MQTTnet.Server
{
public sealed class MqttPendingApplicationMessage
{
public MqttPendingApplicationMessage(MqttApplicationMessage applicationMessage, MqttClientConnection sender)
{
Sender = sender;
ApplicationMessage = applicationMessage;
}
public MqttClientConnection Sender { get; }
public MqttApplicationMessage ApplicationMessage { get; }
}
}
@@ -1,8 +0,0 @@
namespace MQTTnet.Server
{
public enum MqttPendingMessagesOverflowStrategy
{
DropOldestQueuedMessage,
DropNewMessage
}
}
@@ -1,28 +0,0 @@
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class MqttQueuedApplicationMessage
{
public MqttApplicationMessage ApplicationMessage { get; set; }
public string SenderClientId { get; set; }
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.
/// There are 3 QoS levels in MQTT:
/// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At least once (1): Message gets delivered at least once (one time or more often).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary>
public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; }
}
}
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class MqttRetainedMessageMatch
{
public MqttApplicationMessage ApplicationMessage { get; set; }
public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; }
}
}
+246 -146
View File
@@ -1,192 +1,243 @@
using MQTTnet.Adapter;
using MQTTnet.Client.Publishing;
using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Protocol;
using MQTTnet.Server.Status;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Server.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public class MqttServer : Disposable, IMqttServer
public class MqttServer : Disposable
{
readonly MqttServerEventDispatcher _eventDispatcher;
readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer();
readonly ICollection<IMqttServerAdapter> _adapters;
readonly IMqttNetLogger _rootLogger;
readonly MqttNetSourceLogger _logger;
MqttClientSessionsManager _clientSessionsManager;
IMqttRetainedMessagesManager _retainedMessagesManager;
MqttServerKeepAliveMonitor _keepAliveMonitor;
readonly MqttServerOptions _options;
readonly IMqttNetLogger _rootLogger;
readonly MqttRetainedMessagesManager _retainedMessagesManager;
readonly MqttServerKeepAliveMonitor _keepAliveMonitor;
readonly MqttClientSessionsManager _clientSessionsManager;
CancellationTokenSource _cancellationTokenSource;
public MqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
public MqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
{
if (adapters == null) throw new ArgumentNullException(nameof(adapters));
_options = options ?? throw new ArgumentNullException(nameof(options));
if (adapters == null)
{
throw new ArgumentNullException(nameof(adapters));
}
_adapters = adapters.ToList();
if (logger == null) throw new ArgumentNullException(nameof(logger));
_rootLogger = logger ?? throw new ArgumentNullException(nameof(logger));
_logger = logger.WithSource(nameof(MqttServer));
_rootLogger = logger;
_eventDispatcher = new MqttServerEventDispatcher(logger);
_retainedMessagesManager = new MqttRetainedMessagesManager(_eventContainer, _rootLogger);
_clientSessionsManager = new MqttClientSessionsManager(options, _retainedMessagesManager, _eventContainer, _rootLogger);
_keepAliveMonitor = new MqttServerKeepAliveMonitor(options, _clientSessionsManager, _rootLogger);
}
public event Func<ApplicationMessageNotConsumedEventArgs, Task> ApplicationMessageNotConsumedAsync
{
add => _eventContainer.ApplicationMessageNotConsumedEvent.AddHandler(value);
remove => _eventContainer.ApplicationMessageNotConsumedEvent.RemoveHandler(value);
}
public event Func<ClientConnectedEventArgs, Task> ClientConnectedAsync
{
add => _eventContainer.ClientConnectedEvent.AddHandler(value);
remove => _eventContainer.ClientConnectedEvent.RemoveHandler(value);
}
public event Func<ClientDisconnectedEventArgs, Task> ClientDisconnectedAsync
{
add => _eventContainer.ClientDisconnectedEvent.AddHandler(value);
remove => _eventContainer.ClientDisconnectedEvent.RemoveHandler(value);
}
public event Func<ClientSubscribedTopicEventArgs, Task> ClientSubscribedTopicAsync
{
add => _eventContainer.ClientSubscribedTopicEvent.AddHandler(value);
remove => _eventContainer.ClientSubscribedTopicEvent.RemoveHandler(value);
}
public event Func<ClientUnsubscribedTopicEventArgs, Task> ClientUnsubscribedTopicAsync
{
add => _eventContainer.ClientUnsubscribedTopicEvent.AddHandler(value);
remove => _eventContainer.ClientUnsubscribedTopicEvent.RemoveHandler(value);
}
public event Func<InterceptingPacketEventArgs, Task> InterceptingInboundPacketAsync
{
add => _eventContainer.InterceptingInboundPacketEvent.AddHandler(value);
remove => _eventContainer.InterceptingInboundPacketEvent.RemoveHandler(value);
}
public event Func<InterceptingPacketEventArgs, Task> InterceptingOutboundPacketAsync
{
add => _eventContainer.InterceptingOutboundPacketEvent.AddHandler(value);
remove => _eventContainer.InterceptingOutboundPacketEvent.RemoveHandler(value);
}
public event Func<InterceptingPublishEventArgs, Task> InterceptingPublishAsync
{
add => _eventContainer.InterceptingPublishEvent.AddHandler(value);
remove => _eventContainer.InterceptingPublishEvent.RemoveHandler(value);
}
public event Func<InterceptingSubscriptionEventArgs, Task> InterceptingSubscriptionAsync
{
add => _eventContainer.InterceptingSubscriptionEvent.AddHandler(value);
remove => _eventContainer.InterceptingSubscriptionEvent.RemoveHandler(value);
}
public event Func<InterceptingUnsubscriptionEventArgs, Task> InterceptingUnsubscriptionAsync
{
add => _eventContainer.InterceptingUnsubscriptionEvent.AddHandler(value);
remove => _eventContainer.InterceptingUnsubscriptionEvent.RemoveHandler(value);
}
public event Func<LoadingRetainedMessagesEventArgs, Task> LoadingRetainedMessageAsync
{
add => _eventContainer.LoadingRetainedMessagesEvent.AddHandler(value);
remove => _eventContainer.LoadingRetainedMessagesEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> PreparingSessionAsync
{
add => _eventContainer.PreparingSessionEvent.AddHandler(value);
remove => _eventContainer.PreparingSessionEvent.RemoveHandler(value);
}
public event Func<RetainedMessageChangedEventArgs, Task> RetainedMessageChangedAsync
{
add => _eventContainer.RetainedMessageChangedEvent.AddHandler(value);
remove => _eventContainer.RetainedMessageChangedEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> RetainedMessagesClearedAsync
{
add => _eventContainer.RetainedMessagesClearedEvent.AddHandler(value);
remove => _eventContainer.RetainedMessagesClearedEvent.RemoveHandler(value);
}
public event Func<SessionDeletedEventArgs, Task> SessionDeletedAsync
{
add => _eventContainer.SessionDeletedEvent.AddHandler(value);
remove => _eventContainer.SessionDeletedEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> StartedAsync
{
add => _eventContainer.StartedEvent.AddHandler(value);
remove => _eventContainer.StartedEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> StoppedAsync
{
add => _eventContainer.StoppedEvent.AddHandler(value);
remove => _eventContainer.StoppedEvent.RemoveHandler(value);
}
public event Func<ValidatingConnectionEventArgs, Task> ValidatingConnectionAsync
{
add => _eventContainer.ValidatingConnectionEvent.AddHandler(value);
remove => _eventContainer.ValidatingConnectionEvent.RemoveHandler(value);
}
public bool IsStarted => _cancellationTokenSource != null;
public IMqttServerStartedHandler StartedHandler { get; set; }
public IMqttServerStoppedHandler StoppedHandler { get; set; }
public IMqttServerClientConnectedHandler ClientConnectedHandler
public Task DeleteRetainedMessagesAsync()
{
get => _eventDispatcher.ClientConnectedHandler;
set => _eventDispatcher.ClientConnectedHandler = value;
ThrowIfNotStarted();
return _retainedMessagesManager?.ClearMessages() ?? PlatformAbstractionLayer.CompletedTask;
}
public IMqttServerClientDisconnectedHandler ClientDisconnectedHandler
public Task DisconnectClientAsync(string id, MqttDisconnectReasonCode reasonCode)
{
get => _eventDispatcher.ClientDisconnectedHandler;
set => _eventDispatcher.ClientDisconnectedHandler = value;
if (id == null)
{
throw new ArgumentNullException(nameof(id));
}
ThrowIfNotStarted();
return _clientSessionsManager.GetClient(id).StopAsync(reasonCode);
}
public IMqttServerClientSubscribedTopicHandler ClientSubscribedTopicHandler
public Task<IList<MqttClientStatus>> GetClientsAsync()
{
get => _eventDispatcher.ClientSubscribedTopicHandler;
set => _eventDispatcher.ClientSubscribedTopicHandler = value;
}
public IMqttServerClientUnsubscribedTopicHandler ClientUnsubscribedTopicHandler
{
get => _eventDispatcher.ClientUnsubscribedTopicHandler;
set => _eventDispatcher.ClientUnsubscribedTopicHandler = value;
}
public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler
{
get => _eventDispatcher.ApplicationMessageReceivedHandler;
set => _eventDispatcher.ApplicationMessageReceivedHandler = value;
}
public IMqttServerOptions Options { get; private set; }
public Task<IList<IMqttClientStatus>> GetClientStatusAsync()
{
ThrowIfDisposed();
ThrowIfNotStarted();
return _clientSessionsManager.GetClientStatusAsync();
}
public Task<IList<IMqttSessionStatus>> GetSessionStatusAsync()
public Task<IList<MqttApplicationMessage>> GetRetainedMessagesAsync()
{
ThrowIfNotStarted();
return _retainedMessagesManager.GetMessages();
}
public Task<IList<MqttSessionStatus>> GetSessionsAsync()
{
ThrowIfDisposed();
ThrowIfNotStarted();
return _clientSessionsManager.GetSessionStatusAsync();
}
public Task<IList<MqttApplicationMessage>> GetRetainedApplicationMessagesAsync()
public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage)
{
ThrowIfDisposed();
ThrowIfNotStarted();
return _retainedMessagesManager.GetMessagesAsync();
}
public Task ClearRetainedApplicationMessagesAsync()
{
ThrowIfDisposed();
ThrowIfNotStarted();
return _retainedMessagesManager?.ClearMessagesAsync() ?? PlatformAbstractionLayer.CompletedTask;
}
public Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
foreach (var topicFilter in topicFilters)
if (injectedApplicationMessage == null)
{
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
throw new ArgumentNullException(nameof(injectedApplicationMessage));
}
ThrowIfDisposed();
ThrowIfNotStarted();
if (injectedApplicationMessage.ApplicationMessage == null)
{
throw new ArgumentNullException(nameof(injectedApplicationMessage.ApplicationMessage));
}
return _clientSessionsManager.SubscribeAsync(clientId, 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));
ThrowIfDisposed();
ThrowIfNotStarted();
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
}
public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
ThrowIfDisposed();
MqttTopicValidator.ThrowIfInvalid(applicationMessage.Topic);
MqttTopicValidator.ThrowIfInvalid(injectedApplicationMessage.ApplicationMessage.Topic);
ThrowIfNotStarted();
_clientSessionsManager.DispatchApplicationMessage(applicationMessage, null);
return Task.FromResult(new MqttClientPublishResult());
return _clientSessionsManager.DispatchApplicationMessage(injectedApplicationMessage.SenderClientId, injectedApplicationMessage.ApplicationMessage);
}
public async Task StartAsync(IMqttServerOptions options)
public async Task StartAsync()
{
ThrowIfDisposed();
ThrowIfStarted();
Options = options ?? throw new ArgumentNullException(nameof(options));
_cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = _cancellationTokenSource.Token;
_retainedMessagesManager = Options.RetainedMessagesManager ?? throw new MqttConfigurationException("options.RetainedMessagesManager should not be null.");
await _retainedMessagesManager.Start(Options, _rootLogger).ConfigureAwait(false);
await _retainedMessagesManager.LoadMessagesAsync().ConfigureAwait(false);
_clientSessionsManager = new MqttClientSessionsManager(Options, _retainedMessagesManager, _eventDispatcher, _rootLogger);
_clientSessionsManager.Start(cancellationToken);
_keepAliveMonitor = new MqttServerKeepAliveMonitor(Options, _clientSessionsManager, _rootLogger);
await _retainedMessagesManager.Start().ConfigureAwait(false);
_clientSessionsManager.Start();
_keepAliveMonitor.Start(cancellationToken);
foreach (var adapter in _adapters)
{
adapter.ClientHandler = c => OnHandleClient(c, cancellationToken);
await adapter.StartAsync(Options).ConfigureAwait(false);
await adapter.StartAsync(_options, _rootLogger).ConfigureAwait(false);
}
await _eventContainer.StartedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
_logger.Info("Started.");
var startedHandler = StartedHandler;
if (startedHandler != null)
{
await startedHandler.HandleServerStartedAsync(EventArgs.Empty).ConfigureAwait(false);
}
}
public async Task StopAsync()
@@ -201,31 +252,76 @@ namespace MQTTnet.Server
_cancellationTokenSource.Cancel(false);
await _clientSessionsManager.CloseAllConnectionsAsync().ConfigureAwait(false);
foreach (var adapter in _adapters)
{
adapter.ClientHandler = null;
await adapter.StopAsync().ConfigureAwait(false);
}
_logger.Info("Stopped.");
}
finally
{
_clientSessionsManager?.Dispose();
_clientSessionsManager = null;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_retainedMessagesManager = null;
}
var stoppedHandler = StoppedHandler;
if (stoppedHandler != null)
await _eventContainer.StoppedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
_logger.Info("Stopped.");
}
public Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters)
{
if (clientId == null)
{
await stoppedHandler.HandleServerStoppedAsync(EventArgs.Empty).ConfigureAwait(false);
throw new ArgumentNullException(nameof(clientId));
}
if (topicFilters == null)
{
throw new ArgumentNullException(nameof(topicFilters));
}
foreach (var topicFilter in topicFilters)
{
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
}
ThrowIfDisposed();
ThrowIfNotStarted();
return _clientSessionsManager.SubscribeAsync(clientId, 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));
}
ThrowIfDisposed();
ThrowIfNotStarted();
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
}
public Task UpdateRetainedMessageAsync(MqttApplicationMessage retainedMessage)
{
if (retainedMessage == null)
{
throw new ArgumentNullException(nameof(retainedMessage));
}
ThrowIfDisposed();
ThrowIfNotStarted();
return _retainedMessagesManager?.UpdateMessage(null, retainedMessage);
}
protected override void Dispose(bool disposing)
@@ -248,20 +344,24 @@ namespace MQTTnet.Server
return _clientSessionsManager.HandleClientConnectionAsync(channelAdapter, cancellationToken);
}
void ThrowIfStarted()
{
if (_cancellationTokenSource != null)
{
throw new InvalidOperationException("The MQTT server is already started.");
}
}
void ThrowIfNotStarted()
{
ThrowIfDisposed();
if (_cancellationTokenSource == null)
{
throw new InvalidOperationException("The MQTT server is not started.");
}
}
void ThrowIfStarted()
{
ThrowIfDisposed();
if (_cancellationTokenSource != null)
{
throw new InvalidOperationException("The MQTT server is already started.");
}
}
}
}
}
@@ -1,32 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public sealed class MqttServerApplicationMessageInterceptorDelegate : IMqttServerApplicationMessageInterceptor
{
readonly Func<MqttApplicationMessageInterceptorContext, Task> _callback;
public MqttServerApplicationMessageInterceptorDelegate(Action<MqttApplicationMessageInterceptorContext> callback)
{
if (callback == null) throw new ArgumentNullException(nameof(callback));
_callback = context =>
{
callback(context);
return PlatformAbstractionLayer.CompletedTask;
};
}
public MqttServerApplicationMessageInterceptorDelegate(Func<MqttApplicationMessageInterceptorContext, Task> callback)
{
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
}
public Task InterceptApplicationMessagePublishAsync(MqttApplicationMessageInterceptorContext context)
{
return _callback(context);
}
}
}
@@ -1,7 +0,0 @@
namespace MQTTnet.Server
{
public class MqttServerCertificateCredentials : IMqttServerCertificateCredentials
{
public string Password { get; set; }
}
}
@@ -1,32 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public sealed class MqttServerClientConnectedHandlerDelegate : IMqttServerClientConnectedHandler
{
readonly Func<MqttServerClientConnectedEventArgs, Task> _handler;
public MqttServerClientConnectedHandlerDelegate(Action<MqttServerClientConnectedEventArgs> handler)
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
_handler = eventArgs =>
{
handler(eventArgs);
return PlatformAbstractionLayer.CompletedTask;
};
}
public MqttServerClientConnectedHandlerDelegate(Func<MqttServerClientConnectedEventArgs, Task> handler)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
}
public Task HandleClientConnectedAsync(MqttServerClientConnectedEventArgs eventArgs)
{
return _handler(eventArgs);
}
}
}
@@ -1,32 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public sealed class MqttServerClientDisconnectedHandlerDelegate : IMqttServerClientDisconnectedHandler
{
readonly Func<MqttServerClientDisconnectedEventArgs, Task> _handler;
public MqttServerClientDisconnectedHandlerDelegate(Action<MqttServerClientDisconnectedEventArgs> handler)
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
_handler = eventArgs =>
{
handler(eventArgs);
return PlatformAbstractionLayer.CompletedTask;
};
}
public MqttServerClientDisconnectedHandlerDelegate(Func<MqttServerClientDisconnectedEventArgs, Task> handler)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
}
public Task HandleClientDisconnectedAsync(MqttServerClientDisconnectedEventArgs eventArgs)
{
return _handler(eventArgs);
}
}
}
@@ -1,44 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
[Obsolete("Use MqttServerClientSubscribedTopicHandlerDelegate instead. This will be removed in a future version.")]
public sealed class MqttServerClientSubscribedHandlerDelegate : MqttServerClientSubscribedTopicHandlerDelegate
{
public MqttServerClientSubscribedHandlerDelegate(Action<MqttServerClientSubscribedTopicEventArgs> handler) : base(handler)
{
}
public MqttServerClientSubscribedHandlerDelegate(Func<MqttServerClientSubscribedTopicEventArgs, Task> handler) : base(handler)
{
}
}
public class MqttServerClientSubscribedTopicHandlerDelegate : IMqttServerClientSubscribedTopicHandler
{
readonly Func<MqttServerClientSubscribedTopicEventArgs, Task> _handler;
public MqttServerClientSubscribedTopicHandlerDelegate(Action<MqttServerClientSubscribedTopicEventArgs> handler)
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
_handler = eventArgs =>
{
handler(eventArgs);
return PlatformAbstractionLayer.CompletedTask;
};
}
public MqttServerClientSubscribedTopicHandlerDelegate(Func<MqttServerClientSubscribedTopicEventArgs, Task> handler)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
}
public Task HandleClientSubscribedTopicAsync(MqttServerClientSubscribedTopicEventArgs eventArgs)
{
return _handler(eventArgs);
}
}
}
@@ -1,32 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public sealed class MqttServerClientUnsubscribedTopicHandlerDelegate : IMqttServerClientUnsubscribedTopicHandler
{
readonly Func<MqttServerClientUnsubscribedTopicEventArgs, Task> _handler;
public MqttServerClientUnsubscribedTopicHandlerDelegate(Action<MqttServerClientUnsubscribedTopicEventArgs> handler)
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
_handler = eventArgs =>
{
handler(eventArgs);
return PlatformAbstractionLayer.CompletedTask;
};
}
public MqttServerClientUnsubscribedTopicHandlerDelegate(Func<MqttServerClientUnsubscribedTopicEventArgs, Task> handler)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
}
public Task HandleClientUnsubscribedTopicAsync(MqttServerClientUnsubscribedTopicEventArgs eventArgs)
{
return _handler(eventArgs);
}
}
}
@@ -1,32 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public sealed class MqttServerConnectionValidatorDelegate : IMqttServerConnectionValidator
{
readonly Func<MqttConnectionValidatorContext, Task> _callback;
public MqttServerConnectionValidatorDelegate(Action<MqttConnectionValidatorContext> callback)
{
if (callback == null) throw new ArgumentNullException(nameof(callback));
_callback = context =>
{
callback(context);
return PlatformAbstractionLayer.CompletedTask;
};
}
public MqttServerConnectionValidatorDelegate(Func<MqttConnectionValidatorContext, Task> callback)
{
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
}
public Task ValidateConnectionAsync(MqttConnectionValidatorContext context)
{
return _callback(context);
}
}
}
+8 -208
View File
@@ -1,112 +1,18 @@
using MQTTnet.Client.Publishing;
using MQTTnet.Client.Receiving;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Protocol;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public static class MqttServerExtensions
{
public static IMqttServer UseClientConnectedHandler(this IMqttServer server, Func<MqttServerClientConnectedEventArgs, Task> handler)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (handler == null)
{
return server.UseClientConnectedHandler((IMqttServerClientConnectedHandler)null);
}
return server.UseClientConnectedHandler(new MqttServerClientConnectedHandlerDelegate(handler));
}
public static IMqttServer UseClientConnectedHandler(this IMqttServer server, Action<MqttServerClientConnectedEventArgs> handler)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (handler == null)
{
return server.UseClientConnectedHandler((IMqttServerClientConnectedHandler)null);
}
return server.UseClientConnectedHandler(new MqttServerClientConnectedHandlerDelegate(handler));
}
public static IMqttServer UseClientConnectedHandler(this IMqttServer server, IMqttServerClientConnectedHandler handler)
{
if (server == null) throw new ArgumentNullException(nameof(server));
server.ClientConnectedHandler = handler;
return server;
}
public static IMqttServer UseClientDisconnectedHandler(this IMqttServer server, Func<MqttServerClientDisconnectedEventArgs, Task> handler)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (handler == null)
{
return server.UseClientDisconnectedHandler((IMqttServerClientDisconnectedHandler)null);
}
return server.UseClientDisconnectedHandler(new MqttServerClientDisconnectedHandlerDelegate(handler));
}
public static IMqttServer UseClientDisconnectedHandler(this IMqttServer server, Action<MqttServerClientDisconnectedEventArgs> handler)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (handler == null)
{
return server.UseClientDisconnectedHandler((IMqttServerClientDisconnectedHandler)null);
}
return server.UseClientDisconnectedHandler(new MqttServerClientDisconnectedHandlerDelegate(handler));
}
public static IMqttServer UseClientDisconnectedHandler(this IMqttServer server, IMqttServerClientDisconnectedHandler handler)
{
if (server == null) throw new ArgumentNullException(nameof(server));
server.ClientDisconnectedHandler = handler;
return server;
}
public static IMqttServer UseApplicationMessageReceivedHandler(this IMqttServer server, Func<MqttApplicationMessageReceivedEventArgs, Task> handler)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (handler == null)
{
return server.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
}
return server.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
}
public static IMqttServer UseApplicationMessageReceivedHandler(this IMqttServer server, Action<MqttApplicationMessageReceivedEventArgs> handler)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (handler == null)
{
return server.UseApplicationMessageReceivedHandler((IMqttApplicationMessageReceivedHandler)null);
}
return server.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(handler));
}
public static IMqttServer UseApplicationMessageReceivedHandler(this IMqttServer server, IMqttApplicationMessageReceivedHandler handler)
{
if (server == null) throw new ArgumentNullException(nameof(server));
server.ApplicationMessageReceivedHandler = handler;
return server;
}
public static Task SubscribeAsync(this IMqttServer server, string clientId, params MqttTopicFilter[] topicFilters)
public static Task SubscribeAsync(this MqttServer server, string clientId, params MqttTopicFilter[] topicFilters)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
@@ -114,17 +20,8 @@ namespace MQTTnet.Server
return server.SubscribeAsync(clientId, topicFilters);
}
public static Task SubscribeAsync(this IMqttServer server, string clientId, string topic, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return server.SubscribeAsync(clientId, new MqttTopicFilterBuilder().WithTopic(topic).WithQualityOfServiceLevel(qualityOfServiceLevel).Build());
}
public static Task SubscribeAsync(this IMqttServer server, string clientId, string topic)
public static Task SubscribeAsync(this MqttServer server, string clientId, string topic)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
@@ -132,102 +29,5 @@ namespace MQTTnet.Server
return server.SubscribeAsync(clientId, new MqttTopicFilterBuilder().WithTopic(topic).Build());
}
public static Task UnsubscribeAsync(this IMqttServer server, string clientId, params string[] topicFilters)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return server.UnsubscribeAsync(clientId, topicFilters);
}
public static async Task PublishAsync(this IMqttServer server, IEnumerable<MqttApplicationMessage> applicationMessages)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await server.PublishAsync(applicationMessage).ConfigureAwait(false);
}
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, MqttApplicationMessage applicationMessage)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
return server.PublishAsync(applicationMessage, CancellationToken.None);
}
public static async Task PublishAsync(this IMqttServer server, params MqttApplicationMessage[] applicationMessages)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
foreach (var applicationMessage in applicationMessages)
{
await server.PublishAsync(applicationMessage, CancellationToken.None).ConfigureAwait(false);
}
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, string topic)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return server.PublishAsync(builder => builder
.WithTopic(topic));
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, string topic, string payload)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return server.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload));
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return server.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel));
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, string topic, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, bool retain)
{
if (server == null) throw new ArgumentNullException(nameof(server));
if (topic == null) throw new ArgumentNullException(nameof(topic));
return server.PublishAsync(builder => builder
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.WithRetainFlag(retain));
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder, CancellationToken cancellationToken)
{
if (server == null) throw new ArgumentNullException(nameof(server));
var message = builder(new MqttApplicationMessageBuilder()).Build();
return server.PublishAsync(message, cancellationToken);
}
public static Task<MqttClientPublishResult> PublishAsync(this IMqttServer server, Func<MqttApplicationMessageBuilder, MqttApplicationMessageBuilder> builder)
{
if (server == null) throw new ArgumentNullException(nameof(server));
var message = builder(new MqttApplicationMessageBuilder()).Build();
return server.PublishAsync(message, CancellationToken.None);
}
}
}
@@ -1,51 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Server
{
public sealed class MqttServerMultiThreadedApplicationMessageInterceptorDelegate : IMqttServerApplicationMessageInterceptor
{
readonly Func<MqttApplicationMessageInterceptorContext, Task> _callback;
public MqttServerMultiThreadedApplicationMessageInterceptorDelegate(Action<MqttApplicationMessageInterceptorContext> callback)
{
if (callback == null) throw new ArgumentNullException(nameof(callback));
_callback = context =>
{
callback(context);
return PlatformAbstractionLayer.CompletedTask;
};
}
public Func<MqttApplicationMessageInterceptorContext, Exception, Task> ExceptionHandler { get; set; }
public MqttServerMultiThreadedApplicationMessageInterceptorDelegate(Func<MqttApplicationMessageInterceptorContext, Task> callback)
{
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
}
public Task InterceptApplicationMessagePublishAsync(MqttApplicationMessageInterceptorContext context)
{
Task.Run(async () =>
{
try
{
await _callback.Invoke(context).ConfigureAwait(false);
}
catch (Exception exception)
{
var exceptionHandler = ExceptionHandler;
if (exceptionHandler != null)
{
await exceptionHandler.Invoke(context, exception).ConfigureAwait(false);
}
}
}).RunInBackground();
return PlatformAbstractionLayer.CompletedTask;
}
}
}
@@ -1,44 +0,0 @@
using System;
using MQTTnet.Server.Internal;
namespace MQTTnet.Server
{
public sealed class MqttServerOptions : IMqttServerOptions
{
public MqttServerTcpEndpointOptions DefaultEndpointOptions { get; } = new MqttServerTcpEndpointOptions();
public MqttServerTlsTcpEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsTcpEndpointOptions();
/// <summary>
/// Gets or sets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; set; }
public bool EnablePersistentSessions { get; set; }
public int MaxPendingMessagesPerClient { get; set; } = 250;
public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage;
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15);
public TimeSpan KeepAliveMonitorInterval { get; set; } = TimeSpan.FromMilliseconds(500);
public IMqttServerConnectionValidator ConnectionValidator { get; set; }
public IMqttServerApplicationMessageInterceptor ApplicationMessageInterceptor { get; set; }
public IMqttServerClientMessageQueueInterceptor ClientMessageQueueInterceptor { get; set; }
public IMqttServerSubscriptionInterceptor SubscriptionInterceptor { get; set; }
public IMqttServerUnsubscriptionInterceptor UnsubscriptionInterceptor { get; set; }
public IMqttServerApplicationMessageInterceptor UndeliveredMessageInterceptor { get; set; }
public IMqttServerStorage Storage { get; set; }
public IMqttRetainedMessagesManager RetainedMessagesManager { get; set; } = new MqttRetainedMessagesManager();
}
}
@@ -1,32 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public sealed class MqttServerStartedHandlerDelegate : IMqttServerStartedHandler
{
readonly Func<EventArgs, Task> _handler;
public MqttServerStartedHandlerDelegate(Action<EventArgs> handler)
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
_handler = eventArgs =>
{
handler(eventArgs);
return PlatformAbstractionLayer.CompletedTask;
};
}
public MqttServerStartedHandlerDelegate(Func<EventArgs, Task> handler)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
}
public Task HandleServerStartedAsync(EventArgs eventArgs)
{
return _handler(eventArgs);
}
}
}
@@ -1,32 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public sealed class MqttServerStoppedHandlerDelegate : IMqttServerStoppedHandler
{
readonly Func<EventArgs, Task> _handler;
public MqttServerStoppedHandlerDelegate(Action<EventArgs> handler)
{
if (handler == null) throw new ArgumentNullException(nameof(handler));
_handler = eventArgs =>
{
handler(eventArgs);
return PlatformAbstractionLayer.CompletedTask;
};
}
public MqttServerStoppedHandlerDelegate(Func<EventArgs, Task> handler)
{
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
}
public Task HandleServerStoppedAsync(EventArgs eventArgs)
{
return _handler(eventArgs);
}
}
}
@@ -1,32 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server
{
public sealed class MqttServerSubscriptionInterceptorDelegate : IMqttServerSubscriptionInterceptor
{
readonly Func<MqttSubscriptionInterceptorContext, Task> _callback;
public MqttServerSubscriptionInterceptorDelegate(Action<MqttSubscriptionInterceptorContext> callback)
{
if (callback == null) throw new ArgumentNullException(nameof(callback));
_callback = context =>
{
callback(context);
return PlatformAbstractionLayer.CompletedTask;
};
}
public MqttServerSubscriptionInterceptorDelegate(Func<MqttSubscriptionInterceptorContext, Task> callback)
{
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
}
public Task InterceptSubscriptionAsync(MqttSubscriptionInterceptorContext context)
{
return _callback(context);
}
}
}
@@ -1,11 +0,0 @@
namespace MQTTnet.Server
{
public class MqttServerTcpEndpointOptions : MqttServerTcpEndpointBaseOptions
{
public MqttServerTcpEndpointOptions()
{
IsEnabled = true;
Port = 1883;
}
}
}
@@ -1,56 +0,0 @@
using System;
using System.Security.Authentication;
using MQTTnet.Certificates;
namespace MQTTnet.Server
{
public class MqttServerTlsTcpEndpointOptions : MqttServerTcpEndpointBaseOptions
{
ICertificateProvider _certificateProvider;
public MqttServerTlsTcpEndpointOptions()
{
Port = 8883;
}
[Obsolete("Please use CertificateProvider with 'BlobCertificateProvider' instead.")]
public byte[] Certificate { get; set; }
[Obsolete("Please use CertificateProvider with 'BlobCertificateProvider' including password property instead.")]
public IMqttServerCertificateCredentials CertificateCredentials { get; set; }
#if !WINDOWS_UWP
public System.Net.Security.RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
#endif
public ICertificateProvider CertificateProvider
{
get
{
// Backward compatibility only. Gets converted to auto property when
// obsolete properties are removed.
if (_certificateProvider != null)
{
return _certificateProvider;
}
if (Certificate == null)
{
return null;
}
return new BlobCertificateProvider(Certificate)
{
Password = CertificateCredentials?.Password
};
}
set => _certificateProvider = value;
}
public bool ClientCertificateRequired { get; set; }
public bool CheckCertificateRevocation { get; set; }
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
}
}
@@ -1,28 +0,0 @@
using System.Collections.Generic;
namespace MQTTnet.Server
{
public sealed class MqttSubscriptionInterceptorContext
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
/// <summary>
/// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public MqttTopicFilter TopicFilter { get; set; }
/// <summary>
/// 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> SessionItems { get; internal set; }
public bool AcceptSubscription { get; set; } = true;
public bool CloseConnection { get; set; }
}
}
@@ -1,29 +0,0 @@
using System.Collections.Generic;
namespace MQTTnet.Server
{
public sealed class MqttUnsubscriptionInterceptorContext
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
/// <summary>
/// Gets or sets the MQTT topic.
/// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected client.
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator).
/// </summary>
public string Topic { get; internal set; }
/// <summary>
/// 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> SessionItems { get; internal set; }
public bool AcceptUnsubscription { get; set; } = true;
public bool CloseConnection { get; set; }
}
}
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Server
{
public interface IMqttServerCertificateCredentials
{
string Password { get; }
}
}
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Server
{
public enum MqttPendingMessagesOverflowStrategy
{
DropOldestQueuedMessage,
DropNewMessage
}
}
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Server
{
public class MqttServerCertificateCredentials : IMqttServerCertificateCredentials
{
public string Password { get; set; }
}
}
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Server
{
public sealed class MqttServerOptions
{
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(100);
public MqttServerTcpEndpointOptions DefaultEndpointOptions { get; } = new MqttServerTcpEndpointOptions();
public bool EnablePersistentSessions { get; set; }
public TimeSpan KeepAliveMonitorInterval { get; set; } = TimeSpan.FromMilliseconds(500);
public int MaxPendingMessagesPerClient { get; set; } = 250;
public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage;
public MqttServerTlsTcpEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsTcpEndpointOptions();
/// <summary>
/// Gets or sets the default and initial size of the packet write buffer.
/// It is recommended to set this to a value close to the usual expected packet size * 1.5.
/// Do not change this value when no memory issues are experienced.
/// </summary>
public int WriterBufferSize { get; set; } = 4096;
/// <summary>
/// Gets or sets the maximum size of the buffer writer. The writer will reduce its internal buffer
/// to this value after serializing a packet.
/// Do not change this value when no memory issues are experienced.
/// </summary>
public int WriterBufferSizeMax { get; set; } = 65535;
}
}
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Net;
using System.Net.Security;
using System.Security.Authentication;
@@ -140,103 +144,37 @@ namespace MQTTnet.Server
return this;
}
#endif
public MqttServerOptionsBuilder WithStorage(IMqttServerStorage value)
{
_options.Storage = value;
return this;
}
public MqttServerOptionsBuilder WithRetainedMessagesManager(IMqttRetainedMessagesManager value)
{
_options.RetainedMessagesManager = value;
return this;
}
public MqttServerOptionsBuilder WithConnectionValidator(IMqttServerConnectionValidator value)
{
_options.ConnectionValidator = value;
return this;
}
public MqttServerOptionsBuilder WithConnectionValidator(Action<MqttConnectionValidatorContext> value)
{
_options.ConnectionValidator = new MqttServerConnectionValidatorDelegate(value);
return this;
}
public MqttServerOptionsBuilder WithConnectionValidator(Func<MqttConnectionValidatorContext, Task> value)
{
_options.ConnectionValidator = new MqttServerConnectionValidatorDelegate(value);
return this;
}
// public MqttServerOptionsBuilder WithApplicationMessageInterceptor(IMqttServerApplicationMessageInterceptor value)
// {
// _options.ApplicationMessageInterceptor = value;
// return this;
// }
//
// public MqttServerOptionsBuilder WithApplicationMessageInterceptor(Action<InterceptingMqttClientPublishEventArgs> value)
// {
// _options.ApplicationMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(value);
// return this;
// }
//
// public MqttServerOptionsBuilder WithApplicationMessageInterceptor(Func<InterceptingMqttClientPublishEventArgs, Task> value)
// {
// _options.ApplicationMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(value);
// return this;
// }
// public MqttServerOptionsBuilder WithMultiThreadedApplicationMessageInterceptor(Action<InterceptingMqttClientPublishEventArgs> value)
// {
// _options.ApplicationMessageInterceptor = new MqttServerMultiThreadedApplicationMessageInterceptorDelegate(value);
// return this;
// }
//
// public MqttServerOptionsBuilder WithMultiThreadedApplicationMessageInterceptor(Func<InterceptingMqttClientPublishEventArgs, Task> value)
// {
// _options.ApplicationMessageInterceptor = new MqttServerMultiThreadedApplicationMessageInterceptorDelegate(value);
// return this;
// }
public MqttServerOptionsBuilder WithApplicationMessageInterceptor(IMqttServerApplicationMessageInterceptor value)
{
_options.ApplicationMessageInterceptor = value;
return this;
}
public MqttServerOptionsBuilder WithApplicationMessageInterceptor(Action<MqttApplicationMessageInterceptorContext> value)
{
_options.ApplicationMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(value);
return this;
}
public MqttServerOptionsBuilder WithApplicationMessageInterceptor(Func<MqttApplicationMessageInterceptorContext, Task> value)
{
_options.ApplicationMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(value);
return this;
}
public MqttServerOptionsBuilder WithMultiThreadedApplicationMessageInterceptor(Action<MqttApplicationMessageInterceptorContext> value)
{
_options.ApplicationMessageInterceptor = new MqttServerMultiThreadedApplicationMessageInterceptorDelegate(value);
return this;
}
public MqttServerOptionsBuilder WithMultiThreadedApplicationMessageInterceptor(Func<MqttApplicationMessageInterceptorContext, Task> value)
{
_options.ApplicationMessageInterceptor = new MqttServerMultiThreadedApplicationMessageInterceptorDelegate(value);
return this;
}
public MqttServerOptionsBuilder WithClientMessageQueueInterceptor(IMqttServerClientMessageQueueInterceptor value)
{
_options.ClientMessageQueueInterceptor = value;
return this;
}
public MqttServerOptionsBuilder WithClientMessageQueueInterceptor(Action<MqttClientMessageQueueInterceptorContext> value)
{
_options.ClientMessageQueueInterceptor = new MqttClientMessageQueueInterceptorDelegate(value);
return this;
}
public MqttServerOptionsBuilder WithSubscriptionInterceptor(IMqttServerSubscriptionInterceptor value)
{
_options.SubscriptionInterceptor = value;
return this;
}
public MqttServerOptionsBuilder WithUnsubscriptionInterceptor(IMqttServerUnsubscriptionInterceptor value)
{
_options.UnsubscriptionInterceptor = value;
return this;
}
public MqttServerOptionsBuilder WithSubscriptionInterceptor(Action<MqttSubscriptionInterceptorContext> value)
{
_options.SubscriptionInterceptor = new MqttServerSubscriptionInterceptorDelegate(value);
return this;
}
public MqttServerOptionsBuilder WithUndeliveredMessageInterceptor(Action<MqttApplicationMessageInterceptorContext> value)
{
_options.UndeliveredMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(value);
return this;
}
public MqttServerOptionsBuilder WithDefaultEndpointReuseAddress()
{
_options.DefaultEndpointOptions.ReuseAddress = true;
@@ -249,22 +187,22 @@ namespace MQTTnet.Server
return this;
}
public MqttServerOptionsBuilder WithPersistentSessions()
public MqttServerOptionsBuilder WithPersistentSessions(bool value = true)
{
_options.EnablePersistentSessions = true;
_options.EnablePersistentSessions = value;
return this;
}
// /// <summary>
// /// Gets or sets the client ID which is used when publishing messages from the server directly.
// /// </summary>
// public MqttServerOptionsBuilder WithClientId(string value)
// {
// _options.ClientId = value;
// return this;
// }
/// <summary>
/// Gets or sets the client ID which is used when publishing messages from the server directly.
/// </summary>
public MqttServerOptionsBuilder WithClientId(string value)
{
_options.ClientId = value;
return this;
}
public IMqttServerOptions Build()
public MqttServerOptions Build()
{
return _options;
}
@@ -1,4 +1,9 @@
using System.Net;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Net;
using System.Net.Sockets;
namespace MQTTnet.Server
{
@@ -8,10 +13,12 @@ namespace MQTTnet.Server
public int Port { get; set; }
public int ConnectionBacklog { get; set; } = 10;
public int ConnectionBacklog { get; set; } = 100;
public bool NoDelay { get; set; } = true;
public LingerOption LingerState { get; set; } = new LingerOption(true, 0);
#if WINDOWS_UWP
public int BufferSize { get; set; } = 4096;
#endif
@@ -21,7 +28,7 @@ namespace MQTTnet.Server
public IPAddress BoundInterNetworkV6Address { get; set; } = IPAddress.IPv6Any;
/// <summary>
/// This requires admin permissions on Linux.
/// This requires admin permissions on Linux.
/// </summary>
public bool ReuseAddress { get; set; }
}
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Server
{
public class MqttServerTcpEndpointOptions : MqttServerTcpEndpointBaseOptions
{
public MqttServerTcpEndpointOptions()
{
Port = 1883;
}
}
}
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Security.Authentication;
using MQTTnet.Certificates;
namespace MQTTnet.Server
{
public sealed class MqttServerTlsTcpEndpointOptions : MqttServerTcpEndpointBaseOptions
{
public MqttServerTlsTcpEndpointOptions()
{
Port = 8883;
}
#if !WINDOWS_UWP
public System.Net.Security.RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
#endif
public ICertificateProvider CertificateProvider { get; set; }
public bool ClientCertificateRequired { get; set; }
public bool CheckCertificateRevocation { get; set; }
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
}
}
+19
View File
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class PublishResponse
{
public MqttPubAckReasonCode ReasonCode { get; set; } = MqttPubAckReasonCode.Success;
public string ReasonString { get; set; }
public List<MqttUserProperty> UserProperties { get; }
}
}
@@ -1,45 +0,0 @@
using MQTTnet.Formatter;
using System;
using System.Threading.Tasks;
namespace MQTTnet.Server.Status
{
public interface IMqttClientStatus
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
string ClientId { get; }
string Endpoint { get; }
MqttProtocolVersion ProtocolVersion { get; }
DateTime ConnectedTimestamp { get; set; }
DateTime LastPacketReceivedTimestamp { get; }
DateTime LastPacketSentTimestamp { get; set; }
DateTime LastNonKeepAlivePacketReceivedTimestamp { get; }
long ReceivedApplicationMessagesCount { get; }
long SentApplicationMessagesCount { get; }
long ReceivedPacketsCount { get; }
long SentPacketsCount { get; }
IMqttSessionStatus Session { get; }
long BytesSent { get; }
long BytesReceived { get; }
Task DisconnectAsync();
void ResetStatistics();
}
}
@@ -1,25 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MQTTnet.Server.Status
{
public interface IMqttSessionStatus
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
string ClientId { get; }
/// <summary>
/// Gets the count of messages which are not yet sent to the client but already queued.
/// </summary>
long PendingApplicationMessagesCount { get; }
IDictionary<object, object> Items { get; }
Task ClearPendingApplicationMessagesAsync();
Task DeleteAsync();
}
}
@@ -1,60 +1,63 @@
using MQTTnet.Formatter;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Server.Internal;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Status
namespace MQTTnet.Server
{
public sealed class MqttClientStatus : IMqttClientStatus
public sealed class MqttClientStatus
{
readonly MqttClientConnection _connection;
readonly MqttClient _client;
public MqttClientStatus(MqttClientConnection connection)
public MqttClientStatus(MqttClient client)
{
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
_client = client ?? throw new ArgumentNullException(nameof(client));
}
/// <summary>
/// Gets or sets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; set; }
public string Id => _client.Id;
public string Endpoint { get; set; }
public string Endpoint => _client.Endpoint;
public MqttProtocolVersion ProtocolVersion { get; set; }
public MqttProtocolVersion ProtocolVersion => _client.ChannelAdapter.PacketFormatterAdapter.ProtocolVersion;
public DateTime ConnectedTimestamp { get; set; }
public DateTime LastPacketReceivedTimestamp { get; set; }
public DateTime LastPacketSentTimestamp { get; set; }
public DateTime ConnectedTimestamp => _client.Statistics.ConnectedTimestamp;
public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; set; }
public DateTime LastPacketReceivedTimestamp => _client.Statistics.LastPacketReceivedTimestamp;
public long ReceivedApplicationMessagesCount { get; set; }
public DateTime LastPacketSentTimestamp => _client.Statistics.LastPacketSentTimestamp;
public long SentApplicationMessagesCount { get; set; }
public DateTime LastNonKeepAlivePacketReceivedTimestamp => _client.Statistics.LastNonKeepAlivePacketReceivedTimestamp;
public long ReceivedPacketsCount { get; set; }
public long ReceivedApplicationMessagesCount => _client.Statistics.ReceivedApplicationMessagesCount;
public long SentPacketsCount { get; set; }
public long SentApplicationMessagesCount => _client.Statistics.SentApplicationMessagesCount;
public IMqttSessionStatus Session { get; set; }
public long ReceivedPacketsCount => _client.Statistics.ReceivedPacketsCount;
public long BytesSent { get; set; }
public long SentPacketsCount => _client.Statistics.SentPacketsCount;
public long BytesReceived { get; set; }
public MqttSessionStatus Session { get; set; }
public long BytesSent => _client.ChannelAdapter.BytesSent;
public long BytesReceived => _client.ChannelAdapter.BytesReceived;
public Task DisconnectAsync()
{
return _connection.StopAsync(MqttClientDisconnectReason.NormalDisconnection);
return _client.StopAsync(MqttDisconnectReasonCode.NormalDisconnection);
}
public void ResetStatistics()
{
_connection.ResetStatistics();
_client.ResetStatistics();
}
}
}
@@ -1,45 +1,62 @@
using System;
using System.Collections.Generic;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Threading.Tasks;
using MQTTnet.Server.Internal;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Server.Status
namespace MQTTnet.Server
{
public sealed class MqttSessionStatus : IMqttSessionStatus
public sealed class MqttSessionStatus
{
readonly MqttClientSession _session;
readonly MqttClientSessionsManager _sessionsManager;
readonly MqttSession _session;
public MqttSessionStatus(MqttClientSession session, MqttClientSessionsManager sessionsManager)
public MqttSessionStatus(MqttSession session)
{
_session = session ?? throw new ArgumentNullException(nameof(session));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
}
public string Id => _session.Id;
public long PendingApplicationMessagesCount => _session.PendingDataPacketsCount;
public DateTime CreatedTimestamp => _session.CreatedTimestamp;
public IDictionary Items => _session.Items;
public Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
var publishPacketFactory = new MqttPublishPacketFactory();
_session.EnqueuePacket(new MqttPacketBusItem(publishPacketFactory.Create(applicationMessage)));
return PlatformAbstractionLayer.CompletedTask;
}
/// <summary>
/// Gets or sets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; set; }
public Task DeliverApplicationMessageAsync(MqttApplicationMessage applicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
var publishPacketFactory = new MqttPublishPacketFactory();
var packetBusItem = new MqttPacketBusItem(publishPacketFactory.Create(applicationMessage));
_session.EnqueuePacket(packetBusItem);
public long PendingApplicationMessagesCount { get; set; }
public DateTime CreatedTimestamp { get; set; }
/// <summary>
/// This items can be used by the library user in order to store custom information.
/// </summary>
public IDictionary<object, object> Items { get; set; }
return packetBusItem.WaitForDeliveryAsync();
}
public Task DeleteAsync()
{
return _sessionsManager.DeleteSessionAsync(ClientId);
return _session.DeleteAsync();
}
public Task ClearPendingApplicationMessagesAsync()
public Task ClearApplicationMessagesQueueAsync()
{
_session.ApplicationMessagesQueue.Clear();
return Task.FromResult(0);
throw new NotImplementedException();
}
}
}
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class SubscribeResponse
{
/// <summary>
/// Gets or sets the reason code which is sent to the client.
/// The subscription is skipped when the value is not GrantedQoS_.
/// MQTTv5 only.
/// </summary>
public MqttSubscribeReasonCode ReasonCode { get; set; }
public List<MqttUserProperty> UserProperties { get; } = new List<MqttUserProperty>();
public string ReasonString { get; set; }
}
}
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class UnsubscribeResponse
{
/// <summary>
/// Gets or sets the reason code which is sent to the client.
/// MQTTv5 only.
/// </summary>
public MqttUnsubscribeReasonCode ReasonCode { get; set; }
public List<MqttUserProperty> UserProperties { get; } = new List<MqttUserProperty>();
public string ReasonString { get; set; }
}
}