Merge branch 'develop' into master

This commit is contained in:
Vladimir Akopyan
2018-05-11 17:52:20 +02:00
committed by GitHub
75 changed files with 1884 additions and 1146 deletions
+12 -6
View File
@@ -12,7 +12,7 @@
<description>This is a extension library which allows executing synchronous device calls including a response using MQTTnet.</description> <description>This is a extension library which allows executing synchronous device calls including a response using MQTTnet.</description>
<releaseNotes>* Updated to MQTTnet 2.7.5. <releaseNotes>* Updated to MQTTnet 2.7.5.
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</copyright> <copyright>Copyright Christian Kratky 2016-2018</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
<dependencies> <dependencies>
<group targetFramework="netstandard2.0"> <group targetFramework="netstandard2.0">
@@ -21,19 +21,15 @@
<group targetFramework="netstandard1.3"> <group targetFramework="netstandard1.3">
<dependency id="MQTTnet" version="2.7.5" /> <dependency id="MQTTnet" version="2.7.5" />
</group> </group>
<group targetFramework="netstandard2.0"> <group targetFramework="netstandard2.0">
<dependency id="MQTTnet" version="2.7.5" /> <dependency id="MQTTnet" version="2.7.5" />
</group> </group>
<group targetFramework="uap10.0"> <group targetFramework="uap10.0">
<dependency id="MQTTnet" version="2.7.5" /> <dependency id="MQTTnet" version="2.7.5" />
</group> </group>
<group targetFramework="net452"> <group targetFramework="net452">
<dependency id="MQTTnet" version="2.7.5" /> <dependency id="MQTTnet" version="2.7.5" />
</group> </group>
<group targetFramework="net461"> <group targetFramework="net461">
<dependency id="MQTTnet" version="2.7.5" /> <dependency id="MQTTnet" version="2.7.5" />
</group> </group>
@@ -41,7 +37,17 @@
</metadata> </metadata>
<files> <files>
<!-- .NET Standard 1.3 -->
<file src="..\Extensions\MQTTnet.Extensions.Rpc\bin\Release\MQTTnet.Extensions.Rpc.*" target="lib\netstandard1.3\"/>
<!-- .NET Standard 2.0 --> <!-- .NET Standard 2.0 -->
<file src="..\Frameworks\MQTTnet.AspNetCore\bin\Release\netstandard2.0\MQTTnet.AspNetCore.*" target="lib\netstandard2.0\"/> <file src="..\Extensions\MQTTnet.Extensions.Rpc\bin\Release\MQTTnet.Extensions.Rpc.*" target="lib\netstandard2.0\"/>
<!-- Universal Windows -->
<file src="..\Extensions\MQTTnet.Extensions.Rpc\bin\Release\uap10.0\MQTTnet.Extensions.Rpc.*" target="lib\uap10.0\"/>
<!-- .NET Framework -->
<file src="..\Extensions\MQTTnet.Extensions.Rpc\bin\Release\net452\MQTTnet.Extensions.Rpc.*" target="lib\net452\"/>
<file src="..\Extensions\MQTTnet.Extensions.Rpc\bin\Release\net461\MQTTnet.Extensions.Rpc.*" target="lib\net461\"/>
</files> </files>
</package> </package>
+6 -18
View File
@@ -2,7 +2,7 @@
<package > <package >
<metadata> <metadata>
<id>MQTTnet</id> <id>MQTTnet</id>
<version>2.7.5</version> <version>2.8.0</version>
<authors>Christian Kratky</authors> <authors>Christian Kratky</authors>
<owners>Christian Kratky</owners> <owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> <licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,47 +10,36 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description> <description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
<releaseNotes> * [Client] Fixed a deadlock while the client disconnects. <releaseNotes> * [Client] Received messages are now processed in the worker thread by default. Added a new setting for switching back to dedicated threads.
* [Client] Fixed broken support for protocol version 3.1.0. * [Server] Added support for other WebSocket sub protocol formats like mqttv-3.1.1 (thanks to @israellot).
* [Server] The _MqttTcpServerAdapter_ is now added to the ASP.NET services. * [Server] The takeover of an existing client sessions is now treated as a _clean_ disconnect of the previous client.
* [Server] _MqttServerAdapter_ is renamed to _MqttTcpServerAdapter_ (BREAKING CHANGE!).
* [Server] The server no longer sends the will message of a client if the disconnect was clean (via _Disconnect_ packet).
* [Server] The application message interceptor now allows closing the connection.
* [Server] Added a new flag for the _ClientDisconnected_ event which contains a value indicating whether the disconnect was clean (via _Disconnect_ packet).
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2018</copyright> <copyright>Copyright Christian Kratky 2016-2018</copyright>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin</tags>
<dependencies> <dependencies>
<group targetFramework="netstandard1.3"> <group targetFramework="netstandard1.3">
<dependency id="NETStandard.Library" version="1.3.0" /> <dependency id="NETStandard.Library" version="1.3.0" />
<dependency id="System.Net.Security" version="4.3.2" /> <dependency id="System.Net.Security" version="4.3.2" />
<dependency id="System.Net.WebSockets" version="4.3.0" /> <dependency id="System.Net.WebSockets" version="4.3.0" />
<dependency id="System.Net.WebSockets.Client" version="4.3.1" /> <dependency id="System.Net.WebSockets.Client" version="4.3.1" />
</group> </group>
<group targetFramework="netstandard2.0"> <group targetFramework="netstandard2.0">
<dependency id="NETStandard.Library" version="2.0.0" /> <dependency id="NETStandard.Library" version="2.0.0" />
<dependency id="System.Net.Security" version="4.3.2" /> <dependency id="System.Net.Security" version="4.3.2" />
<dependency id="System.Net.WebSockets" version="4.3.0" /> <dependency id="System.Net.WebSockets" version="4.3.0" />
<dependency id="System.Net.WebSockets.Client" version="4.3.1" /> <dependency id="System.Net.WebSockets.Client" version="4.3.2" />
</group> </group>
<group targetFramework="uap10.0"> <group targetFramework="uap10.0">
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.1" /> <dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.1" />
</group> </group>
<group targetFramework="net452"> <group targetFramework="net452">
</group> </group>
<group targetFramework="net461"> <group targetFramework="net461">
</group> </group>
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>
<!-- .NET Standard 1.3 --> <!-- .NET Standard 1.3 -->
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard1.3\MQTTnet.*" target="lib\netstandard1.3\"/> <file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\netstandard1.3\MQTTnet.*" target="lib\netstandard1.3\"/>
@@ -62,7 +51,6 @@
<!-- .NET Framework --> <!-- .NET Framework -->
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net452\MQTTnet.*" target="lib\net452\"/> <file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net452\MQTTnet.*" target="lib\net452\"/>
<file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net461\MQTTnet.*" target="lib\net461\"/> <file src="..\Frameworks\MQTTnet.Netstandard\bin\Release\net461\MQTTnet.*" target="lib\net461\"/>
</files> </files>
</package> </package>
+5 -5
View File
@@ -20,11 +20,11 @@ if ($path) {
&$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx" &$msbuild ..\Frameworks\MQTTnet.AspNetCore\MQTTnet.AspNetCore.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
# Build the RPC extension # Build the RPC extension
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net452" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="net461" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard1.3" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="netstandard2.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
&$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m &$msbuild ..\Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj /t:Build /p:Configuration="Release" /p:TargetFramework="uap10.0" /p:FileVersion=$assemblyVersion /p:AssemblyVersion=$assemblyVersion /verbosity:m /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=".\..\..\Build\codeSigningKey.pfx"
Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue Remove-Item .\NuGet -Force -Recurse -ErrorAction SilentlyContinue
@@ -1,18 +1,18 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Internal; using MQTTnet.Exceptions;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc
{ {
public sealed class MqttRpcClient : IDisposable public sealed class MqttRpcClient : IDisposable
{ {
private const string ResponseTopic = "$MQTTnet.RPC/+/+/response";
private readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>(); private readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
private readonly IMqttClient _mqttClient; private readonly IMqttClient _mqttClient;
private bool _isEnabled;
public MqttRpcClient(IMqttClient mqttClient) public MqttRpcClient(IMqttClient mqttClient)
{ {
@@ -21,19 +21,22 @@ namespace MQTTnet.Extensions.Rpc
_mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived; _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
} }
public async Task EnableAsync() public Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{ {
await _mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(ResponseTopic).WithAtLeastOnceQoS().Build()); return ExecuteAsync(timeout, methodName, Encoding.UTF8.GetBytes(payload), qualityOfServiceLevel, CancellationToken.None);
_isEnabled = true;
} }
public async Task DisableAsync() public Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken)
{ {
await _mqttClient.UnsubscribeAsync(ResponseTopic); return ExecuteAsync(timeout, methodName, Encoding.UTF8.GetBytes(payload), qualityOfServiceLevel, cancellationToken);
_isEnabled = false;
} }
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel) public Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
return ExecuteAsync(timeout, methodName, payload, qualityOfServiceLevel, CancellationToken.None);
}
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken)
{ {
if (methodName == null) throw new ArgumentNullException(nameof(methodName)); if (methodName == null) throw new ArgumentNullException(nameof(methodName));
@@ -42,12 +45,7 @@ namespace MQTTnet.Extensions.Rpc
throw new ArgumentException("The method name cannot contain /, + or #."); throw new ArgumentException("The method name cannot contain /, + or #.");
} }
if (!_isEnabled) var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}";
{
throw new InvalidOperationException("The RPC client is not enabled.");
}
var requestTopic = $"$MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}";
var responseTopic = requestTopic + "/response"; var responseTopic = requestTopic + "/response";
var requestMessage = new MqttApplicationMessageBuilder() var requestMessage = new MqttApplicationMessageBuilder()
@@ -64,18 +62,49 @@ namespace MQTTnet.Extensions.Rpc
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
await _mqttClient.PublishAsync(requestMessage); await _mqttClient.SubscribeAsync(responseTopic, qualityOfServiceLevel).ConfigureAwait(false);
return await tcs.Task.TimeoutAfter(timeout); await _mqttClient.PublishAsync(requestMessage).ConfigureAwait(false);
using (var timeoutCts = new CancellationTokenSource(timeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token))
{
linkedCts.Token.Register(() =>
{
if (!tcs.Task.IsCompleted && !tcs.Task.IsFaulted && !tcs.Task.IsCanceled)
{
tcs.TrySetCanceled();
}
});
try
{
var result = await tcs.Task.ConfigureAwait(false);
timeoutCts.Cancel(false);
return result;
}
catch (TaskCanceledException taskCanceledException)
{
if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
{
throw new MqttCommunicationTimedOutException(taskCanceledException);
}
else
{
throw;
}
}
}
} }
finally finally
{ {
_waitingCalls.TryRemove(responseTopic, out _); _waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
} }
} }
private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs) private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
{ {
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out TaskCompletionSource<byte[]> tcs)) if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var tcs))
{ {
return; return;
} }
@@ -1 +1,29 @@
 // If using the MQTT client PubSubClient it must be ensured that the request topic for each method is subscribed like the following.
_mqttClient.subscribe("MQTTnet.RPC/+/ping");
_mqttClient.subscribe("MQTTnet.RPC/+/do_something");
// It is not allowed to change the structure of the topic. Otherwise RPC will not work. So method names can be separated using
// an _ or . but no +, # or . If it is required to distinguish between devices own rules can be defined like the following.
_mqttClient.subscribe("MQTTnet.RPC/+/deviceA.ping");
_mqttClient.subscribe("MQTTnet.RPC/+/deviceB.ping");
_mqttClient.subscribe("MQTTnet.RPC/+/deviceC.getTemperature");
// Within the callback of the MQTT client the topic must be checked if it belongs to MQTTnet RPC. The following code shows one
// possible way of doing this.
void mqtt_Callback(char *topic, byte *payload, unsigned int payloadLength)
{
String topicString = String(topic);
if (topicString.startsWith("MQTTnet.RPC/")) {
String responseTopic = topicString + String("/response");
if (topicString.endsWith("/deviceA.ping")) {
mqtt_publish(responseTopic, "pong", false);
return;
}
}
}
// Important notes:
// ! Do not send response message with the _retain_ flag set to true.
// ! All required data for a RPC call and the result must be placed into the payload.
@@ -17,12 +17,12 @@ namespace MQTTnet.AspNetCore
{ {
string subprotocol = null; string subprotocol = null;
if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues) if (context.Request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
&& requestedSubProtocolValues.Count > 0
&& requestedSubProtocolValues.Any(v => v.ToLower() == "mqtt")
)
{ {
subprotocol = "mqtt"; // Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
subprotocol = requestedSubProtocolValues
.OrderByDescending(p => p.Length)
.FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
} }
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>(); var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
@@ -3,12 +3,13 @@ using System.Net.WebSockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Serializer; using MQTTnet.Serializer;
using MQTTnet.Server; using MQTTnet.Server;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore
{ {
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter, IDisposable public class MqttWebSocketServerAdapter : IMqttServerAdapter
{ {
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted; public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
@@ -26,8 +27,7 @@ namespace MQTTnet.AspNetCore
{ {
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
var channel = new MqttWebSocketServerChannel(webSocket); var clientAdapter = new MqttChannelAdapter(new MqttWebSocketChannel(webSocket), new MqttPacketSerializer(), new MqttNetLogger());
var clientAdapter = new MqttChannelAdapter(channel, new MqttPacketSerializer(), new MqttNetLogger());
var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter); var eventArgs = new MqttServerAdapterClientAcceptedEventArgs(clientAdapter);
ClientAccepted?.Invoke(this, eventArgs); ClientAccepted?.Invoke(this, eventArgs);
@@ -1,60 +0,0 @@
using System;
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Implementations;
namespace MQTTnet.AspNetCore
{
public class MqttWebSocketServerChannel : IMqttChannel, IDisposable
{
private WebSocket _webSocket;
public MqttWebSocketServerChannel(WebSocket webSocket)
{
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
SendStream = new WebSocketStream(_webSocket);
ReceiveStream = SendStream;
}
public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }
public Task ConnectAsync()
{
return Task.CompletedTask;
}
public async Task DisconnectAsync()
{
if (_webSocket == null)
{
return;
}
try
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
finally
{
Dispose();
}
}
public void Dispose()
{
SendStream?.Dispose();
ReceiveStream?.Dispose();
_webSocket?.Dispose();
SendStream = null;
ReceiveStream = null;
_webSocket = null;
}
}
}
@@ -11,11 +11,11 @@ namespace MQTTnet.Adapter
{ {
IMqttPacketSerializer PacketSerializer { get; } IMqttPacketSerializer PacketSerializer { get; }
Task ConnectAsync(TimeSpan timeout); Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
Task DisconnectAsync(TimeSpan timeout); Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken);
Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets); Task SendPacketsAsync(TimeSpan timeout, IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken);
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken); Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
} }
@@ -4,7 +4,7 @@ using MQTTnet.Server;
namespace MQTTnet.Adapter namespace MQTTnet.Adapter
{ {
public interface IMqttServerAdapter public interface IMqttServerAdapter : IDisposable
{ {
event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted; event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
@@ -8,7 +8,6 @@ using System.Threading.Tasks;
using MQTTnet.Channel; using MQTTnet.Channel;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Serializer; using MQTTnet.Serializer;
@@ -19,11 +18,11 @@ namespace MQTTnet.Adapter
private const uint ErrorOperationAborted = 0x800703E3; private const uint ErrorOperationAborted = 0x800703E3;
private const int ReadBufferSize = 4096; // TODO: Move buffer size to config private const int ReadBufferSize = 4096; // TODO: Move buffer size to config
private bool _isDisposed;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private readonly IMqttChannel _channel; private readonly IMqttChannel _channel;
private bool _isDisposed;
public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetLogger logger) public MqttChannelAdapter(IMqttChannel channel, IMqttPacketSerializer serializer, IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -33,75 +32,52 @@ namespace MQTTnet.Adapter
public IMqttPacketSerializer PacketSerializer { get; } public IMqttPacketSerializer PacketSerializer { get; }
public Task ConnectAsync(TimeSpan timeout) public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
_logger.Verbose<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout); _logger.Verbose<MqttChannelAdapter>("Connecting [Timeout={0}]", timeout);
return ExecuteAndWrapExceptionAsync(() => _channel.ConnectAsync().TimeoutAfter(timeout)); return ExecuteAndWrapExceptionAsync(() =>
Internal.TaskExtensions.TimeoutAfter(ct => _channel.ConnectAsync(ct), timeout, cancellationToken));
} }
public Task DisconnectAsync(TimeSpan timeout) public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
_logger.Verbose<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout); _logger.Verbose<MqttChannelAdapter>("Disconnecting [Timeout={0}]", timeout);
return ExecuteAndWrapExceptionAsync(() => _channel.DisconnectAsync().TimeoutAfter(timeout)); return ExecuteAndWrapExceptionAsync(() =>
Internal.TaskExtensions.TimeoutAfter(ct => _channel.DisconnectAsync(), timeout, cancellationToken));
} }
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets) public async Task SendPacketsAsync(TimeSpan timeout, IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
return ExecuteAndWrapExceptionAsync(async () => foreach (var packet in packets)
{ {
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); if (packet == null)
try
{ {
foreach (var packet in packets) continue;
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (packet == null)
{
continue;
}
_logger.Verbose<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout);
var chunks = PacketSerializer.Serialize(packet);
foreach (var chunk in chunks)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
await _channel.SendStream.WriteAsync(chunk.Array, chunk.Offset, chunk.Count, cancellationToken).ConfigureAwait(false);
}
}
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (timeout > TimeSpan.Zero)
{
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
}
else
{
await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
}
finally
{
_semaphore.Release();
} }
await SendPacketAsync(timeout, cancellationToken, packet).ConfigureAwait(false);
}
}
private Task SendPacketAsync(TimeSpan timeout, CancellationToken cancellationToken, MqttBasePacket packet)
{
return ExecuteAndWrapExceptionAsync(() =>
{
_logger.Verbose<MqttChannelAdapter>("TX >>> {0} [Timeout={1}]", packet, timeout);
var packetData = PacketSerializer.Serialize(packet);
return Internal.TaskExtensions.TimeoutAfter(ct => _channel.WriteAsync(
packetData.Array,
packetData.Offset,
packetData.Count,
ct), timeout, cancellationToken);
}); });
} }
@@ -117,11 +93,11 @@ namespace MQTTnet.Adapter
{ {
if (timeout > TimeSpan.Zero) if (timeout > TimeSpan.Zero)
{ {
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false); receivedMqttPacket = await Internal.TaskExtensions.TimeoutAfter(ct => ReceiveAsync(_channel, ct), timeout, cancellationToken).ConfigureAwait(false);
} }
else else
{ {
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false); receivedMqttPacket = await ReceiveAsync(_channel, cancellationToken).ConfigureAwait(false);
} }
if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested) if (receivedMqttPacket == null || cancellationToken.IsCancellationRequested)
@@ -146,22 +122,22 @@ namespace MQTTnet.Adapter
return packet; return packet;
} }
private static async Task<ReceivedMqttPacket> ReceiveAsync(Stream stream, CancellationToken cancellationToken) private static async Task<ReceivedMqttPacket> ReceiveAsync(IMqttChannel channel, CancellationToken cancellationToken)
{ {
var header = await MqttPacketReader.ReadHeaderAsync(stream, cancellationToken).ConfigureAwait(false); var header = await MqttPacketReader.ReadHeaderAsync(channel, cancellationToken).ConfigureAwait(false);
if (header == null) if (header == null)
{ {
return null; return null;
} }
if (header.BodyLength == 0) if (header.BodyLength == 0)
{ {
return new ReceivedMqttPacket(header, new MemoryStream(new byte[0], false)); return new ReceivedMqttPacket(header, new MemoryStream(new byte[0], false));
} }
var body = header.BodyLength <= ReadBufferSize ? new MemoryStream(header.BodyLength) : new MemoryStream(); var body = new MemoryStream(header.BodyLength);
var buffer = new byte[ReadBufferSize]; var buffer = new byte[Math.Min(ReadBufferSize, header.BodyLength)];
while (body.Length < header.BodyLength) while (body.Length < header.BodyLength)
{ {
var bytesLeft = header.BodyLength - (int)body.Length; var bytesLeft = header.BodyLength - (int)body.Length;
@@ -170,7 +146,7 @@ namespace MQTTnet.Adapter
bytesLeft = buffer.Length; bytesLeft = buffer.Length;
} }
var readBytesCount = await stream.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false); var readBytesCount = await channel.ReadAsync(buffer, 0, bytesLeft, cancellationToken).ConfigureAwait(false);
// Check if the client closed the connection before sending the full body. // Check if the client closed the connection before sending the full body.
if (readBytesCount == 0) if (readBytesCount == 0)
@@ -240,7 +216,7 @@ namespace MQTTnet.Adapter
public void Dispose() public void Dispose()
{ {
_isDisposed = true; _isDisposed = true;
_semaphore?.Dispose();
_channel?.Dispose(); _channel?.Dispose();
} }
@@ -1,15 +1,15 @@
using System; using System;
using System.IO; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MQTTnet.Channel namespace MQTTnet.Channel
{ {
public interface IMqttChannel : IDisposable public interface IMqttChannel : IDisposable
{ {
Stream SendStream { get; } Task ConnectAsync(CancellationToken cancellationToken);
Stream ReceiveStream { get; }
Task ConnectAsync();
Task DisconnectAsync(); Task DisconnectAsync();
Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
} }
} }
@@ -6,17 +6,16 @@ namespace MQTTnet.Client
public interface IMqttClientOptions public interface IMqttClientOptions
{ {
string ClientId { get; } string ClientId { get; }
IMqttClientCredentials Credentials { get; }
bool CleanSession { get; } bool CleanSession { get; }
MqttApplicationMessage WillMessage { get; } IMqttClientCredentials Credentials { get; }
MqttProtocolVersion ProtocolVersion { get; }
IMqttClientChannelOptions ChannelOptions { get; }
TimeSpan CommunicationTimeout { get; } TimeSpan CommunicationTimeout { get; }
TimeSpan KeepAlivePeriod { get; } TimeSpan KeepAlivePeriod { get; }
TimeSpan? KeepAliveSendInterval { get; } TimeSpan? KeepAliveSendInterval { get; }
MqttReceivedApplicationMessageProcessingMode ReceivedApplicationMessageProcessingMode { get; }
MqttProtocolVersion ProtocolVersion { get; } MqttApplicationMessage WillMessage { get; }
IMqttClientChannelOptions ChannelOptions { get; }
} }
} }
@@ -18,12 +18,12 @@ namespace MQTTnet.Client
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
private readonly Stopwatch _sendTracker = new Stopwatch(); private readonly Stopwatch _sendTracker = new Stopwatch();
private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _disconnectLock = new SemaphoreSlim(1, 1);
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
private readonly IMqttClientAdapterFactory _adapterFactory; private readonly IMqttClientAdapterFactory _adapterFactory;
private readonly MqttPacketDispatcher _packetDispatcher;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private IMqttClientOptions _options; private IMqttClientOptions _options;
private bool _isReceivingPackets;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private Task _packetReceiverTask; private Task _packetReceiverTask;
private Task _keepAliveMessageSenderTask; private Task _keepAliveMessageSenderTask;
@@ -33,8 +33,6 @@ namespace MQTTnet.Client
{ {
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory)); _adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_packetDispatcher = new MqttPacketDispatcher(logger);
} }
public event EventHandler<MqttClientConnectedEventArgs> Connected; public event EventHandler<MqttClientConnectedEventArgs> Connected;
@@ -52,27 +50,27 @@ namespace MQTTnet.Client
try try
{ {
_options = options;
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
_options = options;
_packetIdentifierProvider.Reset(); _packetIdentifierProvider.Reset();
_packetDispatcher.Reset(); _packetDispatcher.Reset();
_adapter = _adapterFactory.CreateClientAdapter(options, _logger); _adapter = _adapterFactory.CreateClientAdapter(options, _logger);
_logger.Verbose<MqttClient>("Trying to connect with server."); _logger.Verbose<MqttClient>("Trying to connect with server.");
await _adapter.ConnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); await _adapter.ConnectAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token).ConfigureAwait(false);
_logger.Verbose<MqttClient>("Connection with server established."); _logger.Verbose<MqttClient>("Connection with server established.");
await StartReceivingPacketsAsync().ConfigureAwait(false); StartReceivingPackets(_cancellationTokenSource.Token);
var connectResponse = await AuthenticateAsync(options.WillMessage).ConfigureAwait(false); var connectResponse = await AuthenticateAsync(options.WillMessage, _cancellationTokenSource.Token).ConfigureAwait(false);
_logger.Verbose<MqttClient>("MQTT connection with server established."); _logger.Verbose<MqttClient>("MQTT connection with server established.");
_sendTracker.Restart(); _sendTracker.Restart();
if (_options.KeepAlivePeriod != TimeSpan.Zero) if (_options.KeepAlivePeriod != TimeSpan.Zero)
{ {
StartSendingKeepAliveMessages(); StartSendingKeepAliveMessages(_cancellationTokenSource.Token);
} }
IsConnected = true; IsConnected = true;
@@ -92,16 +90,11 @@ namespace MQTTnet.Client
public async Task DisconnectAsync() public async Task DisconnectAsync()
{ {
if (!IsConnected)
{
return;
}
try try
{ {
if (!_cancellationTokenSource.IsCancellationRequested) if (IsConnected && !_cancellationTokenSource.IsCancellationRequested)
{ {
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false); await SendAsync(new MqttDisconnectPacket(), _cancellationTokenSource.Token).ConfigureAwait(false);
} }
} }
finally finally
@@ -122,7 +115,7 @@ namespace MQTTnet.Client
TopicFilters = topicFilters.ToList() TopicFilters = topicFilters.ToList()
}; };
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket).ConfigureAwait(false); var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false);
if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count) if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count)
{ {
@@ -144,7 +137,7 @@ namespace MQTTnet.Client
TopicFilters = topicFilters.ToList() TopicFilters = topicFilters.ToList()
}; };
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket).ConfigureAwait(false); await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, _cancellationTokenSource.Token).ConfigureAwait(false);
} }
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages) public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
@@ -161,7 +154,7 @@ namespace MQTTnet.Client
case MqttQualityOfServiceLevel.AtMostOnce: case MqttQualityOfServiceLevel.AtMostOnce:
{ {
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier] // No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await SendAsync(qosGroup.Cast<MqttBasePacket>().ToArray()).ConfigureAwait(false); await SendAsync(qosGroup, _cancellationTokenSource.Token).ConfigureAwait(false);
break; break;
} }
case MqttQualityOfServiceLevel.AtLeastOnce: case MqttQualityOfServiceLevel.AtLeastOnce:
@@ -169,7 +162,7 @@ namespace MQTTnet.Client
foreach (var publishPacket in qosGroup) foreach (var publishPacket in qosGroup)
{ {
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket).ConfigureAwait(false); await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
} }
break; break;
@@ -180,13 +173,13 @@ namespace MQTTnet.Client
{ {
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier(); publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNewPacketIdentifier();
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false); var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
var pubRelPacket = new MqttPubRelPacket var pubRelPacket = new MqttPubRelPacket
{ {
PacketIdentifier = pubRecPacket.PacketIdentifier PacketIdentifier = pubRecPacket.PacketIdentifier
}; };
await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket).ConfigureAwait(false); await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, _cancellationTokenSource.Token).ConfigureAwait(false);
} }
break; break;
@@ -207,7 +200,7 @@ namespace MQTTnet.Client
_adapter?.Dispose(); _adapter?.Dispose();
} }
private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage) private async Task<MqttConnAckPacket> AuthenticateAsync(MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken)
{ {
var connectPacket = new MqttConnectPacket var connectPacket = new MqttConnectPacket
{ {
@@ -219,7 +212,7 @@ namespace MQTTnet.Client
WillMessage = willApplicationMessage WillMessage = willApplicationMessage
}; };
var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket).ConfigureAwait(false); var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted) if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{ {
throw new MqttConnectingFailedException(response.ConnectReturnCode); throw new MqttConnectingFailedException(response.ConnectReturnCode);
@@ -264,21 +257,19 @@ namespace MQTTnet.Client
try try
{ {
if (_packetReceiverTask != null && _packetReceiverTask != sender) await WaitForTaskAsync(_packetReceiverTask, sender).ConfigureAwait(false);
{ await WaitForTaskAsync(_keepAliveMessageSenderTask, sender).ConfigureAwait(false);
_packetReceiverTask.Wait();
}
if (_keepAliveMessageSenderTask != null && _keepAliveMessageSenderTask != sender) if (_keepAliveMessageSenderTask != null && _keepAliveMessageSenderTask != sender)
{ {
_keepAliveMessageSenderTask.Wait(); await _keepAliveMessageSenderTask.ConfigureAwait(false);
} }
if (_adapter != null) if (_adapter != null)
{ {
await _adapter.DisconnectAsync(_options.CommunicationTimeout).ConfigureAwait(false); await _adapter.DisconnectAsync(_options.CommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
} }
_logger.Verbose<MqttClient>("Disconnected from adapter."); _logger.Verbose<MqttClient>("Disconnected from adapter.");
} }
catch (Exception adapterException) catch (Exception adapterException)
@@ -297,121 +288,63 @@ namespace MQTTnet.Client
} }
} }
private async Task ProcessReceivedPacketAsync(MqttBasePacket packet) private Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken)
{ {
try return SendAsync(new[] { packet }, cancellationToken);
{
if (packet is MqttPublishPacket publishPacket)
{
await ProcessReceivedPublishPacketAsync(publishPacket).ConfigureAwait(false);
return;
}
if (packet is MqttPingReqPacket)
{
await SendAsync(new MqttPingRespPacket()).ConfigureAwait(false);
return;
}
if (packet is MqttDisconnectPacket)
{
await DisconnectAsync().ConfigureAwait(false);
return;
}
if (packet is MqttPubRelPacket pubRelPacket)
{
await ProcessReceivedPubRelPacket(pubRelPacket).ConfigureAwait(false);
return;
}
_packetDispatcher.Dispatch(packet);
}
catch (Exception exception)
{
_logger.Error<MqttClient>(exception, "Unhandled exception while processing received packet.");
}
} }
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket) private Task SendAsync(IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken)
{ {
try if (cancellationToken.IsCancellationRequested)
{ {
var applicationMessage = publishPacket.ToApplicationMessage(); throw new TaskCanceledException();
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(_options.ClientId, applicationMessage));
}
catch (Exception exception)
{
_logger.Error<MqttClient>(exception, "Unhandled exception while handling application message.");
}
}
private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket)
{
if (_cancellationTokenSource.IsCancellationRequested)
{
return Task.FromResult(0);
} }
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
return Task.FromResult(0);
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
FireApplicationMessageReceivedEvent(publishPacket);
return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
}
throw new MqttCommunicationException("Received a not supported QoS level.");
}
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
{
var response = new MqttPubCompPacket
{
PacketIdentifier = pubRelPacket.PacketIdentifier
};
return SendAsync(response);
}
private Task SendAsync(params MqttBasePacket[] packets)
{
_sendTracker.Restart(); _sendTracker.Restart();
return _adapter.SendPacketsAsync(_options.CommunicationTimeout, _cancellationTokenSource.Token, packets); return _adapter.SendPacketsAsync(_options.CommunicationTimeout, packets, cancellationToken);
} }
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttBasePacket
{ {
ushort? identifier = null; if (cancellationToken.IsCancellationRequested)
if (requestPacket is IMqttPacketWithIdentifier requestPacketWithIdentifier)
{ {
identifier = requestPacketWithIdentifier.PacketIdentifier; throw new TaskCanceledException();
} }
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(typeof(TResponsePacket), identifier, _options.CommunicationTimeout); _sendTracker.Restart();
await SendAsync(requestPacket).ConfigureAwait(false);
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false); ushort identifier = 0;
if (requestPacket is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue)
{
identifier = packetWithIdentifier.PacketIdentifier.Value;
}
var packetAwaiter = _packetDispatcher.AddPacketAwaiter<TResponsePacket>(identifier);
try
{
await _adapter.SendPacketsAsync(_options.CommunicationTimeout, new[] { requestPacket }, cancellationToken).ConfigureAwait(false);
var respone = await Internal.TaskExtensions.TimeoutAfter(ct => packetAwaiter.Task, _options.CommunicationTimeout, cancellationToken).ConfigureAwait(false);
return (TResponsePacket)respone;
}
catch (MqttCommunicationTimedOutException)
{
_logger.Warning<MqttPacketDispatcher>($"Timeout while waiting for packet of type '{typeof(TResponsePacket).Namespace}'.");
throw;
}
finally
{
_packetDispatcher.RemovePacketAwaiter<TResponsePacket>(identifier);
}
} }
private async Task SendKeepAliveMessagesAsync() private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken)
{ {
_logger.Verbose<MqttClient>("Start sending keep alive packets."); _logger.Verbose<MqttClient>("Start sending keep alive packets.");
try try
{ {
while (!_cancellationTokenSource.Token.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75); var keepAliveSendInterval = TimeSpan.FromSeconds(_options.KeepAlivePeriod.TotalSeconds * 0.75);
if (_options.KeepAliveSendInterval.HasValue) if (_options.KeepAliveSendInterval.HasValue)
@@ -421,10 +354,10 @@ namespace MQTTnet.Client
if (_sendTracker.Elapsed > keepAliveSendInterval) if (_sendTracker.Elapsed > keepAliveSendInterval)
{ {
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false); await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket(), cancellationToken).ConfigureAwait(false);
} }
await Task.Delay(keepAliveSendInterval, _cancellationTokenSource.Token).ConfigureAwait(false); await Task.Delay(keepAliveSendInterval, cancellationToken).ConfigureAwait(false);
} }
} }
catch (Exception exception) catch (Exception exception)
@@ -440,7 +373,7 @@ namespace MQTTnet.Client
{ {
_logger.Error<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets."); _logger.Error<MqttClient>(exception, "Unhandled exception while sending/receiving keep alive packets.");
} }
await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false); await DisconnectInternalAsync(_keepAliveMessageSenderTask, exception).ConfigureAwait(false);
} }
finally finally
@@ -449,24 +382,34 @@ namespace MQTTnet.Client
} }
} }
private async Task ReceivePacketsAsync() private async Task ReceivePacketsAsync(CancellationToken cancellationToken)
{ {
_logger.Verbose<MqttClient>("Start receiving packets."); _logger.Verbose<MqttClient>("Start receiving packets.");
try try
{ {
while (!_cancellationTokenSource.Token.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
_isReceivingPackets = true; var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested)
if (_cancellationTokenSource.Token.IsCancellationRequested)
{ {
return; return;
} }
StartProcessReceivedPacket(packet); if (packet == null)
{
continue;
}
if (_options.ReceivedApplicationMessageProcessingMode == MqttReceivedApplicationMessageProcessingMode.SingleThread)
{
await ProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
}
else if (_options.ReceivedApplicationMessageProcessingMode == MqttReceivedApplicationMessageProcessingMode.DedicatedThread)
{
StartProcessReceivedPacketAsync(packet, cancellationToken);
}
} }
} }
catch (Exception exception) catch (Exception exception)
@@ -484,6 +427,7 @@ namespace MQTTnet.Client
} }
await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false); await DisconnectInternalAsync(_packetReceiverTask, exception).ConfigureAwait(false);
_packetDispatcher.Dispatch(exception);
} }
finally finally
{ {
@@ -491,26 +435,133 @@ namespace MQTTnet.Client
} }
} }
private void StartProcessReceivedPacket(MqttBasePacket packet) private async Task ProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
{ {
Task.Run(() => ProcessReceivedPacketAsync(packet), _cancellationTokenSource.Token); try
}
private async Task StartReceivingPacketsAsync()
{
_isReceivingPackets = false;
_packetReceiverTask = Task.Run(ReceivePacketsAsync, _cancellationTokenSource.Token);
while (!_isReceivingPackets && !_cancellationTokenSource.Token.IsCancellationRequested)
{ {
await Task.Delay(TimeSpan.FromMilliseconds(100), _cancellationTokenSource.Token).ConfigureAwait(false); if (packet is MqttPublishPacket publishPacket)
{
await ProcessReceivedPublishPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
return;
}
if (packet is MqttPingReqPacket)
{
await SendAsync(new MqttPingRespPacket(), cancellationToken).ConfigureAwait(false);
return;
}
if (packet is MqttDisconnectPacket)
{
await DisconnectAsync().ConfigureAwait(false);
return;
}
if (packet is MqttPubRelPacket pubRelPacket)
{
await ProcessReceivedPubRelPacket(pubRelPacket, cancellationToken).ConfigureAwait(false);
return;
}
_packetDispatcher.Dispatch(packet);
}
catch (Exception exception)
{
_logger.Error<MqttClient>(exception, "Unhandled exception while processing received packet.");
} }
} }
private void StartSendingKeepAliveMessages() private Task ProcessReceivedPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{ {
_keepAliveMessageSenderTask = Task.Run(SendKeepAliveMessagesAsync, _cancellationTokenSource.Token); if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
return Task.FromResult(0);
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, cancellationToken);
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
FireApplicationMessageReceivedEvent(publishPacket);
return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, cancellationToken);
}
throw new MqttCommunicationException("Received a not supported QoS level.");
}
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{
var response = new MqttPubCompPacket
{
PacketIdentifier = pubRelPacket.PacketIdentifier
};
return SendAsync(response, cancellationToken);
}
private void StartReceivingPackets(CancellationToken cancellationToken)
{
_packetReceiverTask = Task.Factory.StartNew(
() => ReceivePacketsAsync(cancellationToken),
cancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Current);
}
private void StartSendingKeepAliveMessages(CancellationToken cancellationToken)
{
_keepAliveMessageSenderTask = Task.Factory.StartNew(
() => SendKeepAliveMessagesAsync(cancellationToken),
cancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Current);
}
private void StartProcessReceivedPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => ProcessReceivedPacketAsync(packet, cancellationToken), cancellationToken);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
{
try
{
var applicationMessage = publishPacket.ToApplicationMessage();
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(_options.ClientId, applicationMessage));
}
catch (Exception exception)
{
_logger.Error<MqttClient>(exception, "Unhandled exception while handling application message.");
}
}
private static async Task WaitForTaskAsync(Task task, Task sender)
{
if (task == sender || task == null)
{
return;
}
if (task.IsCanceled || task.IsCompleted || task.IsFaulted)
{
return;
}
try
{
await task.ConfigureAwait(false);
}
catch (TaskCanceledException)
{
}
} }
} }
} }
@@ -5,22 +5,17 @@ namespace MQTTnet.Client
{ {
public class MqttClientOptions : IMqttClientOptions public class MqttClientOptions : IMqttClientOptions
{ {
public MqttApplicationMessage WillMessage { get; set; }
public string ClientId { get; set; } = Guid.NewGuid().ToString("N"); public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
public bool CleanSession { get; set; } = true; public bool CleanSession { get; set; } = true;
public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials(); public IMqttClientCredentials Credentials { get; set; } = new MqttClientCredentials();
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15); public IMqttClientChannelOptions ChannelOptions { get; set; }
public TimeSpan? KeepAliveSendInterval { get; set; }
public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10); public TimeSpan CommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
public TimeSpan? KeepAliveSendInterval { get; set; }
public MqttReceivedApplicationMessageProcessingMode ReceivedApplicationMessageProcessingMode { get; set; } = MqttReceivedApplicationMessageProcessingMode.SingleThread;
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; public MqttApplicationMessage WillMessage { get; set; }
public IMqttClientChannelOptions ChannelOptions { get; set; }
} }
} }
@@ -36,6 +36,12 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithKeepAliveSendInterval(TimeSpan value)
{
_options.KeepAliveSendInterval = value;
return this;
}
public MqttClientOptionsBuilder WithClientId(string value) public MqttClientOptionsBuilder WithClientId(string value)
{ {
_options.ClientId = value; _options.ClientId = value;
@@ -108,6 +114,13 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithReceivedApplicationMessageProcessingMode(
MqttReceivedApplicationMessageProcessingMode mode)
{
_options.ReceivedApplicationMessageProcessingMode = mode;
return this;
}
public IMqttClientOptions Build() public IMqttClientOptions Build()
{ {
if (_tlsOptions != null) if (_tlsOptions != null)
@@ -6,7 +6,7 @@
public int? Port { get; set; } public int? Port { get; set; }
public int BufferSize { get; set; } = 20 * 4096; public int BufferSize { get; set; } = 4096;
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
} }
@@ -13,6 +13,8 @@ namespace MQTTnet.Client
public CookieContainer CookieContainer { get; set; } public CookieContainer CookieContainer { get; set; }
public int BufferSize { get; set; } = 4096;
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions(); public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
} }
} }
@@ -1,38 +1,19 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public class MqttPacketDispatcher public class MqttPacketDispatcher
{ {
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _awaiters = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>(); private readonly ConcurrentDictionary<Tuple<ushort, Type>, TaskCompletionSource<MqttBasePacket>> _awaiters = new ConcurrentDictionary<Tuple<ushort, Type>, TaskCompletionSource<MqttBasePacket>>();
private readonly IMqttNetLogger _logger;
public void Dispatch(Exception exception)
public MqttPacketDispatcher(IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); foreach (var awaiter in _awaiters)
}
public async Task<MqttBasePacket> WaitForPacketAsync(Type responseType, ushort? identifier, TimeSpan timeout)
{
var packetAwaiter = AddPacketAwaiter(responseType, identifier);
try
{ {
return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false); awaiter.Value.SetException(exception);
}
catch (MqttCommunicationTimedOutException)
{
_logger.Warning<MqttPacketDispatcher>("Timeout while waiting for packet of type '{0}'.", responseType.Name);
throw;
}
finally
{
RemovePacketAwaiter(responseType, identifier);
} }
} }
@@ -40,21 +21,19 @@ namespace MQTTnet.Client
{ {
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
var type = packet.GetType(); ushort identifier = 0;
if (packet is IMqttPacketWithIdentifier packetWithIdentifier && packetWithIdentifier.PacketIdentifier.HasValue)
if (_awaiters.TryGetValue(type, out var byId))
{ {
ushort? identifier = 0; identifier = packetWithIdentifier.PacketIdentifier.Value;
if (packet is IMqttPacketWithIdentifier packetWithIdentifier) }
{
identifier = packetWithIdentifier.PacketIdentifier;
}
if (byId.TryRemove(identifier.Value, out var tcs)) var type = packet.GetType();
{ var key = new Tuple<ushort, Type>(identifier, type);
tcs.TrySetResult(packet);
return; if (_awaiters.TryRemove(key, out var tcs))
} {
tcs.TrySetResult(packet);
return;
} }
throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched."); throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched.");
@@ -65,7 +44,7 @@ namespace MQTTnet.Client
_awaiters.Clear(); _awaiters.Clear();
} }
private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(Type responseType, ushort? identifier) public TaskCompletionSource<MqttBasePacket> AddPacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
{ {
var tcs = new TaskCompletionSource<MqttBasePacket>(); var tcs = new TaskCompletionSource<MqttBasePacket>();
@@ -73,25 +52,25 @@ namespace MQTTnet.Client
{ {
identifier = 0; identifier = 0;
} }
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>()); var key = new Tuple<ushort, Type>(identifier ?? 0, typeof(TResponsePacket));
if (!byId.TryAdd(identifier.Value, tcs)) if (!_awaiters.TryAdd(key, tcs))
{ {
throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{responseType}' with identifier {identifier}."); throw new InvalidOperationException($"The packet dispatcher already has an awaiter for packet of type '{key.Item2.Name}' with identifier {key.Item1}.");
} }
return tcs; return tcs;
} }
private void RemovePacketAwaiter(Type responseType, ushort? identifier) public void RemovePacketAwaiter<TResponsePacket>(ushort? identifier) where TResponsePacket : MqttBasePacket
{ {
if (!identifier.HasValue) if (!identifier.HasValue)
{ {
identifier = 0; identifier = 0;
} }
var byId = _awaiters.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>()); var key = new Tuple<ushort, Type>(identifier ?? 0, typeof(TResponsePacket));
byId.TryRemove(identifier.Value, out var _); _awaiters.TryRemove(key, out var _);
} }
} }
} }
@@ -0,0 +1,8 @@
namespace MQTTnet.Client
{
public enum MqttReceivedApplicationMessageProcessingMode
{
SingleThread,
DedicatedThread
}
}
@@ -1,6 +1,11 @@
namespace MQTTnet.Exceptions using System;
namespace MQTTnet.Exceptions
{ {
public sealed class MqttCommunicationTimedOutException : MqttCommunicationException public sealed class MqttCommunicationTimedOutException : MqttCommunicationException
{ {
public MqttCommunicationTimedOutException() { }
public MqttCommunicationTimedOutException(Exception innerException) : base(innerException) { }
} }
} }
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime; using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Networking; using Windows.Networking;
using Windows.Networking.Sockets; using Windows.Networking.Sockets;
@@ -17,17 +18,20 @@ namespace MQTTnet.Implementations
{ {
// ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user. public static int BufferSize { get; set; } = 4096; // Can be changed for fine tuning by library user.
private readonly int _bufferSize = BufferSize; private readonly int _bufferSize = BufferSize;
private readonly MqttClientTcpOptions _options; private readonly MqttClientTcpOptions _options;
private StreamSocket _socket; private StreamSocket _socket;
private Stream _readStream;
private Stream _writeStream;
public MqttTcpChannel(MqttClientTcpOptions options) public MqttTcpChannel(MqttClientTcpOptions options)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_bufferSize = _options.BufferSize;
_bufferSize = options.BufferSize;
} }
public MqttTcpChannel(StreamSocket socket) public MqttTcpChannel(StreamSocket socket)
@@ -37,16 +41,15 @@ namespace MQTTnet.Implementations
CreateStreams(); CreateStreams();
} }
public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }
public static Func<MqttClientTcpOptions, IEnumerable<ChainValidationResult>> CustomIgnorableServerCertificateErrorsResolver { get; set; } public static Func<MqttClientTcpOptions, IEnumerable<ChainValidationResult>> CustomIgnorableServerCertificateErrorsResolver { get; set; }
public async Task ConnectAsync() public async Task ConnectAsync(CancellationToken cancellationToken)
{ {
if (_socket == null) if (_socket == null)
{ {
_socket = new StreamSocket(); _socket = new StreamSocket();
_socket.Control.NoDelay = true;
_socket.Control.KeepAlive = true;
} }
if (!_options.TlsOptions.UseTls) if (!_options.TlsOptions.UseTls)
@@ -74,11 +77,22 @@ namespace MQTTnet.Implementations
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _readStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
await _writeStream.WriteAsync(buffer, offset, count, cancellationToken);
await _writeStream.FlushAsync(cancellationToken);
}
public void Dispose() public void Dispose()
{ {
try try
{ {
SendStream?.Dispose(); _readStream?.Dispose();
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
@@ -88,12 +102,12 @@ namespace MQTTnet.Implementations
} }
finally finally
{ {
SendStream = null; _readStream = null;
} }
try try
{ {
ReceiveStream?.Dispose(); _writeStream?.Dispose();
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
@@ -103,7 +117,7 @@ namespace MQTTnet.Implementations
} }
finally finally
{ {
ReceiveStream = null; _writeStream = null;
} }
try try
@@ -122,12 +136,6 @@ namespace MQTTnet.Implementations
} }
} }
private void CreateStreams()
{
SendStream = _socket.OutputStream.AsStreamForWrite(_bufferSize);
ReceiveStream = _socket.InputStream.AsStreamForRead(_bufferSize);
}
private static Certificate LoadCertificate(MqttClientTcpOptions options) private static Certificate LoadCertificate(MqttClientTcpOptions options)
{ {
if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any()) if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any())
@@ -171,6 +179,12 @@ namespace MQTTnet.Implementations
return result; return result;
} }
private void CreateStreams()
{
_readStream = _socket.InputStream.AsStreamForRead(_bufferSize);
_writeStream = _socket.OutputStream.AsStreamForWrite(_bufferSize);
}
} }
} }
#endif #endif
@@ -7,6 +7,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using MQTTnet.Channel; using MQTTnet.Channel;
using MQTTnet.Client; using MQTTnet.Client;
@@ -14,20 +15,10 @@ namespace MQTTnet.Implementations
{ {
public sealed class MqttTcpChannel : IMqttChannel public sealed class MqttTcpChannel : IMqttChannel
{ {
#if NET452 || NET461
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
private readonly int _bufferSize = BufferSize;
#else
private readonly int _bufferSize = 0;
#endif
private readonly MqttClientTcpOptions _options; private readonly MqttClientTcpOptions _options;
private Socket _socket; private Socket _socket;
private SslStream _sslStream; private Stream _stream;
/// <summary> /// <summary>
/// called on client sockets are created in connect /// called on client sockets are created in connect
@@ -35,7 +26,6 @@ namespace MQTTnet.Implementations
public MqttTcpChannel(MqttClientTcpOptions options) public MqttTcpChannel(MqttClientTcpOptions options)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_bufferSize = options.BufferSize;
} }
/// <summary> /// <summary>
@@ -45,21 +35,17 @@ namespace MQTTnet.Implementations
public MqttTcpChannel(Socket socket, SslStream sslStream) public MqttTcpChannel(Socket socket, SslStream sslStream)
{ {
_socket = socket ?? throw new ArgumentNullException(nameof(socket)); _socket = socket ?? throw new ArgumentNullException(nameof(socket));
_sslStream = sslStream;
CreateStreams(); CreateStream(sslStream);
} }
public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }
public static Func<X509Certificate, X509Chain, SslPolicyErrors, MqttClientTcpOptions, bool> CustomCertificateValidationCallback { get; set; } public static Func<X509Certificate, X509Chain, SslPolicyErrors, MqttClientTcpOptions, bool> CustomCertificateValidationCallback { get; set; }
public async Task ConnectAsync() public async Task ConnectAsync(CancellationToken cancellationToken)
{ {
if (_socket == null) if (_socket == null)
{ {
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp); _socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
} }
#if NET452 || NET461 #if NET452 || NET461
@@ -68,13 +54,14 @@ namespace MQTTnet.Implementations
await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false); await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false);
#endif #endif
SslStream sslStream = null;
if (_options.TlsOptions.UseTls) if (_options.TlsOptions.UseTls)
{ {
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback); sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false); await sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
} }
CreateStreams(); CreateStream(sslStream);
} }
public Task DisconnectAsync() public Task DisconnectAsync()
@@ -83,13 +70,21 @@ namespace MQTTnet.Implementations
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _stream.ReadAsync(buffer, offset, count, cancellationToken);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _stream.WriteAsync(buffer, offset, count, cancellationToken);
}
public void Dispose() public void Dispose()
{ {
var oneStreamIsUsed = SendStream != null && ReceiveStream != null && ReferenceEquals(SendStream, ReceiveStream);
try try
{ {
SendStream?.Dispose(); _stream?.Dispose();
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
@@ -99,40 +94,7 @@ namespace MQTTnet.Implementations
} }
finally finally
{ {
SendStream = null; _stream = null;
}
try
{
if (!oneStreamIsUsed)
{
ReceiveStream?.Dispose();
}
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
ReceiveStream = null;
}
try
{
_sslStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
_sslStream = null;
} }
try try
@@ -198,25 +160,16 @@ namespace MQTTnet.Implementations
return certificates; return certificates;
} }
private void CreateStreams() private void CreateStream(Stream stream)
{ {
Stream stream; if (stream != null)
if (_sslStream != null)
{ {
stream = _sslStream; _stream = stream;
} }
else else
{ {
stream = new NetworkStream(_socket, true); _stream = new NetworkStream(_socket, true);
} }
#if NET452 || NET461
SendStream = new BufferedStream(stream, _bufferSize);
ReceiveStream = new BufferedStream(stream, _bufferSize);
#else
SendStream = stream;
ReceiveStream = stream;
#endif
} }
} }
} }
@@ -9,7 +9,7 @@ using MQTTnet.Server;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable public class MqttTcpServerAdapter : IMqttServerAdapter
{ {
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private StreamSocketListener _defaultEndpointSocket; private StreamSocketListener _defaultEndpointSocket;
@@ -26,12 +26,19 @@ namespace MQTTnet.Implementations
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));
if (_defaultEndpointSocket != null) throw new InvalidOperationException("Server is already started."); if (_defaultEndpointSocket != null) throw new InvalidOperationException("Server is already started.");
if (options.DefaultEndpointOptions.IsEnabled) if (options.DefaultEndpointOptions.IsEnabled)
{ {
_defaultEndpointSocket = new StreamSocketListener(); _defaultEndpointSocket = new StreamSocketListener();
await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket);
// This also affects the client sockets.
_defaultEndpointSocket.Control.NoDelay = true;
_defaultEndpointSocket.Control.KeepAlive = true;
_defaultEndpointSocket.Control.QualityOfService = SocketQualityOfService.LowLatency;
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync; _defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync;
await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket);
} }
if (options.TlsEndpointOptions.IsEnabled) if (options.TlsEndpointOptions.IsEnabled)
@@ -55,7 +62,7 @@ namespace MQTTnet.Implementations
public void Dispose() public void Dispose()
{ {
StopAsync(); StopAsync().GetAwaiter().GetResult();
} }
private void AcceptDefaultEndpointConnectionsAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) private void AcceptDefaultEndpointConnectionsAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
@@ -14,7 +14,7 @@ using MQTTnet.Server;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public class MqttTcpServerAdapter : IMqttServerAdapter, IDisposable public class MqttTcpServerAdapter : IMqttServerAdapter
{ {
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
@@ -38,11 +38,16 @@ namespace MQTTnet.Implementations
if (options.DefaultEndpointOptions.IsEnabled) if (options.DefaultEndpointOptions.IsEnabled)
{ {
_defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); _defaultEndpointSocket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
_defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort())); _defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort()));
_defaultEndpointSocket.Listen(options.ConnectionBacklog); _defaultEndpointSocket.Listen(options.ConnectionBacklog);
Task.Run(async () => await AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false); Task.Factory.StartNew(
() => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token),
_cancellationTokenSource.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Current);
} }
if (options.TlsEndpointOptions.IsEnabled) if (options.TlsEndpointOptions.IsEnabled)
@@ -62,7 +67,11 @@ namespace MQTTnet.Implementations
_tlsEndpointSocket.Bind(new IPEndPoint(options.TlsEndpointOptions.BoundIPAddress, options.GetTlsEndpointPort())); _tlsEndpointSocket.Bind(new IPEndPoint(options.TlsEndpointOptions.BoundIPAddress, options.GetTlsEndpointPort()));
_tlsEndpointSocket.Listen(options.ConnectionBacklog); _tlsEndpointSocket.Listen(options.ConnectionBacklog);
Task.Run(async () => await AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false); Task.Factory.StartNew(
() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token),
_cancellationTokenSource.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Current);
} }
return Task.FromResult(0); return Task.FromResult(0);
@@ -78,7 +87,7 @@ namespace MQTTnet.Implementations
_defaultEndpointSocket = null; _defaultEndpointSocket = null;
_tlsCertificate = null; _tlsCertificate = null;
_tlsEndpointSocket?.Dispose(); _tlsEndpointSocket?.Dispose();
_tlsEndpointSocket = null; _tlsEndpointSocket = null;
@@ -87,7 +96,7 @@ namespace MQTTnet.Implementations
public void Dispose() public void Dispose()
{ {
StopAsync(); StopAsync().GetAwaiter().GetResult();
} }
private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken) private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken)
@@ -102,6 +111,7 @@ namespace MQTTnet.Implementations
#else #else
var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false); var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false);
#endif #endif
clientSocket.NoDelay = true;
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger); var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer(), _logger);
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
@@ -137,7 +147,7 @@ namespace MQTTnet.Implementations
var sslStream = new SslStream(new NetworkStream(clientSocket)); var sslStream = new SslStream(new NetworkStream(clientSocket));
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false); await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger); var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer(), _logger);
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter)); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
} }
@@ -1,5 +1,4 @@
using System; using System;
using System.IO;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
@@ -11,22 +10,22 @@ namespace MQTTnet.Implementations
{ {
public sealed class MqttWebSocketChannel : IMqttChannel public sealed class MqttWebSocketChannel : IMqttChannel
{ {
// ReSharper disable once MemberCanBePrivate.Global private readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
private readonly MqttClientWebSocketOptions _options; private readonly MqttClientWebSocketOptions _options;
private ClientWebSocket _webSocket;
private WebSocket _webSocket;
public MqttWebSocketChannel(MqttClientWebSocketOptions options) public MqttWebSocketChannel(MqttClientWebSocketOptions options)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
} }
public Stream SendStream { get; private set; } public MqttWebSocketChannel(WebSocket webSocket)
public Stream ReceiveStream { get; private set; } {
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
}
public async Task ConnectAsync() public async Task ConnectAsync(CancellationToken cancellationToken)
{ {
var uri = _options.Uri; var uri = _options.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase)) if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
@@ -41,42 +40,40 @@ namespace MQTTnet.Implementations
} }
} }
_webSocket = new ClientWebSocket(); var clientWebSocket = new ClientWebSocket();
if (_options.RequestHeaders != null) if (_options.RequestHeaders != null)
{ {
foreach (var requestHeader in _options.RequestHeaders) foreach (var requestHeader in _options.RequestHeaders)
{ {
_webSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value); clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
} }
} }
if (_options.SubProtocols != null) if (_options.SubProtocols != null)
{ {
foreach (var subProtocol in _options.SubProtocols) foreach (var subProtocol in _options.SubProtocols)
{ {
_webSocket.Options.AddSubProtocol(subProtocol); clientWebSocket.Options.AddSubProtocol(subProtocol);
} }
} }
if (_options.CookieContainer != null) if (_options.CookieContainer != null)
{ {
_webSocket.Options.Cookies = _options.CookieContainer; clientWebSocket.Options.Cookies = _options.CookieContainer;
} }
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null) if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
{ {
_webSocket.Options.ClientCertificates = new X509CertificateCollection(); clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
foreach (var certificate in _options.TlsOptions.Certificates) foreach (var certificate in _options.TlsOptions.Certificates)
{ {
_webSocket.Options.ClientCertificates.Add(new X509Certificate(certificate)); clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
} }
} }
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false); await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false);
_webSocket = clientWebSocket;
SendStream = new WebSocketStream(_webSocket);
ReceiveStream = SendStream;
} }
public async Task DisconnectAsync() public async Task DisconnectAsync()
@@ -94,8 +91,32 @@ namespace MQTTnet.Implementations
Dispose(); Dispose();
} }
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count), cancellationToken).ConfigureAwait(false);
return response.Count;
}
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
// This lock is required because the client will throw an exception if _SendAsync_ is
// called from multiple threads at the same time. But this issue only happens with several
// framework versions.
await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
await _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken).ConfigureAwait(false);
}
finally
{
_sendLock.Release();
}
}
public void Dispose() public void Dispose()
{ {
_sendLock?.Dispose();
try try
{ {
_webSocket?.Dispose(); _webSocket?.Dispose();
@@ -106,7 +127,7 @@ namespace MQTTnet.Implementations
finally finally
{ {
_webSocket = null; _webSocket = null;
} }
} }
} }
} }
@@ -1,137 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Exceptions;
namespace MQTTnet.Implementations
{
public class WebSocketStream : Stream
{
private readonly byte[] _chunckBuffer = new byte[MqttWebSocketChannel.BufferSize];
private readonly Queue<byte> _buffer = new Queue<byte>(MqttWebSocketChannel.BufferSize);
private readonly WebSocket _webSocket;
public WebSocketStream(WebSocket webSocket)
{
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush()
{
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var bytesRead = 0;
// Use existing date from buffer.
while (count > 0 && _buffer.Any())
{
buffer[offset] = _buffer.Dequeue();
count--;
bytesRead++;
offset++;
}
if (count == 0)
{
return bytesRead;
}
// Fetch new data if the buffer is not full.
while (_webSocket.State == WebSocketState.Open)
{
await FetchChunkAsync(cancellationToken).ConfigureAwait(false);
while (count > 0 && _buffer.Any())
{
buffer[offset] = _buffer.Dequeue();
count--;
bytesRead++;
offset++;
}
if (count == 0)
{
return bytesRead;
}
}
if (_webSocket.State == WebSocketState.Closed)
{
throw new MqttCommunicationException("WebSocket connection closed.");
}
return bytesRead;
}
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
private async Task FetchChunkAsync(CancellationToken cancellationToken)
{
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false);
if (response.MessageType == WebSocketMessageType.Close)
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
}
else if (response.MessageType == WebSocketMessageType.Binary)
{
for (var i = 0; i < response.Count; i++)
{
_buffer.Enqueue(_chunckBuffer[i]);
}
}
else if (response.MessageType == WebSocketMessageType.Text)
{
throw new MqttProtocolViolationException("WebSocket channel received TEXT message.");
}
}
}
}
@@ -0,0 +1,26 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Internal
{
public sealed class AsyncAutoResetEvent : IDisposable
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0, 1);
public Task WaitOneAsync(CancellationToken cancellationToken)
{
return _semaphore.WaitAsync(cancellationToken);
}
public void Set()
{
_semaphore.Release();
}
public void Dispose()
{
_semaphore?.Dispose();
}
}
}
@@ -0,0 +1,26 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Internal
{
public sealed class AsyncLock : IDisposable
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public Task EnterAsync(CancellationToken cancellationToken)
{
return _semaphore.WaitAsync(cancellationToken);
}
public void Exit()
{
_semaphore.Release();
}
public void Dispose()
{
_semaphore?.Dispose();
}
}
}
@@ -2,7 +2,7 @@
namespace MQTTnet.Internal namespace MQTTnet.Internal
{ {
internal static class MqttApplicationMessageExtensions public static class MqttApplicationMessageExtensions
{ {
public static MqttApplicationMessage ToApplicationMessage(this MqttPublishPacket publishPacket) public static MqttApplicationMessage ToApplicationMessage(this MqttPublishPacket publishPacket)
{ {
@@ -7,72 +7,52 @@ namespace MQTTnet.Internal
{ {
public static class TaskExtensions public static class TaskExtensions
{ {
public static async Task TimeoutAfter(this Task task, TimeSpan timeout) public static async Task TimeoutAfter(Func<CancellationToken, Task> action, TimeSpan timeout, CancellationToken cancellationToken)
{ {
if (task == null) throw new ArgumentNullException(nameof(task)); if (action == null) throw new ArgumentNullException(nameof(action));
using (var timeoutCts = new CancellationTokenSource()) using (var timeoutCts = new CancellationTokenSource(timeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
{ {
try try
{ {
var timeoutTask = Task.Delay(timeout, timeoutCts.Token); await action(linkedCts.Token).ConfigureAwait(false);
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
if (finishedTask == timeoutTask)
{
throw new MqttCommunicationTimedOutException();
}
if (task.IsCanceled)
{
throw new TaskCanceledException();
}
if (task.IsFaulted)
{
throw new MqttCommunicationException(task.Exception?.GetBaseException());
}
} }
finally catch (OperationCanceledException exception)
{ {
timeoutCts.Cancel(); var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
if (timeoutReached)
{
throw new MqttCommunicationTimedOutException(exception);
}
throw;
} }
} }
} }
public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout) public static async Task<TResult> TimeoutAfter<TResult>(Func<CancellationToken, Task<TResult>> action, TimeSpan timeout, CancellationToken cancellationToken)
{ {
if (task == null) throw new ArgumentNullException(nameof(task)); if (action == null) throw new ArgumentNullException(nameof(action));
using (var timeoutCts = new CancellationTokenSource()) using (var timeoutCts = new CancellationTokenSource(timeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
{ {
try try
{ {
var timeoutTask = Task.Delay(timeout, timeoutCts.Token); return await action(linkedCts.Token).ConfigureAwait(false);
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
if (finishedTask == timeoutTask)
{
throw new MqttCommunicationTimedOutException();
}
if (task.IsCanceled)
{
throw new TaskCanceledException();
}
if (task.IsFaulted)
{
throw new MqttCommunicationException(task.Exception.GetBaseException());
}
return task.Result;
} }
finally catch (OperationCanceledException exception)
{ {
timeoutCts.Cancel(); var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
if (timeoutReached)
{
throw new MqttCommunicationTimedOutException(exception);
}
throw;
} }
} }
} }
} }
} }
@@ -0,0 +1,41 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
namespace MQTTnet.Core.Internal
{
public class TestMqttChannel : IMqttChannel
{
private readonly MemoryStream _stream;
public TestMqttChannel(MemoryStream stream)
{
_stream = stream;
}
public void Dispose()
{
}
public Task ConnectAsync(CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
public Task DisconnectAsync()
{
return Task.FromResult(0);
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _stream.ReadAsync(buffer, offset, count, cancellationToken);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _stream.WriteAsync(buffer, offset, count, cancellationToken);
}
}
}
@@ -6,7 +6,6 @@
<AssemblyName>MQTTnet</AssemblyName> <AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace> <RootNamespace>MQTTnet</RootNamespace>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> <GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<DebugType>Full</DebugType>
<AssemblyVersion>0.0.0.0</AssemblyVersion> <AssemblyVersion>0.0.0.0</AssemblyVersion>
<FileVersion>0.0.0.0</FileVersion> <FileVersion>0.0.0.0</FileVersion>
<Version>0.0.0.0</Version> <Version>0.0.0.0</Version>
@@ -19,7 +18,7 @@
<DelaySign>false</DelaySign> <DelaySign>false</DelaySign>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'"> <PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'">
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker> <NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier> <TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
@@ -32,6 +31,10 @@
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> <LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DebugType>Full</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
@@ -43,17 +46,17 @@
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'"> <ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="System.Net.Security" Version="4.3.2" /> <PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" /> <PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" /> <PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'"> <ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
<PackageReference Include="System.Net.Security" Version="4.3.2" /> <PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" /> <PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" /> <PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'"> <ItemGroup Condition="'$(TargetFramework)'=='uap10.0'">
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="5.4.1" /> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.0.8" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net452'"> <ItemGroup Condition="'$(TargetFramework)'=='net452'">
@@ -71,7 +71,7 @@ namespace MQTTnet.ManagedClient
_connectionCancellationToken = new CancellationTokenSource(); _connectionCancellationToken = new CancellationTokenSource();
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(async () => await MaintainConnectionAsync(_connectionCancellationToken.Token).ConfigureAwait(false), _connectionCancellationToken.Token).ConfigureAwait(false); Task.Run(() => MaintainConnectionAsync(_connectionCancellationToken.Token), _connectionCancellationToken.Token);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
_logger.Info<ManagedMqttClient>("Started"); _logger.Info<ManagedMqttClient>("Started");
@@ -190,10 +190,7 @@ namespace MQTTnet.ManagedClient
if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed) if (connectionState == ReconnectionResult.Reconnected || _subscriptionsNotPushed)
{ {
await SynchronizeSubscriptionsAsync().ConfigureAwait(false); await SynchronizeSubscriptionsAsync().ConfigureAwait(false);
StartPublishing(); StartPublishing();
return; return;
} }
@@ -375,7 +372,7 @@ namespace MQTTnet.ManagedClient
_publishingCancellationToken = cts; _publishingCancellationToken = cts;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(async () => await PublishQueuedMessagesAsync(cts.Token).ConfigureAwait(false), cts.Token).ConfigureAwait(false); Task.Run(() => PublishQueuedMessagesAsync(cts.Token), cts.Token);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
} }
@@ -32,7 +32,12 @@ namespace MQTTnet
return this; return this;
} }
public MqttApplicationMessageBuilder WithPayload(MemoryStream payload) public MqttApplicationMessageBuilder WithPayload(Stream payload)
{
return WithPayload(payload, payload.Length - payload.Position);
}
public MqttApplicationMessageBuilder WithPayload(Stream payload, long length)
{ {
if (payload == null) if (payload == null)
{ {
@@ -46,7 +51,7 @@ namespace MQTTnet
} }
else else
{ {
_payload = new byte[payload.Length - payload.Position]; _payload = new byte[length];
payload.Read(_payload, 0, _payload.Length); payload.Read(_payload, 0, _payload.Length);
} }
@@ -11,7 +11,7 @@ namespace MQTTnet.Packets
public override string ToString() public override string ToString()
{ {
var topicFiltersText = string.Join(",", TopicFilters); var topicFiltersText = string.Join(",", TopicFilters);
return "Subscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]"; return "Unsubscribe: [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
} }
} }
} }
@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using MQTTnet.Packets; using MQTTnet.Packets;
@@ -9,8 +8,8 @@ namespace MQTTnet.Serializer
{ {
MqttProtocolVersion ProtocolVersion { get; set; } MqttProtocolVersion ProtocolVersion { get; set; }
ICollection<ArraySegment<byte>> Serialize(MqttBasePacket mqttPacket); ArraySegment<byte> Serialize(MqttBasePacket mqttPacket);
MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body); MqttBasePacket Deserialize(MqttPacketHeader header, Stream body);
} }
} }
@@ -1,9 +1,9 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -13,7 +13,7 @@ namespace MQTTnet.Serializer
public sealed class MqttPacketReader : BinaryReader public sealed class MqttPacketReader : BinaryReader
{ {
private readonly MqttPacketHeader _header; private readonly MqttPacketHeader _header;
public MqttPacketReader(MqttPacketHeader header, Stream bodyStream) public MqttPacketReader(MqttPacketHeader header, Stream bodyStream)
: base(bodyStream, Encoding.UTF8, true) : base(bodyStream, Encoding.UTF8, true)
{ {
@@ -22,7 +22,7 @@ namespace MQTTnet.Serializer
public bool EndOfRemainingData => BaseStream.Position == _header.BodyLength; public bool EndOfRemainingData => BaseStream.Position == _header.BodyLength;
public static async Task<MqttPacketHeader> ReadHeaderAsync(Stream stream, CancellationToken cancellationToken) public static async Task<MqttPacketHeader> ReadHeaderAsync(IMqttChannel stream, CancellationToken cancellationToken)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@@ -33,7 +33,7 @@ namespace MQTTnet.Serializer
// some large delay and thus the thread should be put back to the pool (await). So ReadByte() // some large delay and thus the thread should be put back to the pool (await). So ReadByte()
// is not an option here. // is not an option here.
var buffer = new byte[1]; var buffer = new byte[1];
var readCount = await stream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false); var readCount = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
if (readCount <= 0) if (readCount <= 0)
{ {
return null; return null;
@@ -89,15 +89,14 @@ namespace MQTTnet.Serializer
return ReadBytes(_header.BodyLength - (int)BaseStream.Position); return ReadBytes(_header.BodyLength - (int)BaseStream.Position);
} }
private static async Task<int> ReadBodyLengthAsync(Stream stream, CancellationToken cancellationToken) private static async Task<int> ReadBodyLengthAsync(IMqttChannel stream, CancellationToken cancellationToken)
{ {
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html. // Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
var multiplier = 1; var multiplier = 1;
var value = 0; var value = 0;
byte encodedByte; int encodedByte;
var buffer = new byte[1]; var buffer = new byte[1];
var readBytes = new List<byte>();
do do
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
@@ -112,12 +111,11 @@ namespace MQTTnet.Serializer
} }
encodedByte = buffer[0]; encodedByte = buffer[0];
readBytes.Add(encodedByte);
value += (byte)(encodedByte & 127) * multiplier; value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128) if (multiplier > 128 * 128 * 128)
{ {
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", readBytes)})."); throw new MqttProtocolViolationException("Remaining length is invalid.");
} }
multiplier *= 128; multiplier *= 128;
@@ -2,7 +2,6 @@
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -12,37 +11,42 @@ namespace MQTTnet.Serializer
public sealed class MqttPacketSerializer : IMqttPacketSerializer public sealed class MqttPacketSerializer : IMqttPacketSerializer
{ {
private static byte[] ProtocolVersionV311Name { get; } = Encoding.UTF8.GetBytes("MQTT"); private static byte[] ProtocolVersionV311Name { get; } = Encoding.UTF8.GetBytes("MQTT");
private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIs"); private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIsdp");
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
public ICollection<ArraySegment<byte>> Serialize(MqttBasePacket packet) public ArraySegment<byte> Serialize(MqttBasePacket packet)
{ {
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
using (var stream = new MemoryStream(128)) using (var stream = new MemoryStream(128))
using (var writer = new MqttPacketWriter(stream)) using (var writer = new MqttPacketWriter(stream))
{ {
// Leave enough head space for max header size (fixed + 4 variable remaining length)
stream.Position = 5;
var fixedHeader = SerializePacket(packet, writer); var fixedHeader = SerializePacket(packet, writer);
var remainingLength = (int)stream.Length;
stream.Position = 1;
var remainingLength = MqttPacketWriter.EncodeRemainingLength((int)stream.Length - 5, stream);
var headerSize = remainingLength + 1;
var headerOffset = 5 - headerSize;
// Position cursor on correct offset on beginining of array (has leading 0x0)
stream.Position = headerOffset;
writer.Write(fixedHeader); writer.Write(fixedHeader);
MqttPacketWriter.WriteRemainingLength(remainingLength, writer);
var headerLength = (int)stream.Length - remainingLength;
#if NET461 || NET452 || NETSTANDARD2_0 #if NET461 || NET452 || NETSTANDARD2_0
var buffer = stream.GetBuffer(); var buffer = stream.GetBuffer();
#else #else
var buffer = stream.ToArray(); var buffer = stream.ToArray();
#endif #endif
return new List<ArraySegment<byte>> return new ArraySegment<byte>(buffer, headerOffset, (int)stream.Length - headerOffset);
{
new ArraySegment<byte>(buffer, remainingLength, headerLength),
new ArraySegment<byte>(buffer, 0, remainingLength)
};
} }
} }
public MqttBasePacket Deserialize(MqttPacketHeader header, MemoryStream body) public MqttBasePacket Deserialize(MqttPacketHeader header, Stream body)
{ {
if (header == null) throw new ArgumentNullException(nameof(header)); if (header == null) throw new ArgumentNullException(nameof(header));
if (body == null) throw new ArgumentNullException(nameof(body)); if (body == null) throw new ArgumentNullException(nameof(body));
@@ -178,7 +182,7 @@ namespace MQTTnet.Serializer
var topic = reader.ReadStringWithLengthPrefix(); var topic = reader.ReadStringWithLengthPrefix();
ushort packetIdentifier = 0; ushort? packetIdentifier = null;
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{ {
packetIdentifier = reader.ReadUInt16(); packetIdentifier = reader.ReadUInt16();
@@ -186,12 +190,12 @@ namespace MQTTnet.Serializer
var packet = new MqttPublishPacket var packet = new MqttPublishPacket
{ {
PacketIdentifier = packetIdentifier,
Retain = retain, Retain = retain,
QualityOfServiceLevel = qualityOfServiceLevel,
Dup = dup,
Topic = topic, Topic = topic,
Payload = reader.ReadRemainingData(), Payload = reader.ReadRemainingData(),
PacketIdentifier = packetIdentifier QualityOfServiceLevel = qualityOfServiceLevel,
Dup = dup
}; };
return packet; return packet;
@@ -199,22 +203,30 @@ namespace MQTTnet.Serializer
private static MqttBasePacket DeserializeConnect(MqttPacketReader reader) private static MqttBasePacket DeserializeConnect(MqttPacketReader reader)
{ {
reader.ReadBytes(2); // Skip 2 bytes reader.ReadBytes(2); // Skip 2 bytes for header and remaining length.
MqttProtocolVersion protocolVersion; MqttProtocolVersion protocolVersion;
var protocolName = reader.ReadBytes(4); var protocolName = reader.ReadBytes(4);
if (protocolName.SequenceEqual(ProtocolVersionV310Name))
{ if (protocolName.SequenceEqual(ProtocolVersionV311Name))
reader.ReadBytes(2);
protocolVersion = MqttProtocolVersion.V310;
}
else if (protocolName.SequenceEqual(ProtocolVersionV311Name))
{ {
protocolVersion = MqttProtocolVersion.V311; protocolVersion = MqttProtocolVersion.V311;
} }
else else
{ {
throw new MqttProtocolViolationException("Protocol name is not supported."); var buffer = new byte[6];
Array.Copy(protocolName, buffer, 4);
protocolName = reader.ReadBytes(2);
Array.Copy(protocolName, 0, buffer, 4, 2);
if (protocolName.SequenceEqual(ProtocolVersionV310Name))
{
protocolVersion = MqttProtocolVersion.V310;
}
else
{
throw new MqttProtocolViolationException("Protocol name is not supported.");
}
} }
reader.ReadByte(); // Skip protocol level reader.ReadByte(); // Skip protocol level
@@ -293,7 +305,7 @@ namespace MQTTnet.Serializer
return packet; return packet;
} }
private static void ValidateConnectPacket(MqttConnectPacket packet) private static void ValidateConnectPacket(MqttConnectPacket packet)
{ {
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
@@ -319,16 +331,15 @@ namespace MQTTnet.Serializer
ValidateConnectPacket(packet); ValidateConnectPacket(packet);
// Write variable header // Write variable header
writer.Write(0x00, 0x04); // 3.1.2.1 Protocol Name
if (ProtocolVersion == MqttProtocolVersion.V311) if (ProtocolVersion == MqttProtocolVersion.V311)
{ {
writer.Write(ProtocolVersionV311Name); writer.WriteWithLengthPrefix(ProtocolVersionV311Name);
writer.Write(0x04); // 3.1.2.2 Protocol Level (4) writer.Write(0x04); // 3.1.2.2 Protocol Level 4
} }
else else
{ {
writer.Write(ProtocolVersionV310Name); writer.WriteWithLengthPrefix(ProtocolVersionV310Name);
writer.Write(0x64, 0x70, 0x03); // Protocol Level (0x03) writer.Write(0x03); // Protocol Level 3
} }
var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags
@@ -347,6 +358,11 @@ namespace MQTTnet.Serializer
connectFlags.Write(false); connectFlags.Write(false);
} }
if (packet.Password != null && packet.Username == null)
{
throw new MqttProtocolViolationException("If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22].");
}
connectFlags.Write(packet.Password != null); connectFlags.Write(packet.Password != null);
connectFlags.Write(packet.Username != null); connectFlags.Write(packet.Username != null);
@@ -55,14 +55,20 @@ namespace MQTTnet.Serializer
Write(value); Write(value);
} }
public static void WriteRemainingLength(int length, BinaryWriter target) public static int EncodeRemainingLength(int length, MemoryStream stream)
{ {
if (length == 0) // write the encoded remaining length right aligned on the 4 byte buffer
if (length <= 0)
{ {
target.Write((byte)0); stream.Seek(3, SeekOrigin.Current);
return; stream.WriteByte(0);
return 1;
} }
var buffer = new byte[4];
var offset = 0;
// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html. // Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
var x = length; var x = length;
do do
@@ -74,8 +80,15 @@ namespace MQTTnet.Serializer
encodedByte = encodedByte | 128; encodedByte = encodedByte | 128;
} }
target.Write((byte)encodedByte); buffer[offset] = (byte)encodedByte;
offset++;
} while (x > 0); } while (x > 0);
stream.Seek(4 - offset, SeekOrigin.Current);
stream.Write(buffer, 0, offset);
return offset;
} }
} }
} }
@@ -3,11 +3,15 @@ using MQTTnet.Serializer;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
// TODO: Rename to "RegisteredClient"
// TODO: Add IsConnected
// TODO: Add interface
public class ConnectedMqttClient public class ConnectedMqttClient
{ {
public string ClientId { get; set; } public string ClientId { get; set; }
public MqttProtocolVersion ProtocolVersion { get; set; } public MqttProtocolVersion? ProtocolVersion { get; set; }
public TimeSpan LastPacketReceived { get; set; } public TimeSpan LastPacketReceived { get; set; }
@@ -0,0 +1,8 @@
namespace MQTTnet.Server
{
public enum MqttClientDisconnectType
{
Clean,
NotClean
}
}
@@ -13,12 +13,12 @@ namespace MQTTnet.Server
private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch(); private readonly Stopwatch _lastNonKeepAlivePacketReceivedTracker = new Stopwatch();
private readonly string _clientId; private readonly string _clientId;
private readonly Func<Task> _timeoutCallback; private readonly Action _timeoutCallback;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
private Task _workerTask; private Task _workerTask;
public MqttClientKeepAliveMonitor(string clientId, Func<Task> timeoutCallback, IMqttNetLogger logger) public MqttClientKeepAliveMonitor(string clientId, Action timeoutCallback, IMqttNetLogger logger)
{ {
_clientId = clientId; _clientId = clientId;
_timeoutCallback = timeoutCallback; _timeoutCallback = timeoutCallback;
@@ -61,10 +61,7 @@ namespace MQTTnet.Server
{ {
_logger.Warning<MqttClientSession>("Client '{0}': Did not receive any packet or keep alive signal.", _clientId); _logger.Warning<MqttClientSession>("Client '{0}': Did not receive any packet or keep alive signal.", _clientId);
if (_timeoutCallback != null) _timeoutCallback?.Invoke();
{
await _timeoutCallback().ConfigureAwait(false);
}
return; return;
} }
@@ -1,11 +1,11 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -14,7 +14,7 @@ namespace MQTTnet.Server
public sealed class MqttClientPendingMessagesQueue : IDisposable public sealed class MqttClientPendingMessagesQueue : IDisposable
{ {
private readonly ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>(); private readonly ConcurrentQueue<MqttBasePacket> _queue = new ConcurrentQueue<MqttBasePacket>();
private readonly SemaphoreSlim _queueWaitSemaphore = new SemaphoreSlim(0); private readonly AsyncAutoResetEvent _queueAutoResetEvent = new AsyncAutoResetEvent();
private readonly IMqttServerOptions _options; private readonly IMqttServerOptions _options;
private readonly MqttClientSession _clientSession; private readonly MqttClientSession _clientSession;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
@@ -66,7 +66,7 @@ namespace MQTTnet.Server
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
_queue.Enqueue(packet); _queue.Enqueue(packet);
_queueWaitSemaphore.Release(); _queueAutoResetEvent.Set();
_logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _clientSession.ClientId); _logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _clientSession.ClientId);
} }
@@ -94,7 +94,7 @@ namespace MQTTnet.Server
MqttBasePacket packet = null; MqttBasePacket packet = null;
try try
{ {
await _queueWaitSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); await _queueAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false);
if (!_queue.TryDequeue(out packet)) if (!_queue.TryDequeue(out packet))
{ {
throw new InvalidOperationException(); // should not happen throw new InvalidOperationException(); // should not happen
@@ -105,7 +105,7 @@ namespace MQTTnet.Server
return; return;
} }
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { packet }).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { packet }, cancellationToken).ConfigureAwait(false);
_logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId); _logger.Verbose<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _clientSession.ClientId);
} }
@@ -132,21 +132,21 @@ namespace MQTTnet.Server
if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{ {
publishPacket.Dup = true; publishPacket.Dup = true;
_queue.Enqueue(packet);
_queueWaitSemaphore.Release(); Enqueue(publishPacket);
} }
} }
if (!cancellationToken.IsCancellationRequested) if (!cancellationToken.IsCancellationRequested)
{ {
await _clientSession.StopAsync().ConfigureAwait(false); _clientSession.Stop(MqttClientDisconnectType.NotClean);
} }
} }
} }
public void Dispose() public void Dispose()
{ {
_queueWaitSemaphore?.Dispose(); _queueAutoResetEvent?.Dispose();
} }
} }
} }
@@ -16,11 +16,11 @@ namespace MQTTnet.Server
public sealed class MqttClientSession : IDisposable public sealed class MqttClientSession : IDisposable
{ {
private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); private readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
private readonly IMqttServerOptions _options;
private readonly IMqttNetLogger _logger;
private readonly MqttRetainedMessagesManager _retainedMessagesManager; private readonly MqttRetainedMessagesManager _retainedMessagesManager;
private readonly IMqttNetLogger _logger;
private readonly IMqttServerOptions _options;
private readonly MqttClientSessionsManager _sessionsManager;
private IMqttChannelAdapter _adapter;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private MqttApplicationMessage _willMessage; private MqttApplicationMessage _willMessage;
private bool _wasCleanDisconnect; private bool _wasCleanDisconnect;
@@ -28,22 +28,22 @@ namespace MQTTnet.Server
public MqttClientSession( public MqttClientSession(
string clientId, string clientId,
IMqttServerOptions options, IMqttServerOptions options,
MqttClientSessionsManager sessionsManager,
MqttRetainedMessagesManager retainedMessagesManager, MqttRetainedMessagesManager retainedMessagesManager,
IMqttNetLogger logger) IMqttNetLogger logger)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_sessionsManager = sessionsManager;
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
ClientId = clientId; ClientId = clientId;
KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeoutAsync, _logger); KeepAliveMonitor = new MqttClientKeepAliveMonitor(clientId, StopDueToKeepAliveTimeout, _logger);
SubscriptionsManager = new MqttClientSubscriptionsManager(_options, clientId); SubscriptionsManager = new MqttClientSubscriptionsManager(clientId, _options, sessionsManager.Server);
PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger); PendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
} }
public Func<MqttClientSession, MqttApplicationMessage, Task> ApplicationMessageReceivedCallback { get; set; }
public MqttClientSubscriptionsManager SubscriptionsManager { get; } public MqttClientSubscriptionsManager SubscriptionsManager { get; }
public MqttClientPendingMessagesQueue PendingMessagesQueue { get; } public MqttClientPendingMessagesQueue PendingMessagesQueue { get; }
@@ -52,9 +52,9 @@ namespace MQTTnet.Server
public string ClientId { get; } public string ClientId { get; }
public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion; public MqttProtocolVersion? ProtocolVersion { get; private set; }
public bool IsConnected => _adapter != null; public bool IsConnected { get; private set; }
public async Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter) public async Task<bool> RunAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter adapter)
{ {
@@ -63,34 +63,45 @@ namespace MQTTnet.Server
try try
{ {
var cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
_wasCleanDisconnect = false; _wasCleanDisconnect = false;
_willMessage = connectPacket.WillMessage; _willMessage = connectPacket.WillMessage;
_adapter = adapter;
_cancellationTokenSource = cancellationTokenSource;
PendingMessagesQueue.Start(adapter, cancellationTokenSource.Token); IsConnected = true;
KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, cancellationTokenSource.Token); ProtocolVersion = adapter.PacketSerializer.ProtocolVersion;
await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false); PendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token);
KeepAliveMonitor.Start(connectPacket.KeepAlivePeriod, _cancellationTokenSource.Token);
await ReceivePacketsAsync(adapter, _cancellationTokenSource.Token).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
} }
catch (MqttCommunicationException exception) catch (MqttCommunicationException exception)
{ {
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId); _logger.Warning<MqttClientSession>(exception,
"Client '{0}': Communication exception while processing client packets.", ClientId);
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); _logger.Error<MqttClientSession>(exception,
"Client '{0}': Unhandled exception while processing client packets.", ClientId);
}
finally
{
ProtocolVersion = null;
IsConnected = false;
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
} }
return _wasCleanDisconnect; return _wasCleanDisconnect;
} }
public async Task StopAsync(bool wasCleanDisconnect = false) public void Stop(MqttClientDisconnectType type)
{ {
try try
{ {
@@ -99,37 +110,31 @@ namespace MQTTnet.Server
return; return;
} }
_wasCleanDisconnect = wasCleanDisconnect; _wasCleanDisconnect = type == MqttClientDisconnectType.Clean;
_cancellationTokenSource?.Cancel(false); _cancellationTokenSource?.Cancel(false);
PendingMessagesQueue.WaitForCompletion(); PendingMessagesQueue.WaitForCompletion();
KeepAliveMonitor.WaitForCompletion(); KeepAliveMonitor.WaitForCompletion();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_adapter = null;
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
}
finally
{
var willMessage = _willMessage; var willMessage = _willMessage;
_willMessage = null; // clear willmessage so it is send just once _willMessage = null; // clear willmessage so it is send just once
if (willMessage != null && !wasCleanDisconnect) if (willMessage != null && !_wasCleanDisconnect)
{ {
await ApplicationMessageReceivedCallback(this, willMessage).ConfigureAwait(false); _sessionsManager.StartDispatchApplicationMessage(this, willMessage);
} }
} }
finally
{
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
}
} }
public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage) public async Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage)
{ {
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage); var result = await SubscriptionsManager.CheckSubscriptionsAsync(applicationMessage).ConfigureAwait(false);
if (!result.IsSubscribed) if (!result.IsSubscribed)
{ {
return; return;
@@ -153,10 +158,10 @@ namespace MQTTnet.Server
{ {
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
await SubscriptionsManager.SubscribeAsync(new MqttSubscribePacket SubscriptionsManager.Subscribe(new MqttSubscribePacket
{ {
TopicFilters = topicFilters TopicFilters = topicFilters
}).ConfigureAwait(false); });
await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false); await EnqueueSubscribedRetainedMessagesAsync(topicFilters).ConfigureAwait(false);
} }
@@ -165,26 +170,26 @@ namespace MQTTnet.Server
{ {
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return SubscriptionsManager.UnsubscribeAsync(new MqttUnsubscribePacket SubscriptionsManager.Unsubscribe(new MqttUnsubscribePacket
{ {
TopicFilters = topicFilters TopicFilters = topicFilters
}); });
return Task.FromResult(0);
} }
public void Dispose() public void Dispose()
{ {
ApplicationMessageReceivedCallback = null;
SubscriptionsManager?.Dispose(); SubscriptionsManager?.Dispose();
PendingMessagesQueue?.Dispose(); PendingMessagesQueue?.Dispose();
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
} }
private Task StopDueToKeepAliveTimeoutAsync() private void StopDueToKeepAliveTimeout()
{ {
_logger.Info<MqttClientSession>("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId); _logger.Info<MqttClientSession>("Client '{0}': Timeout while waiting for KeepAlive packet.", ClientId);
return StopAsync(); Stop(MqttClientDisconnectType.NotClean);
} }
private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken) private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
@@ -201,15 +206,18 @@ namespace MQTTnet.Server
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
} }
catch (MqttCommunicationException exception)
{
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
await StopAsync().ConfigureAwait(false);
}
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId); if (exception is MqttCommunicationException)
await StopAsync().ConfigureAwait(false); {
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
}
else
{
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
}
Stop(MqttClientDisconnectType.NotClean);
} }
} }
@@ -222,7 +230,7 @@ namespace MQTTnet.Server
if (packet is MqttPingReqPacket) if (packet is MqttPingReqPacket)
{ {
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { new MqttPingRespPacket() }); return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { new MqttPingRespPacket() }, cancellationToken);
} }
if (packet is MqttPubRelPacket pubRelPacket) if (packet is MqttPubRelPacket pubRelPacket)
@@ -237,7 +245,7 @@ namespace MQTTnet.Server
PacketIdentifier = pubRecPacket.PacketIdentifier PacketIdentifier = pubRecPacket.PacketIdentifier
}; };
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { responsePacket }); return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { responsePacket }, cancellationToken);
} }
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket) if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
@@ -258,35 +266,20 @@ namespace MQTTnet.Server
if (packet is MqttDisconnectPacket) if (packet is MqttDisconnectPacket)
{ {
return StopAsync(true); Stop(MqttClientDisconnectType.Clean);
return Task.FromResult(0);
} }
if (packet is MqttConnectPacket) if (packet is MqttConnectPacket)
{ {
return StopAsync(); Stop(MqttClientDisconnectType.NotClean);
return Task.FromResult(0);
} }
_logger.Warning<MqttClientSession>("Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet); _logger.Warning<MqttClientSession>("Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
return StopAsync(); Stop(MqttClientDisconnectType.NotClean);
}
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) return Task.FromResult(0);
{
var subscribeResult = await SubscriptionsManager.SubscribeAsync(subscribePacket).ConfigureAwait(false);
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { subscribeResult.ResponsePacket }).ConfigureAwait(false);
if (subscribeResult.CloseConnection)
{
await StopAsync().ConfigureAwait(false);
}
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
}
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{
var unsubscribeResult = await SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { unsubscribeResult });
} }
private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters) private async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<TopicFilter> topicFilters)
@@ -298,6 +291,26 @@ namespace MQTTnet.Server
} }
} }
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
{
var subscribeResult = SubscriptionsManager.Subscribe(subscribePacket);
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { subscribeResult.ResponsePacket }, cancellationToken).ConfigureAwait(false);
if (subscribeResult.CloseConnection)
{
Stop(MqttClientDisconnectType.NotClean);
return;
}
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
}
private async Task HandleIncomingUnsubscribePacketAsync(IMqttChannelAdapter adapter, MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{
var unsubscribeResult = SubscriptionsManager.Unsubscribe(unsubscribePacket);
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { unsubscribeResult }, cancellationToken);
}
private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken) private Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{ {
var applicationMessage = publishPacket.ToApplicationMessage(); var applicationMessage = publishPacket.ToApplicationMessage();
@@ -306,7 +319,8 @@ namespace MQTTnet.Server
{ {
case MqttQualityOfServiceLevel.AtMostOnce: case MqttQualityOfServiceLevel.AtMostOnce:
{ {
return ApplicationMessageReceivedCallback?.Invoke(this, applicationMessage); _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
return Task.FromResult(0);
} }
case MqttQualityOfServiceLevel.AtLeastOnce: case MqttQualityOfServiceLevel.AtLeastOnce:
{ {
@@ -325,25 +339,25 @@ namespace MQTTnet.Server
private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) private async Task HandleIncomingPublishPacketWithQoS1(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{ {
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false); _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }; var response = new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier };
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false);
} }
private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken) private async Task HandleIncomingPublishPacketWithQoS2(IMqttChannelAdapter adapter, MqttApplicationMessage applicationMessage, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{ {
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery] // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
await ApplicationMessageReceivedCallback(this, applicationMessage).ConfigureAwait(false); _sessionsManager.StartDispatchApplicationMessage(this, applicationMessage);
var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }; var response = new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier };
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }).ConfigureAwait(false); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken).ConfigureAwait(false);
} }
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{ {
var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }; var response = new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier };
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] { response }); return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[] { response }, cancellationToken);
} }
} }
} }
@@ -8,38 +8,34 @@ using MQTTnet.Diagnostics;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Serializer;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public sealed class MqttClientSessionsManager : IDisposable public sealed class MqttClientSessionsManager : IDisposable
{ {
private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>(); private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _sessionsLock = new SemaphoreSlim(1, 1);
private readonly IMqttServerOptions _options;
private readonly MqttRetainedMessagesManager _retainedMessagesManager; private readonly MqttRetainedMessagesManager _retainedMessagesManager;
private readonly IMqttServerOptions _options;
private readonly IMqttNetLogger _logger; private readonly IMqttNetLogger _logger;
public MqttClientSessionsManager(IMqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger) public MqttClientSessionsManager(IMqttServerOptions options, MqttServer server, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
Server = server ?? throw new ArgumentNullException(nameof(server));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
} }
public Action<ConnectedMqttClient> ClientConnectedCallback { get; set; } public MqttServer Server { get; }
public Action<ConnectedMqttClient, bool> ClientDisconnectedCallback { get; set; }
public Action<string, TopicFilter> ClientSubscribedTopicCallback { get; set; }
public Action<string, string> ClientUnsubscribedTopicCallback { get; set; }
public Action<string, MqttApplicationMessage> ApplicationMessageReceivedCallback { get; set; }
public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken) public async Task RunSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
{ {
var clientId = string.Empty; var clientId = string.Empty;
var wasCleanDisconnect = false; var wasCleanDisconnect = false;
MqttClientSession clientSession = null; MqttClientSession clientSession = null;
try try
{ {
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken) if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken)
@@ -57,30 +53,30 @@ namespace MQTTnet.Server
var connectReturnCode = ValidateConnection(connectPacket); var connectReturnCode = ValidateConnection(connectPacket);
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{ {
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[]
{ {
new MqttConnAckPacket new MqttConnAckPacket
{ {
ConnectReturnCode = connectReturnCode ConnectReturnCode = connectReturnCode
} }
}).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false);
return; return;
} }
var result = await GetOrCreateClientSessionAsync(connectPacket).ConfigureAwait(false); var result = await PrepareClientSessionAsync(connectPacket).ConfigureAwait(false);
clientSession = result.Session; clientSession = result.Session;
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new[] await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, new[]
{ {
new MqttConnAckPacket new MqttConnAckPacket
{ {
ConnectReturnCode = connectReturnCode, ConnectReturnCode = connectReturnCode,
IsSessionPresent = result.IsExistingSession IsSessionPresent = result.IsExistingSession
} }
}).ConfigureAwait(false); }, cancellationToken).ConfigureAwait(false);
ClientConnectedCallback?.Invoke(new ConnectedMqttClient Server.OnClientConnected(new ConnectedMqttClient
{ {
ClientId = clientId, ClientId = clientId,
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
@@ -99,15 +95,15 @@ namespace MQTTnet.Server
{ {
try try
{ {
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false); await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
clientAdapter.Dispose(); clientAdapter.Dispose();
} }
catch (Exception exception) catch (Exception exception)
{ {
_logger.Error<MqttClientSessionsManager>(exception, exception.Message); _logger.Error<MqttClientSessionsManager>(exception, exception.Message);
} }
ClientDisconnectedCallback?.Invoke(new ConnectedMqttClient Server.OnClientDisconnected(new ConnectedMqttClient
{ {
ClientId = clientId, ClientId = clientId,
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion, ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion,
@@ -119,31 +115,31 @@ namespace MQTTnet.Server
public async Task StopAsync() public async Task StopAsync()
{ {
await _semaphore.WaitAsync().ConfigureAwait(false); await _sessionsLock.WaitAsync().ConfigureAwait(false);
try try
{ {
foreach (var session in _sessions) foreach (var session in _sessions)
{ {
await session.Value.StopAsync().ConfigureAwait(false); session.Value.Stop(MqttClientDisconnectType.NotClean);
} }
_sessions.Clear(); _sessions.Clear();
} }
finally finally
{ {
_semaphore.Release(); _sessionsLock.Release();
} }
} }
public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync() public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
{ {
await _semaphore.WaitAsync().ConfigureAwait(false); await _sessionsLock.WaitAsync().ConfigureAwait(false);
try try
{ {
return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
{ {
ClientId = s.Value.ClientId, ClientId = s.Value.ClientId,
ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311, ProtocolVersion = s.Value.ProtocolVersion,
LastPacketReceived = s.Value.KeepAliveMonitor.LastPacketReceived, LastPacketReceived = s.Value.KeepAliveMonitor.LastPacketReceived,
LastNonKeepAlivePacketReceived = s.Value.KeepAliveMonitor.LastNonKeepAlivePacketReceived, LastNonKeepAlivePacketReceived = s.Value.KeepAliveMonitor.LastNonKeepAlivePacketReceived,
PendingApplicationMessages = s.Value.PendingMessagesQueue.Count PendingApplicationMessages = s.Value.PendingMessagesQueue.Count
@@ -151,49 +147,13 @@ namespace MQTTnet.Server
} }
finally finally
{ {
_semaphore.Release(); _sessionsLock.Release();
} }
} }
public async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) public void StartDispatchApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
{ {
try Task.Run(() => DispatchApplicationMessageAsync(senderClientSession, applicationMessage));
{
var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage);
if (interceptorContext.CloseConnection)
{
await senderClientSession.StopAsync().ConfigureAwait(false);
}
if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
{
return;
}
if (applicationMessage.Retain)
{
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
}
ApplicationMessageReceivedCallback?.Invoke(senderClientSession?.ClientId, applicationMessage);
}
catch (Exception exception)
{
_logger.Error<MqttClientSessionsManager>(exception, "Error while processing application message");
}
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
foreach (var clientSession in _sessions.Values)
{
await clientSession.EnqueueApplicationMessageAsync(applicationMessage);
}
}
finally
{
_semaphore.Release();
}
} }
public async Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters) public async Task SubscribeAsync(string clientId, IList<TopicFilter> topicFilters)
@@ -201,19 +161,19 @@ namespace MQTTnet.Server
if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
await _semaphore.WaitAsync().ConfigureAwait(false); await _sessionsLock.WaitAsync().ConfigureAwait(false);
try try
{ {
if (!_sessions.TryGetValue(clientId, out var session)) if (!_sessions.TryGetValue(clientId, out var session))
{ {
throw new InvalidOperationException($"Client session {clientId} is unknown."); throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
} }
await session.SubscribeAsync(topicFilters); await session.SubscribeAsync(topicFilters).ConfigureAwait(false);
} }
finally finally
{ {
_semaphore.Release(); _sessionsLock.Release();
} }
} }
@@ -222,36 +182,25 @@ namespace MQTTnet.Server
if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
await _semaphore.WaitAsync().ConfigureAwait(false); await _sessionsLock.WaitAsync().ConfigureAwait(false);
try try
{ {
if (!_sessions.TryGetValue(clientId, out var session)) if (!_sessions.TryGetValue(clientId, out var session))
{ {
throw new InvalidOperationException($"Client session {clientId} is unknown."); throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
} }
await session.UnsubscribeAsync(topicFilters); await session.UnsubscribeAsync(topicFilters).ConfigureAwait(false);
} }
finally finally
{ {
_semaphore.Release(); _sessionsLock.Release();
} }
} }
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage) public void Dispose()
{ {
var interceptorContext = new MqttApplicationMessageInterceptorContext( _sessionsLock?.Dispose();
senderClientSession?.ClientId,
applicationMessage);
var interceptor = _options.ApplicationMessageInterceptor;
if (interceptor == null)
{
return interceptorContext;
}
interceptor(interceptorContext);
return interceptorContext;
} }
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket) private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
@@ -271,9 +220,9 @@ namespace MQTTnet.Server
return context.ReturnCode; return context.ReturnCode;
} }
private async Task<GetOrCreateClientSessionResult> GetOrCreateClientSessionAsync(MqttConnectPacket connectPacket) private async Task<GetOrCreateClientSessionResult> PrepareClientSessionAsync(MqttConnectPacket connectPacket)
{ {
await _semaphore.WaitAsync().ConfigureAwait(false); await _sessionsLock.WaitAsync().ConfigureAwait(false);
try try
{ {
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession); var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
@@ -283,7 +232,7 @@ namespace MQTTnet.Server
{ {
_sessions.Remove(connectPacket.ClientId); _sessions.Remove(connectPacket.ClientId);
await clientSession.StopAsync().ConfigureAwait(false); clientSession.Stop(MqttClientDisconnectType.Clean);
clientSession.Dispose(); clientSession.Dispose();
clientSession = null; clientSession = null;
@@ -300,14 +249,7 @@ namespace MQTTnet.Server
{ {
isExistingSession = false; isExistingSession = false;
clientSession = new MqttClientSession(connectPacket.ClientId, _options, _retainedMessagesManager, _logger) clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _retainedMessagesManager, _logger);
{
ApplicationMessageReceivedCallback = DispatchApplicationMessageAsync
};
clientSession.SubscriptionsManager.TopicSubscribedCallback = ClientSubscribedTopicCallback;
clientSession.SubscriptionsManager.TopicUnsubscribedCallback = ClientUnsubscribedTopicCallback;
_sessions[connectPacket.ClientId] = clientSession; _sessions[connectPacket.ClientId] = clientSession;
_logger.Verbose<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId); _logger.Verbose<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId);
@@ -317,19 +259,65 @@ namespace MQTTnet.Server
} }
finally finally
{ {
_semaphore.Release(); _sessionsLock.Release();
} }
} }
public void Dispose() private async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
{ {
ClientConnectedCallback = null; try
ClientDisconnectedCallback = null; {
ClientSubscribedTopicCallback = null; var interceptorContext = InterceptApplicationMessage(senderClientSession, applicationMessage);
ClientUnsubscribedTopicCallback = null; if (interceptorContext.CloseConnection)
ApplicationMessageReceivedCallback = null; {
senderClientSession.Stop(MqttClientDisconnectType.NotClean);
}
_semaphore?.Dispose(); if (interceptorContext.ApplicationMessage == null || !interceptorContext.AcceptPublish)
{
return;
}
if (applicationMessage.Retain)
{
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
}
Server.OnApplicationMessageReceived(senderClientSession?.ClientId, applicationMessage);
}
catch (Exception exception)
{
_logger.Error<MqttClientSessionsManager>(exception, "Error while processing application message");
}
await _sessionsLock.WaitAsync().ConfigureAwait(false);
try
{
foreach (var clientSession in _sessions.Values)
{
await clientSession.EnqueueApplicationMessageAsync(applicationMessage).ConfigureAwait(false);
}
}
finally
{
_sessionsLock.Release();
}
}
private MqttApplicationMessageInterceptorContext InterceptApplicationMessage(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
{
var interceptorContext = new MqttApplicationMessageInterceptorContext(
senderClientSession?.ClientId,
applicationMessage);
var interceptor = _options.ApplicationMessageInterceptor;
if (interceptor == null)
{
return interceptorContext;
}
interceptor(interceptorContext);
return interceptorContext;
} }
} }
} }
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -10,21 +11,20 @@ namespace MQTTnet.Server
{ {
public sealed class MqttClientSubscriptionsManager : IDisposable public sealed class MqttClientSubscriptionsManager : IDisposable
{ {
private readonly ConcurrentDictionary<string, MqttQualityOfServiceLevel> _subscriptions = new ConcurrentDictionary<string, MqttQualityOfServiceLevel>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
private readonly IMqttServerOptions _options; private readonly IMqttServerOptions _options;
private readonly MqttServer _server;
private readonly string _clientId; private readonly string _clientId;
public MqttClientSubscriptionsManager(IMqttServerOptions options, string clientId) public MqttClientSubscriptionsManager(string clientId, IMqttServerOptions options, MqttServer server)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options));
_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); _clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
_options = options ?? throw new ArgumentNullException(nameof(options));
_server = server;
} }
public Action<string, TopicFilter> TopicSubscribedCallback { get; set; } public MqttClientSubscribeResult Subscribe(MqttSubscribePacket subscribePacket)
public Action<string, string> TopicUnsubscribedCallback { get; set; }
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttSubscribePacket subscribePacket)
{ {
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
@@ -38,57 +38,41 @@ namespace MQTTnet.Server
CloseConnection = false CloseConnection = false
}; };
await _semaphore.WaitAsync().ConfigureAwait(false); foreach (var topicFilter in subscribePacket.TopicFilters)
try
{ {
foreach (var topicFilter in subscribePacket.TopicFilters) var interceptorContext = InterceptSubscribe(topicFilter);
if (!interceptorContext.AcceptSubscription)
{ {
var interceptorContext = InterceptSubscribe(topicFilter); result.ResponsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure);
if (!interceptorContext.AcceptSubscription) }
{ else
result.ResponsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.Failure); {
} result.ResponsePacket.SubscribeReturnCodes.Add(ConvertToMaximumQoS(topicFilter.QualityOfServiceLevel));
else }
{
result.ResponsePacket.SubscribeReturnCodes.Add(ConvertToMaximumQoS(topicFilter.QualityOfServiceLevel)); if (interceptorContext.CloseConnection)
} {
result.CloseConnection = true;
if (interceptorContext.CloseConnection) }
{
result.CloseConnection = true; if (interceptorContext.AcceptSubscription)
} {
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
if (interceptorContext.AcceptSubscription) _server.OnClientSubscribedTopic(_clientId, topicFilter);
{
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
TopicSubscribedCallback?.Invoke(_clientId, topicFilter);
}
} }
}
finally
{
_semaphore.Release();
} }
return result; return result;
} }
public async Task<MqttUnsubAckPacket> UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket) public MqttUnsubAckPacket Unsubscribe(MqttUnsubscribePacket unsubscribePacket)
{ {
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
await _semaphore.WaitAsync().ConfigureAwait(false); foreach (var topicFilter in unsubscribePacket.TopicFilters)
try
{ {
foreach (var topicFilter in unsubscribePacket.TopicFilters) _subscriptions.TryRemove(topicFilter, out _);
{ _server.OnClientUnsubscribedTopic(_clientId, topicFilter);
_subscriptions.Remove(topicFilter);
TopicUnsubscribedCallback?.Invoke(_clientId, topicFilter);
}
}
finally
{
_semaphore.Release();
} }
return new MqttUnsubAckPacket return new MqttUnsubAckPacket
@@ -57,7 +57,7 @@ namespace MQTTnet.Server
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters); return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
} }
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages) public Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
{ {
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages)); if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
@@ -65,8 +65,10 @@ namespace MQTTnet.Server
foreach (var applicationMessage in applicationMessages) foreach (var applicationMessage in applicationMessages)
{ {
await _clientSessionsManager.DispatchApplicationMessageAsync(null, applicationMessage); _clientSessionsManager.StartDispatchApplicationMessage(null, applicationMessage);
} }
return Task.FromResult(0);
} }
public async Task StartAsync(IMqttServerOptions options) public async Task StartAsync(IMqttServerOptions options)
@@ -80,14 +82,7 @@ namespace MQTTnet.Server
_retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger); _retainedMessagesManager = new MqttRetainedMessagesManager(Options, _logger);
await _retainedMessagesManager.LoadMessagesAsync(); await _retainedMessagesManager.LoadMessagesAsync();
_clientSessionsManager = new MqttClientSessionsManager(Options, _retainedMessagesManager, _logger) _clientSessionsManager = new MqttClientSessionsManager(Options, this, _retainedMessagesManager, _logger);
{
ClientConnectedCallback = OnClientConnected,
ClientDisconnectedCallback = OnClientDisconnected,
ClientSubscribedTopicCallback = OnClientSubscribedTopic,
ClientUnsubscribedTopicCallback = OnClientUnsubscribedTopic,
ApplicationMessageReceivedCallback = OnApplicationMessageReceived
};
foreach (var adapter in _adapters) foreach (var adapter in _adapters)
{ {
@@ -132,29 +127,29 @@ namespace MQTTnet.Server
} }
} }
private void OnClientConnected(ConnectedMqttClient client) internal void OnClientConnected(ConnectedMqttClient client)
{ {
_logger.Info<MqttServer>("Client '{0}': Connected.", client.ClientId); _logger.Info<MqttServer>("Client '{0}': Connected.", client.ClientId);
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client)); ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(client));
} }
private void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect) internal void OnClientDisconnected(ConnectedMqttClient client, bool wasCleanDisconnect)
{ {
_logger.Info<MqttServer>("Client '{0}': Disconnected (clean={1}).", client.ClientId, wasCleanDisconnect); _logger.Info<MqttServer>("Client '{0}': Disconnected (clean={1}).", client.ClientId, wasCleanDisconnect);
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client, wasCleanDisconnect)); ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(client, wasCleanDisconnect));
} }
private void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter) internal void OnClientSubscribedTopic(string clientId, TopicFilter topicFilter)
{ {
ClientSubscribedTopic?.Invoke(this, new MqttClientSubscribedTopicEventArgs(clientId, topicFilter)); ClientSubscribedTopic?.Invoke(this, new MqttClientSubscribedTopicEventArgs(clientId, topicFilter));
} }
private void OnClientUnsubscribedTopic(string clientId, string topicFilter) internal void OnClientUnsubscribedTopic(string clientId, string topicFilter)
{ {
ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter)); ClientUnsubscribedTopic?.Invoke(this, new MqttClientUnsubscribedTopicEventArgs(clientId, topicFilter));
} }
private void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage) internal void OnApplicationMessageReceived(string clientId, MqttApplicationMessage applicationMessage)
{ {
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage)); ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(clientId, applicationMessage));
} }
@@ -19,12 +19,17 @@ namespace MQTTnet.Server
var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None); var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None);
var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None); var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None);
// # > In either case it MUST be the last character specified in the Topic Filter [MQTT-4.7.1-2].
for (var i = 0; i < fragmentsFilter.Length; i++) for (var i = 0; i < fragmentsFilter.Length; i++)
{ {
switch (fragmentsFilter[i]) if (fragmentsFilter[i] == "+")
{ {
case "+": continue; continue;
case "#" when i == fragmentsFilter.Length - 1: return true; }
if (fragmentsFilter[i] == "#")
{
return true;
} }
if (i >= fragmentsTopic.Length) if (i >= fragmentsTopic.Length)
@@ -38,7 +43,7 @@ namespace MQTTnet.Server
} }
} }
return fragmentsTopic.Length <= fragmentsFilter.Length; return fragmentsTopic.Length == fragmentsFilter.Length;
} }
} }
} }
@@ -6,7 +6,6 @@
<AssemblyName>MQTTnet</AssemblyName> <AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace> <RootNamespace>MQTTnet</RootNamespace>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> <GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<DebugType>Full</DebugType>
<AssemblyVersion>0.0.0.0</AssemblyVersion> <AssemblyVersion>0.0.0.0</AssemblyVersion>
<FileVersion>0.0.0.0</FileVersion> <FileVersion>0.0.0.0</FileVersion>
<Version>0.0.0.0</Version> <Version>0.0.0.0</Version>
@@ -19,7 +18,7 @@
<DelaySign>false</DelaySign> <DelaySign>false</DelaySign>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'"> <PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'">
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker> <NugetTargetMoniker>UAP,Version=v10.0</NugetTargetMoniker>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier> <TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
@@ -32,6 +31,10 @@
<LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets> <LanguageTargets>$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets</LanguageTargets>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DebugType>Full</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
@@ -43,17 +46,17 @@
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'"> <ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="System.Net.Security" Version="4.3.2" /> <PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" /> <PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" /> <PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'"> <ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
<PackageReference Include="System.Net.Security" Version="4.3.2" /> <PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" /> <PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" /> <PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0'"> <ItemGroup Condition="'$(TargetFramework)'=='uap10.0'">
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="5.4.1" /> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.0.8" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net452'"> <ItemGroup Condition="'$(TargetFramework)'=='net452'">
+18 -17
View File
@@ -38,7 +38,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions",
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Extensions\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTserver", "MQTTserver\MQTTserver.csproj", "{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Benchmarks", "Tests\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{998D04DD-7CB0-45F5-A393-E2495C16399E}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -170,22 +170,22 @@ Global
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x64.Build.0 = Release|Any CPU {C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x64.Build.0 = Release|Any CPU
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x86.ActiveCfg = Release|Any CPU {C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x86.ActiveCfg = Release|Any CPU
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x86.Build.0 = Release|Any CPU {C444E9C8-95FA-430E-9126-274129DE16CD}.Release|x86.Build.0 = Release|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|Any CPU.Build.0 = Debug|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|ARM.ActiveCfg = Debug|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|ARM.ActiveCfg = Debug|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|ARM.Build.0 = Debug|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|ARM.Build.0 = Debug|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|x64.ActiveCfg = Debug|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|x64.ActiveCfg = Debug|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|x64.Build.0 = Debug|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|x64.Build.0 = Debug|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|x86.ActiveCfg = Debug|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|x86.ActiveCfg = Debug|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Debug|x86.Build.0 = Debug|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Debug|x86.Build.0 = Debug|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|Any CPU.ActiveCfg = Release|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|Any CPU.Build.0 = Release|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|Any CPU.Build.0 = Release|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|ARM.ActiveCfg = Release|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|ARM.ActiveCfg = Release|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|ARM.Build.0 = Release|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|ARM.Build.0 = Release|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|x64.ActiveCfg = Release|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|x64.ActiveCfg = Release|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|x64.Build.0 = Release|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|x64.Build.0 = Release|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|x86.ActiveCfg = Release|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|x86.ActiveCfg = Release|Any CPU
{5FCCD9CE-9E7E-40C1-9B99-3328FED9EED7}.Release|x86.Build.0 = Release|Any CPU {998D04DD-7CB0-45F5-A393-E2495C16399E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -198,6 +198,7 @@ Global
{C6FF8AEA-0855-41EC-A1F3-AC262225BAB9} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {C6FF8AEA-0855-41EC-A1F3-AC262225BAB9} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
{F10C4060-F7EE-4A83-919F-FF723E72F94A} = {32A630A7-2598-41D7-B625-204CD906F5FB} {F10C4060-F7EE-4A83-919F-FF723E72F94A} = {32A630A7-2598-41D7-B625-204CD906F5FB}
{C444E9C8-95FA-430E-9126-274129DE16CD} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587} {C444E9C8-95FA-430E-9126-274129DE16CD} = {12816BCC-AF9E-44A9-9AE5-C246AF2A0587}
{998D04DD-7CB0-45F5-A393-E2495C16399E} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
+3 -3
View File
@@ -18,10 +18,10 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* TLS 1.2 support for client and server (but not UWP servers) * TLS 1.2 support for client and server (but not UWP servers)
* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS) * Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS)
* Lightweight (only the low level implementation of MQTT, no overhead) * Lightweight (only the low level implementation of MQTT, no overhead)
* Performance optimized (processing ~40.000 messages / second)* * Performance optimized (processing ~60.000 messages / second)*
* Interfaces included for mocking and testing * Interfaces included for mocking and testing
* Access to internal trace messages * Access to internal trace messages
* Unit tested (70+ tests) * Unit tested (~80 tests)
\* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_. \* Tested on local machine (Intel i7 8700K) with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetCore_.
@@ -86,7 +86,7 @@ If you use this library and want to see your project here please let me know.
## MIT License ## MIT License
Copyright (c) 2017 Christian Kratky Copyright (c) 2017-2018 Christian Kratky
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+54
View File
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.Extensions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.1.0" newVersion="1.2.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.IO.Compression" publicKeyToken="b77a5c561934e089" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.2.0" newVersion="4.1.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.InteropServices" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.IO.FileSystem" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.Algorithms" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.0.0" newVersion="4.1.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.IO.FileSystem.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Xml.XPath.XDocument" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{998D04DD-7CB0-45F5-A393-E2495C16399E}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>MQTTnet.Benchmarks</RootNamespace>
<AssemblyName>MQTTnet.Benchmarks</AssemblyName>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="BenchmarkDotNet, Version=0.10.14.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\..\packages\BenchmarkDotNet.0.10.14\lib\net46\BenchmarkDotNet.dll</HintPath>
</Reference>
<Reference Include="BenchmarkDotNet.Core, Version=0.10.14.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\..\packages\BenchmarkDotNet.Core.0.10.14\lib\net46\BenchmarkDotNet.Core.dll</HintPath>
</Reference>
<Reference Include="BenchmarkDotNet.Toolchains.Roslyn, Version=0.10.14.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.10.14\lib\net46\BenchmarkDotNet.Toolchains.Roslyn.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CodeAnalysis, Version=2.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.Common.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CodeAnalysis.CSharp, Version=2.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.CodeAnalysis.CSharp.2.6.1\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
</Reference>
<Reference Include="Microsoft.DotNet.InternalAbstractions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.DotNet.InternalAbstractions.1.0.0\lib\net451\Microsoft.DotNet.InternalAbstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.DotNet.PlatformAbstractions, Version=1.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.DotNet.PlatformAbstractions.1.1.1\lib\net451\Microsoft.DotNet.PlatformAbstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Win32.Registry, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Win32.Registry.4.3.0\lib\net46\Microsoft.Win32.Registry.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.AppContext, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Collections.Immutable, Version=1.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Console, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Diagnostics.FileVersionInfo, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll</HintPath>
</Reference>
<Reference Include="System.Diagnostics.StackTrace, Version=4.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.IO.FileSystem, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll</HintPath>
</Reference>
<Reference Include="System.IO.FileSystem.Primitives, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Management" />
<Reference Include="System.Numerics" />
<Reference Include="System.Reflection, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Metadata, Version=1.4.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath>
</Reference>
<Reference Include="System.Runtime, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.InteropServices, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Runtime.InteropServices.4.3.0\lib\net462\System.Runtime.InteropServices.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Algorithms, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encoding.CodePages, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Thread, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.ReaderWriter, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XmlDocument, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XPath, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll</HintPath>
</Reference>
<Reference Include="System.Xml.XPath.XDocument, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="MessageProcessingBenchmark.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SerializerBenchmark.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj">
<Project>{3587E506-55A2-4EB3-99C7-DC01E42D25D2}</Project>
<Name>MQTTnet.NetStandard</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -0,0 +1,47 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Columns;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using MQTTnet.Client;
using MQTTnet.Server;
namespace MQTTnet.Benchmarks
{
[ClrJob]
[RPlotExporter, RankColumn]
public class MessageProcessingBenchmark
{
private IMqttServer _mqttServer;
private IMqttClient _mqttClient;
private MqttApplicationMessage _message;
[GlobalSetup]
public void Setup()
{
var factory = new MqttFactory();
_mqttServer = factory.CreateMqttServer();
_mqttClient = factory.CreateMqttClient();
var serverOptions = new MqttServerOptionsBuilder().Build();
_mqttServer.StartAsync(serverOptions).GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
_message = new MqttApplicationMessageBuilder()
.WithTopic("A")
.Build();
}
[Benchmark]
public void Send_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
}
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using System;
using System.Threading;
using BenchmarkDotNet.Running;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine($"MQTTnet - BenchmarkApp.{TargetFrameworkInfoProvider.TargetFramework}");
Console.WriteLine("1 = MessageProcessingBenchmark");
Console.WriteLine("2 = SerializerBenchmark");
var pressedKey = Console.ReadKey(true);
switch (pressedKey.KeyChar)
{
case '1':
BenchmarkRunner.Run<MessageProcessingBenchmark>();
break;
case '2':
BenchmarkRunner.Run<SerializerBenchmark>();
break;
default:
break;
}
Console.ReadLine();
}
}
}
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MQTTnet.Benchmarks")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MQTTnet.Benchmarks")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("998d04dd-7cb0-45f5-a393-e2495c16399e")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -0,0 +1,74 @@
using BenchmarkDotNet.Attributes;
using MQTTnet.Client;
using MQTTnet.Packets;
using MQTTnet.Serializer;
using MQTTnet.Internal;
using MQTTnet.Server;
using BenchmarkDotNet.Attributes.Jobs;
using BenchmarkDotNet.Attributes.Exporters;
using System;
using System.Threading;
using System.IO;
using MQTTnet.Core.Internal;
namespace MQTTnet.Benchmarks
{
[ClrJob]
[RPlotExporter]
[MemoryDiagnoser]
public class SerializerBenchmark
{
private MqttBasePacket _packet;
private ArraySegment<byte> _serializedPacket;
private MqttPacketSerializer _serializer;
[GlobalSetup]
public void Setup()
{
var message = new MqttApplicationMessageBuilder()
.WithTopic("A")
.Build();
_packet = message.ToPublishPacket();
_serializer = new MqttPacketSerializer();
_serializedPacket = _serializer.Serialize(_packet);
}
[Benchmark]
public void Serialize_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{
_serializer.Serialize(_packet);
}
}
[Benchmark]
public void Deserialize_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{
using (var headerStream = new MemoryStream(Join(_serializedPacket)))
{
var header = MqttPacketReader.ReadHeaderAsync(new TestMqttChannel(headerStream), CancellationToken.None).GetAwaiter().GetResult();
using (var bodyStream = new MemoryStream(Join(_serializedPacket), (int)headerStream.Position, header.BodyLength))
{
_serializer.Deserialize(header, bodyStream);
}
}
}
}
private static byte[] Join(params ArraySegment<byte>[] chunks)
{
var buffer = new MemoryStream();
foreach (var chunk in chunks)
{
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
}
return buffer.ToArray();
}
}
}
+54
View File
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BenchmarkDotNet" version="0.10.14" targetFramework="net462" />
<package id="BenchmarkDotNet.Core" version="0.10.14" targetFramework="net462" />
<package id="BenchmarkDotNet.Toolchains.Roslyn" version="0.10.14" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.Common" version="2.6.1" targetFramework="net462" />
<package id="Microsoft.CodeAnalysis.CSharp" version="2.6.1" targetFramework="net462" />
<package id="Microsoft.DotNet.InternalAbstractions" version="1.0.0" targetFramework="net462" />
<package id="Microsoft.DotNet.PlatformAbstractions" version="1.1.1" targetFramework="net462" />
<package id="Microsoft.Win32.Registry" version="4.3.0" targetFramework="net462" />
<package id="System.AppContext" version="4.3.0" targetFramework="net462" />
<package id="System.Collections" version="4.3.0" targetFramework="net462" />
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net462" />
<package id="System.Collections.Immutable" version="1.3.1" targetFramework="net462" />
<package id="System.Console" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.FileVersionInfo" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.StackTrace" version="4.3.0" targetFramework="net462" />
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net462" />
<package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net462" />
<package id="System.Globalization" version="4.3.0" targetFramework="net462" />
<package id="System.IO.Compression" version="4.3.0" targetFramework="net462" />
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net462" />
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net462" />
<package id="System.Linq" version="4.3.0" targetFramework="net462" />
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net462" />
<package id="System.Reflection" version="4.3.0" targetFramework="net462" />
<package id="System.Reflection.Metadata" version="1.4.2" targetFramework="net462" />
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net462" />
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net462" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding.CodePages" version="4.3.0" targetFramework="net462" />
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Threading" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net462" />
<package id="System.Threading.Thread" version="4.3.0" targetFramework="net462" />
<package id="System.ValueTuple" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XmlSerializer" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XPath" version="4.3.0" targetFramework="net462" />
<package id="System.Xml.XPath.XDocument" version="4.3.0" targetFramework="net462" />
</packages>
@@ -0,0 +1,34 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Internal;
namespace MQTTnet.Core.Tests
{
[TestClass]
public class AsyncAutoResetEventTests
{
[TestMethod]
public async Task AsyncAutoResetEvent()
{
var aare = new AsyncAutoResetEvent();
var increment = 0;
var globalI = 0;
#pragma warning disable 4014
Task.Run(async () =>
#pragma warning restore 4014
{
await aare.WaitOneAsync(CancellationToken.None);
globalI += increment;
});
await Task.Delay(500);
increment = 1;
aare.Set();
await Task.Delay(100);
Assert.AreEqual(1, globalI);
}
}
}
@@ -0,0 +1,44 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Internal;
namespace MQTTnet.Core.Tests
{
[TestClass]
public class AsyncLockTests
{
[TestMethod]
public void AsyncLock()
{
const int ThreadsCount = 10;
var threads = new Task[ThreadsCount];
var @lock = new AsyncLock();
var globalI = 0;
for (var i = 0; i < ThreadsCount; i++)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
threads[i] = Task.Run(async () =>
{
await @lock.EnterAsync(CancellationToken.None);
try
{
var localI = globalI;
await Task.Delay(10); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
finally
{
@lock.Exit();
}
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
Task.WaitAll(threads);
Assert.AreEqual(ThreadsCount, globalI);
}
}
}
+18 -14
View File
@@ -1,9 +1,9 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Internal;
namespace MQTTnet.Core.Tests namespace MQTTnet.Core.Tests
{ {
@@ -14,20 +14,20 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public async Task TimeoutAfter() public async Task TimeoutAfter()
{ {
await Task.Delay(TimeSpan.FromMilliseconds(500)).TimeoutAfter(TimeSpan.FromMilliseconds(100)); await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct), TimeSpan.FromMilliseconds(100), CancellationToken.None);
} }
[ExpectedException(typeof(MqttCommunicationTimedOutException))] [ExpectedException(typeof(MqttCommunicationTimedOutException))]
[TestMethod] [TestMethod]
public async Task TimeoutAfterWithResult() public async Task TimeoutAfterWithResult()
{ {
await Task.Delay(TimeSpan.FromMilliseconds(500)).ContinueWith(t => 5).TimeoutAfter(TimeSpan.FromMilliseconds(100)); await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(100), CancellationToken.None);
} }
[TestMethod] [TestMethod]
public async Task TimeoutAfterCompleteInTime() public async Task TimeoutAfterCompleteInTime()
{ {
var result = await Task.Delay(TimeSpan.FromMilliseconds(100)).ContinueWith(t => 5).TimeoutAfter(TimeSpan.FromMilliseconds(500)); var result = await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(100), ct).ContinueWith(t => 5, ct), TimeSpan.FromMilliseconds(500), CancellationToken.None);
Assert.AreEqual(5, result); Assert.AreEqual(5, result);
} }
@@ -36,17 +36,17 @@ namespace MQTTnet.Core.Tests
{ {
try try
{ {
await Task.Run(() => await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Run(() =>
{ {
var iis = new int[0]; var iis = new int[0];
iis[1] = 0; iis[1] = 0;
}).TimeoutAfter(TimeSpan.FromSeconds(1)); }, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
Assert.Fail(); Assert.Fail();
} }
catch (MqttCommunicationException e) catch (Exception e)
{ {
Assert.IsTrue(e.InnerException is IndexOutOfRangeException); Assert.IsTrue(e is IndexOutOfRangeException);
} }
} }
@@ -55,17 +55,18 @@ namespace MQTTnet.Core.Tests
{ {
try try
{ {
await Task.Run(() => await MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Run(() =>
{ {
var iis = new int[0]; var iis = new int[0];
return iis[1]; iis[1] = 0;
}).TimeoutAfter(TimeSpan.FromSeconds(1)); return iis[0];
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
Assert.Fail(); Assert.Fail();
} }
catch (MqttCommunicationException e) catch (Exception e)
{ {
Assert.IsTrue(e.InnerException is IndexOutOfRangeException); Assert.IsTrue(e is IndexOutOfRangeException);
} }
} }
@@ -73,7 +74,10 @@ namespace MQTTnet.Core.Tests
public async Task TimeoutAfterMemoryUsage() public async Task TimeoutAfterMemoryUsage()
{ {
var tasks = Enumerable.Range(0, 100000) var tasks = Enumerable.Range(0, 100000)
.Select(i => Task.Delay(TimeSpan.FromMilliseconds(1)).TimeoutAfter(TimeSpan.FromMinutes(1))); .Select(i =>
{
return MQTTnet.Internal.TaskExtensions.TimeoutAfter(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct), TimeSpan.FromMinutes(1), CancellationToken.None);
});
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
AssertIsLess(3_000_000, GC.GetTotalMemory(true)); AssertIsLess(3_000_000, GC.GetTotalMemory(true));
@@ -1,5 +1,4 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Packets; using MQTTnet.Packets;
@@ -18,7 +17,6 @@ namespace MQTTnet.Core.Tests
var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate
{ {
timeoutCalledCount++; timeoutCalledCount++;
return Task.FromResult(0);
}, new MqttNetLogger()); }, new MqttNetLogger());
Assert.AreEqual(0, timeoutCalledCount); Assert.AreEqual(0, timeoutCalledCount);
@@ -40,7 +38,6 @@ namespace MQTTnet.Core.Tests
var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate var monitor = new MqttClientKeepAliveMonitor(string.Empty, delegate
{ {
timeoutCalledCount++; timeoutCalledCount++;
return Task.FromResult(0);
}, new MqttNetLogger()); }, new MqttNetLogger());
Assert.AreEqual(0, timeoutCalledCount); Assert.AreEqual(0, timeoutCalledCount);
@@ -1,6 +1,7 @@
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Core.Internal;
using MQTTnet.Serializer; using MQTTnet.Serializer;
namespace MQTTnet.Core.Tests namespace MQTTnet.Core.Tests
@@ -11,8 +12,7 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public void MqttPacketReader_EmptyStream() public void MqttPacketReader_EmptyStream()
{ {
var memStream = new MemoryStream(); var header = MqttPacketReader.ReadHeaderAsync(new TestMqttChannel(new MemoryStream()), CancellationToken.None).GetAwaiter().GetResult();
var header = MqttPacketReader.ReadHeaderAsync(memStream, CancellationToken.None).GetAwaiter().GetResult();
Assert.IsNull(header); Assert.IsNull(header);
} }
@@ -1,9 +1,9 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Core.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Serializer; using MQTTnet.Serializer;
@@ -25,7 +25,7 @@ namespace MQTTnet.Core.Tests
CleanSession = true CleanSession = true
}; };
SerializeAndCompare(p, "EB0ABE1RSXNkcAPCAHsAA1hZWgAEVVNFUgAEUEFTUw==", MqttProtocolVersion.V310); SerializeAndCompare(p, "EB0ABk1RSXNkcAPCAHsAA1hZWgAEVVNFUgAEUEFTUw==", MqttProtocolVersion.V310);
} }
[TestMethod] [TestMethod]
@@ -403,9 +403,9 @@ namespace MQTTnet.Core.Tests
private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
{ {
var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion }; var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion };
var chunks = serializer.Serialize(packet); var data = serializer.Serialize(packet);
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(chunks))); Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(Join(data)));
} }
private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
@@ -416,7 +416,7 @@ namespace MQTTnet.Core.Tests
using (var headerStream = new MemoryStream(Join(buffer1))) using (var headerStream = new MemoryStream(Join(buffer1)))
{ {
var header = MqttPacketReader.ReadHeaderAsync(headerStream, CancellationToken.None).GetAwaiter().GetResult(); var header = MqttPacketReader.ReadHeaderAsync(new TestMqttChannel(headerStream), CancellationToken.None).GetAwaiter().GetResult();
using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.BodyLength)) using (var bodyStream = new MemoryStream(Join(buffer1), (int)headerStream.Position, header.BodyLength))
{ {
@@ -428,7 +428,7 @@ namespace MQTTnet.Core.Tests
} }
} }
private static byte[] Join(IEnumerable<ArraySegment<byte>> chunks) private static byte[] Join(params ArraySegment<byte>[] chunks)
{ {
var buffer = new MemoryStream(); var buffer = new MemoryStream();
foreach (var chunk in chunks) foreach (var chunk in chunks)
+58 -22
View File
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
@@ -58,8 +59,8 @@ namespace MQTTnet.Core.Tests
await s.StartAsync(new MqttServerOptions()); await s.StartAsync(new MqttServerOptions());
var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build(); var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build();
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient("c1");
var c2 = await serverAdapter.ConnectTestClient(s, "c2", willMessage); var c2 = await serverAdapter.ConnectTestClient("c2", willMessage);
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build()); await c1.SubscribeAsync(new TopicFilterBuilder().WithTopic("#").Build());
@@ -90,8 +91,8 @@ namespace MQTTnet.Core.Tests
{ {
await s.StartAsync(new MqttServerOptions()); await s.StartAsync(new MqttServerOptions());
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient("c1");
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); var c2 = await serverAdapter.ConnectTestClient("c2");
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build();
@@ -149,7 +150,7 @@ namespace MQTTnet.Core.Tests
{ {
await s.StartAsync(new MqttServerOptions()); await s.StartAsync(new MqttServerOptions());
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient("c1");
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
@@ -167,6 +168,40 @@ namespace MQTTnet.Core.Tests
Assert.AreEqual(1, receivedMessagesCount); Assert.AreEqual(1, receivedMessagesCount);
} }
[TestMethod]
public async Task MqttServer_RetainedMessagesFlow()
{
var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build();
var serverAdapter = new TestMqttServerAdapter();
var s = new MqttFactory().CreateMqttServer(new[] { serverAdapter }, new MqttNetLogger());
await s.StartAsync(new MqttServerOptions());
var c1 = await serverAdapter.ConnectTestClient("c1");
await c1.PublishAsync(retainedMessage);
Thread.Sleep(500);
await c1.DisconnectAsync();
Thread.Sleep(500);
var receivedMessages = 0;
var c2 = await serverAdapter.ConnectTestClient("c2");
c2.ApplicationMessageReceived += (_, e) =>
{
receivedMessages++;
};
for (var i = 0; i < 5; i++)
{
await c2.UnsubscribeAsync("r");
await Task.Delay(500);
Assert.AreEqual(i, receivedMessages);
await c2.SubscribeAsync("r");
await Task.Delay(500);
Assert.AreEqual(i + 1, receivedMessages);
}
await c2.DisconnectAsync();
}
[TestMethod] [TestMethod]
public async Task MqttServer_NoRetainedMessage() public async Task MqttServer_NoRetainedMessage()
{ {
@@ -179,11 +214,11 @@ namespace MQTTnet.Core.Tests
{ {
await s.StartAsync(new MqttServerOptions()); await s.StartAsync(new MqttServerOptions());
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient("c1");
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).Build()); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).Build());
await c1.DisconnectAsync(); await c1.DisconnectAsync();
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); var c2 = await serverAdapter.ConnectTestClient("c2");
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build());
@@ -208,11 +243,11 @@ namespace MQTTnet.Core.Tests
{ {
await s.StartAsync(new MqttServerOptions()); await s.StartAsync(new MqttServerOptions());
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient("c1");
await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.DisconnectAsync(); await c1.DisconnectAsync();
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); var c2 = await serverAdapter.ConnectTestClient("c2");
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build());
@@ -237,15 +272,16 @@ namespace MQTTnet.Core.Tests
{ {
await s.StartAsync(new MqttServerOptions()); await s.StartAsync(new MqttServerOptions());
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient("c1");
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build()); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build());
await c1.DisconnectAsync(); await c1.DisconnectAsync();
var c2 = await serverAdapter.ConnectTestClient(s, "c2");
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
await c2.SubscribeAsync(new TopicFilter("retained", MqttQualityOfServiceLevel.AtMostOnce));
var c2 = await serverAdapter.ConnectTestClient("c2");
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
await Task.Delay(200);
await c2.SubscribeAsync(new TopicFilter("retained", MqttQualityOfServiceLevel.AtMostOnce));
await Task.Delay(500); await Task.Delay(500);
} }
finally finally
@@ -270,7 +306,7 @@ namespace MQTTnet.Core.Tests
await s.StartAsync(options); await s.StartAsync(options);
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient("c1");
await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); await c1.PublishAndWaitForAsync(s, new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.DisconnectAsync(); await c1.DisconnectAsync();
@@ -290,7 +326,7 @@ namespace MQTTnet.Core.Tests
var options = new MqttServerOptions { Storage = storage }; var options = new MqttServerOptions { Storage = storage };
await s.StartAsync(options); await s.StartAsync(options);
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); var c2 = await serverAdapter.ConnectTestClient("c2");
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build()); await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("retained").Build());
@@ -321,8 +357,8 @@ namespace MQTTnet.Core.Tests
await s.StartAsync(options); await s.StartAsync(options);
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient("c1");
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); var c2 = await serverAdapter.ConnectTestClient("c2");
await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("test").Build()); await c2.SubscribeAsync(new TopicFilterBuilder().WithTopic("test").Build());
var isIntercepted = false; var isIntercepted = false;
@@ -356,8 +392,8 @@ namespace MQTTnet.Core.Tests
{ {
await s.StartAsync(new MqttServerOptions()); await s.StartAsync(new MqttServerOptions());
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient("c1");
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); var c2 = await serverAdapter.ConnectTestClient("c2");
c1.ApplicationMessageReceived += (_, e) => c1.ApplicationMessageReceived += (_, e) =>
{ {
@@ -411,8 +447,8 @@ namespace MQTTnet.Core.Tests
{ {
await s.StartAsync(new MqttServerOptions()); await s.StartAsync(new MqttServerOptions());
var c1 = await serverAdapter.ConnectTestClient(s, "c1"); var c1 = await serverAdapter.ConnectTestClient("c1");
var c2 = await serverAdapter.ConnectTestClient(s, "c2"); var c2 = await serverAdapter.ConnectTestClient("c2");
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
@@ -1,4 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -11,12 +13,12 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public void MqttSubscriptionsManager_SubscribeSingleSuccess() public void MqttSubscriptionsManager_SubscribeSingleSuccess()
{ {
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), ""); var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger()));
var sp = new MqttSubscribePacket(); var sp = new MqttSubscribePacket();
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
sm.SubscribeAsync(sp).Wait(); sm.Subscribe(sp);
var pp = new MqttApplicationMessage var pp = new MqttApplicationMessage
{ {
@@ -32,12 +34,12 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public void MqttSubscriptionsManager_SubscribeDifferentQoSSuccess() public void MqttSubscriptionsManager_SubscribeDifferentQoSSuccess()
{ {
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), ""); var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger()));
var sp = new MqttSubscribePacket(); var sp = new MqttSubscribePacket();
sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtMostOnce)); sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtMostOnce));
sm.SubscribeAsync(sp).Wait(); sm.Subscribe(sp);
var pp = new MqttApplicationMessage var pp = new MqttApplicationMessage
{ {
@@ -53,13 +55,13 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public void MqttSubscriptionsManager_SubscribeTwoTimesSuccess() public void MqttSubscriptionsManager_SubscribeTwoTimesSuccess()
{ {
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), ""); var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger()));
var sp = new MqttSubscribePacket(); var sp = new MqttSubscribePacket();
sp.TopicFilters.Add(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)); sp.TopicFilters.Add(new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce));
sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtLeastOnce)); sp.TopicFilters.Add(new TopicFilter("A/B/C", MqttQualityOfServiceLevel.AtLeastOnce));
sm.SubscribeAsync(sp).Wait(); sm.Subscribe(sp);
var pp = new MqttApplicationMessage var pp = new MqttApplicationMessage
{ {
@@ -75,12 +77,12 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public void MqttSubscriptionsManager_SubscribeSingleNoSuccess() public void MqttSubscriptionsManager_SubscribeSingleNoSuccess()
{ {
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), ""); var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger()));
var sp = new MqttSubscribePacket(); var sp = new MqttSubscribePacket();
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
sm.SubscribeAsync(sp).Wait(); sm.Subscribe(sp);
var pp = new MqttApplicationMessage var pp = new MqttApplicationMessage
{ {
@@ -94,12 +96,12 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public void MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle() public void MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle()
{ {
var sm = new MqttClientSubscriptionsManager(new MqttServerOptions(), ""); var sm = new MqttClientSubscriptionsManager("", new MqttServerOptions(), new MqttServer(new IMqttServerAdapter[0], new MqttNetLogger()));
var sp = new MqttSubscribePacket(); var sp = new MqttSubscribePacket();
sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build()); sp.TopicFilters.Add(new TopicFilterBuilder().WithTopic("A/B/C").Build());
sm.SubscribeAsync(sp).Wait(); sm.Subscribe(sp);
var pp = new MqttApplicationMessage var pp = new MqttApplicationMessage
{ {
@@ -111,7 +113,7 @@ namespace MQTTnet.Core.Tests
var up = new MqttUnsubscribePacket(); var up = new MqttUnsubscribePacket();
up.TopicFilters.Add("A/B/C"); up.TopicFilters.Add("A/B/C");
sm.UnsubscribeAsync(up).Wait(); sm.Unsubscribe(up);
Assert.IsFalse(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed); Assert.IsFalse(sm.CheckSubscriptionsAsync(pp).Result.IsSubscribed);
} }
@@ -21,17 +21,17 @@ namespace MQTTnet.Core.Tests
{ {
} }
public Task ConnectAsync(TimeSpan timeout) public Task ConnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task DisconnectAsync(TimeSpan timeout) public Task DisconnectAsync(TimeSpan timeout, CancellationToken cancellationToken)
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets) public Task SendPacketsAsync(TimeSpan timeout, IEnumerable<MqttBasePacket> packets, CancellationToken cancellationToken)
{ {
ThrowIfPartnerIsNull(); ThrowIfPartnerIsNull();
@@ -43,11 +43,30 @@ namespace MQTTnet.Core.Tests
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken) public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
{ {
ThrowIfPartnerIsNull(); ThrowIfPartnerIsNull();
return Task.Run(() => if (timeout > TimeSpan.Zero)
{
using (var timeoutCts = new CancellationTokenSource(timeout))
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
{
return await Task.Run(() =>
{
try
{
return _incomingPackets.Take(cts.Token);
}
catch
{
return null;
}
}, cts.Token);
}
}
return await Task.Run(() =>
{ {
try try
{ {
@@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Client; using MQTTnet.Client;
@@ -11,7 +12,7 @@ namespace MQTTnet.Core.Tests
{ {
public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted; public event EventHandler<MqttServerAdapterClientAcceptedEventArgs> ClientAccepted;
public async Task<IMqttClient> ConnectTestClient(IMqttServer server, string clientId, MqttApplicationMessage willMessage = null) public async Task<IMqttClient> ConnectTestClient(string clientId, MqttApplicationMessage willMessage = null)
{ {
var adapterA = new TestMqttCommunicationAdapter(); var adapterA = new TestMqttCommunicationAdapter();
var adapterB = new TestMqttCommunicationAdapter(); var adapterB = new TestMqttCommunicationAdapter();
@@ -22,8 +23,6 @@ namespace MQTTnet.Core.Tests
new TestMqttCommunicationAdapterFactory(adapterA), new TestMqttCommunicationAdapterFactory(adapterA),
new MqttNetLogger()); new MqttNetLogger());
var connected = WaitForClientToConnect(server, clientId);
FireClientAcceptedEvent(adapterB); FireClientAcceptedEvent(adapterB);
var options = new MqttClientOptions var options = new MqttClientOptions
@@ -34,29 +33,11 @@ namespace MQTTnet.Core.Tests
}; };
await client.ConnectAsync(options); await client.ConnectAsync(options);
await connected; SpinWait.SpinUntil(() => client.IsConnected);
return client; return client;
} }
private static Task WaitForClientToConnect(IMqttServer s, string clientId)
{
var tcs = new TaskCompletionSource<object>();
void Handler(object sender, Server.MqttClientConnectedEventArgs args)
{
if (args.Client.ClientId == clientId)
{
s.ClientConnected -= Handler;
tcs.SetResult(null);
}
}
s.ClientConnected += Handler;
return tcs.Task;
}
private void FireClientAcceptedEvent(IMqttChannelAdapter adapter) private void FireClientAcceptedEvent(IMqttChannelAdapter adapter)
{ {
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(adapter)); ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(adapter));
@@ -71,5 +52,9 @@ namespace MQTTnet.Core.Tests
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
public void Dispose()
{
}
} }
} }
@@ -60,7 +60,33 @@ namespace MQTTnet.Core.Tests
CompareAndAssert("A/B/C/D", "#", true); CompareAndAssert("A/B/C/D", "#", true);
} }
private void CompareAndAssert(string topic, string filter, bool expectedResult) [TestMethod]
public void TopicFilterComparer_MultiLevel_Sport()
{
// Tests from official MQTT spec (4.7.1.2 Multi-level wildcard)
CompareAndAssert("sport/tennis/player1", "sport/tennis/player1/#", true);
CompareAndAssert("sport/tennis/player1/ranking", "sport/tennis/player1/#", true);
CompareAndAssert("sport/tennis/player1/score/wimbledon", "sport/tennis/player1/#", true);
CompareAndAssert("sport/tennis/player1", "sport/tennis/+", true);
CompareAndAssert("sport/tennis/player2", "sport/tennis/+", true);
CompareAndAssert("sport/tennis/player1/ranking", "sport/tennis/+", false);
CompareAndAssert("sport", "sport/#", true);
CompareAndAssert("sport", "sport/+", false);
CompareAndAssert("sport/", "sport/+", true);
}
[TestMethod]
public void TopicFilterComparer_SingleLevel_Finance()
{
// Tests from official MQTT spec (4.7.1.3 Single level wildcard)
CompareAndAssert("/finance", "+/+", true);
CompareAndAssert("/finance", "/+", true);
CompareAndAssert("/finance", "+", false);
}
private static void CompareAndAssert(string topic, string filter, bool expectedResult)
{ {
Assert.AreEqual(expectedResult, MqttTopicFilterComparer.IsMatch(topic, filter)); Assert.AreEqual(expectedResult, MqttTopicFilterComparer.IsMatch(topic, filter));
} }
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<TypeScriptToolsVersion>2.3</TypeScriptToolsVersion> <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -44,7 +44,7 @@ namespace MQTTnet.TestApp.NetCore
try try
{ {
await client.ConnectAsync(options); await client.ConnectAsync(options).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -59,7 +59,7 @@ namespace MQTTnet.TestApp.NetCore
var sentMessagesCount = 0; var sentMessagesCount = 0;
while (stopwatch.ElapsedMilliseconds < 1000) while (stopwatch.ElapsedMilliseconds < 1000)
{ {
await client.PublishAsync(messages).ConfigureAwait(false); client.PublishAsync(messages).GetAwaiter().GetResult();
sentMessagesCount++; sentMessagesCount++;
} }
@@ -165,7 +165,7 @@ namespace MQTTnet.TestApp.NetCore
Console.WriteLine("Press any key to exit."); Console.WriteLine("Press any key to exit.");
Console.ReadLine(); Console.ReadLine();
await mqttServer.StopAsync(); await mqttServer.StopAsync().ConfigureAwait(false);
} }
catch (Exception e) catch (Exception e)
{ {
+5
View File
@@ -19,6 +19,7 @@ namespace MQTTnet.TestApp.NetCore
Console.WriteLine("2 = Start server"); Console.WriteLine("2 = Start server");
Console.WriteLine("3 = Start performance test"); Console.WriteLine("3 = Start performance test");
Console.WriteLine("4 = Start managed client"); Console.WriteLine("4 = Start managed client");
Console.WriteLine("5 = Start public broker test");
var pressedKey = Console.ReadKey(true); var pressedKey = Console.ReadKey(true);
if (pressedKey.KeyChar == '1') if (pressedKey.KeyChar == '1')
@@ -37,6 +38,10 @@ namespace MQTTnet.TestApp.NetCore
{ {
Task.Run(ManagedClientTest.RunAsync); Task.Run(ManagedClientTest.RunAsync);
} }
else if (pressedKey.KeyChar == '5')
{
Task.Run(PublicBrokerTest.RunAsync);
}
Thread.Sleep(Timeout.Infinite); Thread.Sleep(Timeout.Infinite);
} }
@@ -0,0 +1,128 @@
using MQTTnet.Client;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Protocol;
using Newtonsoft.Json;
namespace MQTTnet.TestApp.NetCore
{
public static class PublicBrokerTest
{
public static async Task RunAsync()
{
//MqttNetGlobalLogger.LogMessagePublished += (s, e) => Console.WriteLine(e.TraceMessage);
// iot.eclipse.org
await ExecuteTestAsync("iot.eclipse.org TCP",
new MqttClientOptionsBuilder().WithTcpServer("iot.eclipse.org", 1883).Build());
await ExecuteTestAsync("iot.eclipse.org WS",
new MqttClientOptionsBuilder().WithWebSocketServer("iot.eclipse.org:80/mqtt").Build());
await ExecuteTestAsync("iot.eclipse.org WS TLS",
new MqttClientOptionsBuilder().WithWebSocketServer("iot.eclipse.org:443/mqtt").WithTls().Build());
// test.mosquitto.org
await ExecuteTestAsync("test.mosquitto.org TCP",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).Build());
await ExecuteTestAsync("test.mosquitto.org TCP TLS",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithTls().Build());
await ExecuteTestAsync("test.mosquitto.org WS",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").Build());
await ExecuteTestAsync("test.mosquitto.org WS TLS",
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").Build());
// broker.hivemq.com
await ExecuteTestAsync("broker.hivemq.com TCP",
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).Build());
await ExecuteTestAsync("broker.hivemq.com WS",
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build());
// mqtt.swifitch.cz
await ExecuteTestAsync("mqtt.swifitch.cz",
new MqttClientOptionsBuilder().WithTcpServer("mqtt.swifitch.cz", 1883).Build());
// CloudMQTT
var configFile = Path.Combine("E:\\CloudMqttTestConfig.json");
if (File.Exists(configFile))
{
var config = JsonConvert.DeserializeObject<MqttConfig>(File.ReadAllText(configFile));
await ExecuteTestAsync("CloudMQTT TCP",
new MqttClientOptionsBuilder().WithTcpServer(config.Server, config.Port).WithCredentials(config.Username, config.Password).Build());
await ExecuteTestAsync("CloudMQTT TCP TLS",
new MqttClientOptionsBuilder().WithTcpServer(config.Server, config.SslPort).WithCredentials(config.Username, config.Password).WithTls().Build());
await ExecuteTestAsync("CloudMQTT WS TLS",
new MqttClientOptionsBuilder().WithWebSocketServer(config.Server + ":" + config.SslWebSocketPort + "/mqtt").WithCredentials(config.Username, config.Password).WithTls().Build());
}
Write("Finished.", ConsoleColor.White);
Console.ReadLine();
}
private static async Task ExecuteTestAsync(string name, IMqttClientOptions options)
{
try
{
Write("Testing '" + name + "'... ", ConsoleColor.Gray);
var factory = new MqttFactory();
var client = factory.CreateMqttClient();
var topic = Guid.NewGuid().ToString();
MqttApplicationMessage receivedMessage = null;
client.ApplicationMessageReceived += (s, e) => receivedMessage = e.ApplicationMessage;
await client.ConnectAsync(options);
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce);
await client.PublishAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce);
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
{
throw new Exception("Message invalid.");
}
await client.UnsubscribeAsync("test");
await client.DisconnectAsync();
Write("[OK]\n", ConsoleColor.Green);
}
catch (Exception e)
{
Write("[FAILED] " + e.Message + "\n", ConsoleColor.Red);
}
}
private static void Write(string message, ConsoleColor color)
{
Console.ForegroundColor = color;
Console.Write(message);
}
public class MqttConfig
{
public string Server { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public int Port { get; set; }
public int SslPort { get; set; }
public int WebSocketPort { get; set; }
public int SslWebSocketPort { get; set; }
}
}
}
@@ -350,13 +350,10 @@ namespace MQTTnet.TestApp.UniversalWindows
payload = Convert.FromBase64String(RpcPayload.Text); payload = Convert.FromBase64String(RpcPayload.Text);
} }
try try
{ {
var rpcClient = new MqttRpcClient(_mqttClient); var rpcClient = new MqttRpcClient(_mqttClient);
await rpcClient.EnableAsync();
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), RpcMethod.Text, payload, qos); var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), RpcMethod.Text, payload, qos);
await rpcClient.DisableAsync();
RpcResponses.Items.Add(RpcMethod.Text + " >>> " + Encoding.UTF8.GetString(response)); RpcResponses.Items.Add(RpcMethod.Text + " >>> " + Encoding.UTF8.GetString(response));
} }
@@ -364,6 +361,10 @@ namespace MQTTnet.TestApp.UniversalWindows
{ {
RpcResponses.Items.Add(RpcMethod.Text + " >>> [TIMEOUT]"); RpcResponses.Items.Add(RpcMethod.Text + " >>> [TIMEOUT]");
} }
catch (Exception exception)
{
RpcResponses.Items.Add(RpcMethod.Text + " >>> [EXCEPTION (" + exception.Message + ")]");
}
} }
private void ClearRpcResponses(object sender, RoutedEventArgs e) private void ClearRpcResponses(object sender, RoutedEventArgs e)