diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec
index 2c200893..bb6012d3 100644
--- a/Build/MQTTnet.nuspec
+++ b/Build/MQTTnet.nuspec
@@ -20,6 +20,11 @@
* [Server] Improved topic filter comparisons (support for $).
* [Server] Added more MQTTv5 response information to all interceptors (BREAKING CHANGE!).
* [Server] Improved session management for MQTT v5 (#1294, thanks to @logicaloud).
+* [Server] All interceptors and events are migrated from interfaces to simple events. All existing APIs are availble but must be migrated to corresponding events.
+* [Server] Removed all interceptor and event interfaces including the delegate implementations etc.
+* [Server] Renamed a lot of classes and adjsuted namespaces.
+* [Server] Introduced a new queueing approach for internal message process (packet bus).
+
Git commit: $gitCommit
Copyright Christian Kratky 2016-2021
diff --git a/MQTTnet.sln.DotSettings b/MQTTnet.sln.DotSettings
index bcb734e5..e5f37ff2 100644
--- a/MQTTnet.sln.DotSettings
+++ b/MQTTnet.sln.DotSettings
@@ -1,9 +1,244 @@
+ Required
+ Required
+ Required
+ Required
NEVER
False
False
False
180
+ <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
+ <TypePattern DisplayName="Non-reorderable types">
+ <TypePattern.Match>
+ <Or>
+ <And>
+ <Kind Is="Interface" />
+ <Or>
+ <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" />
+ <HasAttribute Name="System.Runtime.InteropServices.ComImport" />
+ </Or>
+ </And>
+ <Kind Is="Struct" />
+ <HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" />
+ <HasAttribute Name="JetBrains.Annotations.NoReorder" />
+ </Or>
+ </TypePattern.Match>
+ </TypePattern>
+
+ <TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All">
+ <TypePattern.Match>
+ <And>
+ <Kind Is="Class" />
+ <HasMember>
+ <And>
+ <Kind Is="Method" />
+ <HasAttribute Name="Xunit.FactAttribute" Inherited="True" />
+ <HasAttribute Name="Xunit.TheoryAttribute" Inherited="True" />
+ </And>
+ </HasMember>
+ </And>
+ </TypePattern.Match>
+
+ <Entry DisplayName="Setup/Teardown Methods">
+ <Entry.Match>
+ <Or>
+ <Kind Is="Constructor" />
+ <And>
+ <Kind Is="Method" />
+ <ImplementsInterface Name="System.IDisposable" />
+ </And>
+ </Or>
+ </Entry.Match>
+
+ <Entry.SortBy>
+ <Kind>
+ <Kind.Order>
+ <DeclarationKind>Constructor</DeclarationKind>
+ </Kind.Order>
+ </Kind>
+ </Entry.SortBy>
+ </Entry>
+
+
+ <Entry DisplayName="All other members" />
+
+ <Entry DisplayName="Test Methods" Priority="100">
+ <Entry.Match>
+ <And>
+ <Kind Is="Method" />
+ <HasAttribute Name="Xunit.FactAttribute" Inherited="false" />
+ <HasAttribute Name="Xunit.TheoryAttribute" Inherited="false" />
+ </And>
+ </Entry.Match>
+
+ <Entry.SortBy>
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ </TypePattern>
+
+ <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All">
+ <TypePattern.Match>
+ <And>
+ <Kind Is="Class" />
+ <Or>
+ <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="true" />
+ <HasAttribute Name="NUnit.Framework.TestFixtureSourceAttribute" Inherited="true" />
+ <HasMember>
+ <And>
+ <Kind Is="Method" />
+ <HasAttribute Name="NUnit.Framework.TestAttribute" Inherited="false" />
+ <HasAttribute Name="NUnit.Framework.TestCaseAttribute" Inherited="false" />
+ <HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" Inherited="false" />
+ </And>
+ </HasMember>
+ </Or>
+ </And>
+ </TypePattern.Match>
+
+ <Entry DisplayName="Setup/Teardown Methods">
+ <Entry.Match>
+ <And>
+ <Kind Is="Method" />
+ <Or>
+ <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="true" />
+ <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="true" />
+ <HasAttribute Name="NUnit.Framework.TestFixtureSetUpAttribute" Inherited="true" />
+ <HasAttribute Name="NUnit.Framework.TestFixtureTearDownAttribute" Inherited="true" />
+ <HasAttribute Name="NUnit.Framework.OneTimeSetUpAttribute" Inherited="true" />
+ <HasAttribute Name="NUnit.Framework.OneTimeTearDownAttribute" Inherited="true" />
+ </Or>
+ </And>
+ </Entry.Match>
+ </Entry>
+
+ <Entry DisplayName="All other members" />
+
+ <Entry DisplayName="Test Methods" Priority="100">
+ <Entry.Match>
+ <And>
+ <Kind Is="Method" />
+ <HasAttribute Name="NUnit.Framework.TestAttribute" Inherited="false" />
+ <HasAttribute Name="NUnit.Framework.TestCaseAttribute" Inherited="false" />
+ <HasAttribute Name="NUnit.Framework.TestCaseSourceAttribute" Inherited="false" />
+ </And>
+ </Entry.Match>
+
+ <Entry.SortBy>
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ </TypePattern>
+
+ <TypePattern DisplayName="Default Pattern">
+ <Entry DisplayName="Public Delegates" Priority="100">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Kind Is="Delegate" />
+ </And>
+ </Entry.Match>
+
+ <Entry.SortBy>
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+
+ <Entry DisplayName="Public Enums" Priority="100">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Kind Is="Enum" />
+ </And>
+ </Entry.Match>
+
+ <Entry.SortBy>
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+
+ <Entry DisplayName="Static Fields and Constants">
+ <Entry.Match>
+ <Or>
+ <Kind Is="Constant" />
+ <And>
+ <Kind Is="Field" />
+ <Static />
+ </And>
+ </Or>
+ </Entry.Match>
+
+ <Entry.SortBy>
+ <Kind>
+ <Kind.Order>
+ <DeclarationKind>Constant</DeclarationKind>
+ <DeclarationKind>Field</DeclarationKind>
+ </Kind.Order>
+ </Kind>
+ </Entry.SortBy>
+ </Entry>
+
+ <Entry DisplayName="Fields">
+ <Entry.Match>
+ <And>
+ <Kind Is="Field" />
+ <Not>
+ <Static />
+ </Not>
+ </And>
+ </Entry.Match>
+
+ <Entry.SortBy>
+ <Readonly />
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+
+ <Entry DisplayName="Constructors">
+ <Entry.Match>
+ <Kind Is="Constructor" />
+ </Entry.Match>
+
+ <Entry.SortBy>
+ <Static/>
+ </Entry.SortBy>
+ </Entry>
+
+ <Entry DisplayName="Events">
+ <Entry.Match>
+ <Kind Is="Event" />
+ </Entry.Match>
+
+ <Entry.SortBy>
+ <Name Direction="Ascending" />
+ </Entry.SortBy>
+ </Entry>
+
+ <Entry DisplayName="Properties, Indexers">
+ <Entry.Match>
+ <Or>
+ <Kind Is="Property" />
+ <Kind Is="Indexer" />
+ </Or>
+ </Entry.Match>
+ </Entry>
+
+ <Entry DisplayName="All other members">
+ <Entry.SortBy>
+ <Access Order="public internal protected private" />
+ <Name Direction="Ascending" />
+ </Entry.SortBy>
+ </Entry>
+
+ <Entry DisplayName="Nested Types">
+ <Entry.Match>
+ <Kind Is="Type" />
+ </Entry.Match>
+ </Entry>
+ </TypePattern>
+</Patterns>
+
True
True
True
diff --git a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs
index 389ec72e..71341047 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs
+++ b/Source/MQTTnet.Extensions.ManagedClient/ManagedMqttClient.cs
@@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Packets;
+using IMqttClient = MQTTnet.Client.IMqttClient;
namespace MQTTnet.Extensions.ManagedClient
{
diff --git a/Source/MQTTnet/Formatter/MqttConnAckPacketFactory.cs b/Source/MQTTnet/Formatter/MqttConnAckPacketFactory.cs
index 043357fa..85438cab 100644
--- a/Source/MQTTnet/Formatter/MqttConnAckPacketFactory.cs
+++ b/Source/MQTTnet/Formatter/MqttConnAckPacketFactory.cs
@@ -6,14 +6,14 @@ namespace MQTTnet.Formatter
{
public sealed class MqttConnAckPacketFactory
{
- public MqttConnAckPacket Create(ValidatingConnectionEventArgs clientConnectionValidationEventArgs)
+ public MqttConnAckPacket Create(ValidatingConnectionEventArgs validatingConnectionEventArgs)
{
- if (clientConnectionValidationEventArgs == null) throw new ArgumentNullException(nameof(clientConnectionValidationEventArgs));
+ if (validatingConnectionEventArgs == null) throw new ArgumentNullException(nameof(validatingConnectionEventArgs));
var connAckPacket = new MqttConnAckPacket
{
- ReturnCode = MqttConnectReasonCodeConverter.ToConnectReturnCode(clientConnectionValidationEventArgs.ReasonCode),
- ReasonCode = clientConnectionValidationEventArgs.ReasonCode,
+ ReturnCode = MqttConnectReasonCodeConverter.ToConnectReturnCode(validatingConnectionEventArgs.ReasonCode),
+ ReasonCode = validatingConnectionEventArgs.ReasonCode,
Properties =
{
RetainAvailable = true,
@@ -22,17 +22,17 @@ namespace MQTTnet.Formatter
TopicAliasMaximum = ushort.MaxValue,
WildcardSubscriptionAvailable = true,
- AuthenticationMethod = clientConnectionValidationEventArgs.AuthenticationMethod,
- AuthenticationData = clientConnectionValidationEventArgs.ResponseAuthenticationData,
- AssignedClientIdentifier = clientConnectionValidationEventArgs.AssignedClientIdentifier,
- ReasonString = clientConnectionValidationEventArgs.ReasonString,
- ServerReference = clientConnectionValidationEventArgs.ServerReference
+ AuthenticationMethod = validatingConnectionEventArgs.AuthenticationMethod,
+ AuthenticationData = validatingConnectionEventArgs.ResponseAuthenticationData,
+ AssignedClientIdentifier = validatingConnectionEventArgs.AssignedClientIdentifier,
+ ReasonString = validatingConnectionEventArgs.ReasonString,
+ ServerReference = validatingConnectionEventArgs.ServerReference
}
};
- if (clientConnectionValidationEventArgs.ResponseUserProperties != null)
+ if (validatingConnectionEventArgs.ResponseUserProperties != null)
{
- connAckPacket.Properties.UserProperties.AddRange(clientConnectionValidationEventArgs.ResponseUserProperties);
+ connAckPacket.Properties.UserProperties.AddRange(validatingConnectionEventArgs.ResponseUserProperties);
}
return connAckPacket;
diff --git a/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs b/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs
index 82f36f1c..f8891342 100644
--- a/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs
+++ b/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs
@@ -6,6 +6,14 @@ namespace MQTTnet.Formatter
{
public sealed class MqttDisconnectPacketFactory
{
+ public MqttDisconnectPacket Create(MqttDisconnectReasonCode reasonCode)
+ {
+ return new MqttDisconnectPacket
+ {
+ ReasonCode = reasonCode
+ };
+ }
+
public MqttDisconnectPacket Create(MqttClientDisconnectOptions clientDisconnectOptions)
{
var packet = new MqttDisconnectPacket();
diff --git a/Source/MQTTnet/Internal/AsyncEvent.cs b/Source/MQTTnet/Internal/AsyncEvent.cs
index 9fbaa19c..9a60c505 100644
--- a/Source/MQTTnet/Internal/AsyncEvent.cs
+++ b/Source/MQTTnet/Internal/AsyncEvent.cs
@@ -2,39 +2,49 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using MQTTnet.Diagnostics;
namespace MQTTnet.Internal
{
public sealed class AsyncEvent where TEventArgs : EventArgs
{
- readonly List> _handlers = new List>();
+ readonly List> _handlers = new List>();
public void AddHandler(Func handler)
{
- if (handler == null) throw new ArgumentNullException(nameof(handler));
-
- _handlers.Add(handler);
- }
+ if (handler == null)
+ {
+ throw new ArgumentNullException(nameof(handler));
+ }
- public void RemoveHandler(Func handler)
+ _handlers.Add(new AsyncEventInvocator(null, handler));
+ }
+
+ public void AddHandler(Action handler)
{
- if (handler == null) throw new ArgumentNullException(nameof(handler));
-
- _handlers.Remove(handler);
+ if (handler == null)
+ {
+ throw new ArgumentNullException(nameof(handler));
+ }
+
+ _handlers.Add(new AsyncEventInvocator(handler, null));
}
public async Task InvokeAsync(TEventArgs eventArgs)
{
foreach (var handler in _handlers)
{
- await handler.Invoke(eventArgs);
+ await handler.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
public async Task InvokeAsync(Func eventArgsProvider)
{
- if (eventArgsProvider == null) throw new ArgumentNullException(nameof(eventArgsProvider));
-
+ if (eventArgsProvider == null)
+ {
+ throw new ArgumentNullException(nameof(eventArgsProvider));
+ }
+
if (!_handlers.Any())
{
return default;
@@ -44,5 +54,47 @@ namespace MQTTnet.Internal
await InvokeAsync(eventArgs).ConfigureAwait(false);
return eventArgs;
}
+
+ public void RemoveHandler(Func handler)
+ {
+ if (handler == null)
+ {
+ throw new ArgumentNullException(nameof(handler));
+ }
+
+ _handlers.RemoveAll(h => h.WrapsHandler(handler));
+ }
+
+ public void RemoveHandler(Action handler)
+ {
+ if (handler == null)
+ {
+ throw new ArgumentNullException(nameof(handler));
+ }
+
+ _handlers.RemoveAll(h => h.WrapsHandler(handler));
+ }
+
+ public async Task TryInvokeAsync(TEventArgs eventArgs, MqttNetSourceLogger logger)
+ {
+ if (eventArgs == null)
+ {
+ throw new ArgumentNullException(nameof(eventArgs));
+ }
+
+ if (logger == null)
+ {
+ throw new ArgumentNullException(nameof(logger));
+ }
+
+ try
+ {
+ await InvokeAsync(eventArgs).ConfigureAwait(false);
+ }
+ catch (Exception exception)
+ {
+ logger.Warning(exception, $"Error while invoking event ({typeof(TEventArgs)}).");
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Internal/AsyncEventInvocator.cs b/Source/MQTTnet/Internal/AsyncEventInvocator.cs
new file mode 100644
index 00000000..d956dd9d
--- /dev/null
+++ b/Source/MQTTnet/Internal/AsyncEventInvocator.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Threading.Tasks;
+using MQTTnet.Implementations;
+
+namespace MQTTnet.Internal
+{
+ public sealed class AsyncEventInvocator
+ {
+ readonly Action _handler;
+ readonly Func _asyncHandler;
+
+ public AsyncEventInvocator(Action handler, Func asyncHandler)
+ {
+ _handler = handler;
+ _asyncHandler = asyncHandler;
+ }
+
+ public bool WrapsHandler(Action handler)
+ {
+ return ReferenceEquals(_handler, handler);
+ }
+
+ public bool WrapsHandler(Func handler)
+ {
+ return ReferenceEquals(_asyncHandler, handler);
+ }
+
+ public Task InvokeAsync(TEventArgs eventArgs)
+ {
+ if (_handler != null)
+ {
+ _handler.Invoke(eventArgs);
+ return PlatformAbstractionLayer.CompletedTask;
+ }
+
+ if (_asyncHandler != null)
+ {
+ return _asyncHandler.Invoke(eventArgs);
+ }
+
+ throw new InvalidOperationException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Internal/MqttPacketBus.cs b/Source/MQTTnet/Internal/MqttPacketBus.cs
index 446c26a6..0d7e8132 100644
--- a/Source/MQTTnet/Internal/MqttPacketBus.cs
+++ b/Source/MQTTnet/Internal/MqttPacketBus.cs
@@ -9,9 +9,7 @@ namespace MQTTnet.Internal
{
public sealed class MqttPacketBus : IDisposable
{
- readonly object _syncRoot = new object();
-
- readonly LinkedList[] _partitions = new LinkedList[]
+ readonly LinkedList[] _partitions =
{
new LinkedList(),
new LinkedList(),
@@ -19,10 +17,11 @@ namespace MQTTnet.Internal
};
readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0);
+ readonly object _syncRoot = new object();
int _activePartition = (int) MqttPacketBusPartition.Health;
- public int PacketsCount
+ public int ItemsCount
{
get
{
@@ -33,50 +32,18 @@ namespace MQTTnet.Internal
}
}
- public int DataPacketsCount
+ public void Clear()
{
- get
+ lock (_syncRoot)
{
- lock (_syncRoot)
+ foreach (var partition in _partitions)
{
- return _partitions[(int) MqttPacketBusPartition.Data].Count;
+ partition.Clear();
}
}
}
- public List ExportPackets(MqttPacketBusPartition partition)
- {
- lock (_syncRoot)
- {
- return _partitions[(int) partition].Select(i => i.Packet).ToList();
- }
- }
-
- public void Enqueue(MqttPacketBusItem item, MqttPacketBusPartition partition)
- {
- if (item == null) throw new ArgumentNullException(nameof(item));
-
- lock (_syncRoot)
- {
- _partitions[(int) partition].AddLast(item);
- }
-
- _semaphore.Release();
- }
-
- void MoveActivePartition()
- {
- if (_activePartition >= _partitions.Length - 1)
- {
- _activePartition = 0;
- }
- else
- {
- _activePartition++;
- }
- }
-
- public async Task DequeueAsync(CancellationToken cancellationToken)
+ public async Task DequeueItemAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
@@ -99,7 +66,7 @@ namespace MQTTnet.Internal
// the worker back to the thread pool.
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
}
-
+
cancellationToken.ThrowIfCancellationRequested();
throw new InvalidOperationException("MqttPacketBus is broken.");
@@ -110,15 +77,60 @@ namespace MQTTnet.Internal
_semaphore?.Dispose();
}
- public void Clear()
+ public void DropFirstItem(MqttPacketBusPartition partition)
{
lock (_syncRoot)
{
- foreach (var partition in _partitions)
+ var partitionInstance = _partitions[(int) partition];
+
+ if (partitionInstance.Any())
{
- partition.Clear();
+ partitionInstance.RemoveFirst();
}
}
}
+
+ public void EnqueueItem(MqttPacketBusItem item, MqttPacketBusPartition partition)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ lock (_syncRoot)
+ {
+ _partitions[(int) partition].AddLast(item);
+ }
+
+ _semaphore.Release();
+ }
+
+ public List ExportPackets(MqttPacketBusPartition partition)
+ {
+ lock (_syncRoot)
+ {
+ return _partitions[(int) partition].Select(i => i.Packet).ToList();
+ }
+ }
+
+ void MoveActivePartition()
+ {
+ if (_activePartition >= _partitions.Length - 1)
+ {
+ _activePartition = 0;
+ }
+ else
+ {
+ _activePartition++;
+ }
+ }
+
+ public int PartitionItemsCount(MqttPacketBusPartition partition)
+ {
+ lock (_syncRoot)
+ {
+ return _partitions[(int) partition].Count;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/MQTTnet.csproj.DotSettings b/Source/MQTTnet/MQTTnet.csproj.DotSettings
index da48a98e..2cabc058 100644
--- a/Source/MQTTnet/MQTTnet.csproj.DotSettings
+++ b/Source/MQTTnet/MQTTnet.csproj.DotSettings
@@ -12,6 +12,7 @@
True
True
True
+ True
True
True
True
\ No newline at end of file
diff --git a/Source/MQTTnet/MqttFactory.cs b/Source/MQTTnet/MqttFactory.cs
index 378a864e..34e1e5f4 100644
--- a/Source/MQTTnet/MqttFactory.cs
+++ b/Source/MQTTnet/MqttFactory.cs
@@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using MQTTnet.Diagnostics;
+using IMqttClient = MQTTnet.Client.IMqttClient;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet
diff --git a/Source/MQTTnet/Server/Events/InterceptingPublishEventArgs.cs b/Source/MQTTnet/Server/Events/InterceptingPublishEventArgs.cs
index 5054bf39..806a3b34 100644
--- a/Source/MQTTnet/Server/Events/InterceptingPublishEventArgs.cs
+++ b/Source/MQTTnet/Server/Events/InterceptingPublishEventArgs.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Threading;
@@ -17,7 +18,7 @@ namespace MQTTnet.Server
///
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
///
- public IDictionary