Browse Source

Merge pull request #1 from chkr1011/master

v2.2.0 Merge
release/3.x.x
rydergillen-compacSort 7 years ago
committed by GitHub
parent
commit
dea2da52f9
57 changed files with 705 additions and 418 deletions
  1. +7
    -3
      Build/MQTTnet.nuspec
  2. +2
    -2
      Frameworks/MQTTnet.NetFramework/Implementations/MqttServerAdapter.cs
  3. +10
    -4
      Frameworks/MQTTnet.NetFramework/Implementations/MqttTcpChannel.cs
  4. +2
    -2
      Frameworks/MQTTnet.NetFramework/MqttClientFactory.cs
  5. +1
    -1
      Frameworks/MQTTnet.NetFramework/MqttServerFactory.cs
  6. +2
    -2
      Frameworks/MQTTnet.NetFramework/Properties/AssemblyInfo.cs
  7. +2
    -2
      Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs
  8. +10
    -4
      Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs
  9. +2
    -2
      Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj
  10. +2
    -2
      Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs
  11. +1
    -1
      Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs
  12. +1
    -1
      Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs
  13. +9
    -3
      Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs
  14. +2
    -2
      Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs
  15. +1
    -1
      Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs
  16. +2
    -2
      Frameworks/MQTTnet.UniversalWindows/Properties/AssemblyInfo.cs
  17. +3
    -0
      MQTTnet.Core/Adapter/IMqttCommunicationAdapter.cs
  18. +37
    -33
      MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs
  19. +24
    -0
      MQTTnet.Core/Client/IMqttClient.cs
  20. +119
    -74
      MQTTnet.Core/Client/MqttClient.cs
  21. +5
    -2
      MQTTnet.Core/Client/MqttClientOptions.cs
  22. +6
    -1
      MQTTnet.Core/Exceptions/MqttCommunicationException.cs
  23. +2
    -2
      MQTTnet.Core/MQTTnet.Core.csproj
  24. +1
    -1
      MQTTnet.Core/Packets/IMqttPacketWithIdentifier.cs
  25. +1
    -20
      MQTTnet.Core/Packets/MqttBasePacket.cs
  26. +1
    -1
      MQTTnet.Core/Packets/MqttBasePublishPacket.cs
  27. +5
    -1
      MQTTnet.Core/Packets/MqttConnectPacket.cs
  28. +27
    -0
      MQTTnet.Core/Packets/MqttPacketExtensions.cs
  29. +1
    -1
      MQTTnet.Core/Packets/MqttSubAckPacket.cs
  30. +1
    -1
      MQTTnet.Core/Packets/MqttSubscribePacket.cs
  31. +1
    -1
      MQTTnet.Core/Packets/MqttUnsubAckPacket.cs
  32. +1
    -1
      MQTTnet.Core/Packets/MqttUnsubscribe.cs
  33. +2
    -2
      MQTTnet.Core/Serializer/ByteReader.cs
  34. +2
    -0
      MQTTnet.Core/Serializer/IMqttPacketSerializer.cs
  35. +1
    -1
      MQTTnet.Core/Serializer/MqttPacketReader.cs
  36. +89
    -74
      MQTTnet.Core/Serializer/MqttPacketSerializer.cs
  37. +36
    -36
      MQTTnet.Core/Serializer/MqttPacketWriter.cs
  38. +8
    -0
      MQTTnet.Core/Serializer/MqttProtocolVersion.cs
  39. +11
    -0
      MQTTnet.Core/Server/ConnectedMqttClient.cs
  40. +18
    -0
      MQTTnet.Core/Server/IMqttServer.cs
  41. +3
    -4
      MQTTnet.Core/Server/MqttClientMessageQueue.cs
  42. +1
    -4
      MQTTnet.Core/Server/MqttClientPublishPacketContext.cs
  43. +48
    -42
      MQTTnet.Core/Server/MqttClientSession.cs
  44. +25
    -8
      MQTTnet.Core/Server/MqttClientSessionsManager.cs
  45. +27
    -18
      MQTTnet.Core/Server/MqttClientSubscriptionsManager.cs
  46. +10
    -2
      MQTTnet.Core/Server/MqttServer.cs
  47. +1
    -1
      MQTTnet.Core/Server/MqttServerDefaultEndpointOptions.cs
  48. +2
    -2
      MQTTnet.Core/Server/MqttServerOptions.cs
  49. +0
    -19
      MQTTnet.Core/Server/MqttServerTlsEndpointOptionsExtensions.cs
  50. +6
    -1
      MQTTnet.sln
  51. +27
    -20
      README.md
  52. +1
    -1
      Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj
  53. +30
    -4
      Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs
  54. +58
    -1
      Tests/MQTTnet.Core.Tests/MqttServerTests.cs
  55. +4
    -4
      Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs
  56. +3
    -0
      Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs
  57. +1
    -1
      Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs

+ 7
- 3
Build/MQTTnet.nuspec View File

@@ -2,7 +2,7 @@
<package > <package >
<metadata> <metadata>
<id>MQTTnet</id> <id>MQTTnet</id>
<version>2.1.5</version>
<version>2.2.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,10 +10,14 @@
<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 .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description> <description>MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
<releaseNotes>*
<releaseNotes>* [Server] Added support for MQTT protocol version 3.1.0
* [Server] Providing the used protocol version of connected clients
* [Client] Added support for protocol version 3.1.0
* [Core] Several minor performance improvements
* [Core] Fixed an issue with connection management (Thanks to wuzhenda; Zuendelmeister)
</releaseNotes> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</copyright> <copyright>Copyright Christian Kratky 2016-2017</copyright>
<tags>MQTT MQTTClient MQTTServer MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Queue Hardware</tags>
<tags>MQTT MQTTClient MQTTServer MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Queue Hardware Arduino</tags>
<dependencies> <dependencies>


<group targetFramework="netstandard1.3"> <group targetFramework="netstandard1.3">


+ 2
- 2
Frameworks/MQTTnet.NetFramework/Implementations/MqttServerAdapter.cs View File

@@ -86,7 +86,7 @@ namespace MQTTnet.Implementations
try try
{ {
var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null); var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null);
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new DefaultMqttV311PacketSerializer());
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer());
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter)); ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
} }
catch (Exception exception) when (!(exception is ObjectDisposedException)) catch (Exception exception) when (!(exception is ObjectDisposedException))
@@ -107,7 +107,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); await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false);
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new DefaultMqttV311PacketSerializer());
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer());
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter)); ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
} }
catch (Exception exception) catch (Exception exception)


+ 10
- 4
Frameworks/MQTTnet.NetFramework/Implementations/MqttTcpChannel.cs View File

@@ -12,12 +12,11 @@ namespace MQTTnet.Implementations
{ {
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
{ {
private readonly Socket _socket;
private Socket _socket;
private SslStream _sslStream; private SslStream _sslStream;


public MqttTcpChannel() public MqttTcpChannel()
{ {
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
} }


public MqttTcpChannel(Socket socket, SslStream sslStream) public MqttTcpChannel(Socket socket, SslStream sslStream)
@@ -31,6 +30,11 @@ namespace MQTTnet.Implementations
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));
try try
{ {
if (_socket == null)
{
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
}

await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, options.Server, options.GetPort(), null); await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, options.Server, options.GetPort(), null);


if (options.TlsOptions.UseTls) if (options.TlsOptions.UseTls)
@@ -49,8 +53,7 @@ namespace MQTTnet.Implementations
{ {
try try
{ {
_sslStream.Dispose();
_socket.Dispose();
Dispose();
return Task.FromResult(0); return Task.FromResult(0);
} }
catch (SocketException exception) catch (SocketException exception)
@@ -108,6 +111,9 @@ namespace MQTTnet.Implementations
{ {
_socket?.Dispose(); _socket?.Dispose();
_sslStream?.Dispose(); _sslStream?.Dispose();

_socket = null;
_sslStream = null;
} }


private static X509CertificateCollection LoadCertificates(MqttClientOptions options) private static X509CertificateCollection LoadCertificates(MqttClientOptions options)


+ 2
- 2
Frameworks/MQTTnet.NetFramework/MqttClientFactory.cs View File

@@ -8,11 +8,11 @@ namespace MQTTnet
{ {
public class MqttClientFactory public class MqttClientFactory
{ {
public MqttClient CreateMqttClient(MqttClientOptions options)
public IMqttClient CreateMqttClient(MqttClientOptions options)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));


return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new DefaultMqttV311PacketSerializer()));
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttPacketSerializer()));
} }
} }
} }

+ 1
- 1
Frameworks/MQTTnet.NetFramework/MqttServerFactory.cs View File

@@ -8,7 +8,7 @@ namespace MQTTnet
{ {
public class MqttServerFactory public class MqttServerFactory
{ {
public MqttServer CreateMqttServer(MqttServerOptions options)
public IMqttServer CreateMqttServer(MqttServerOptions options)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));




+ 2
- 2
Frameworks/MQTTnet.NetFramework/Properties/AssemblyInfo.cs View File

@@ -11,5 +11,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
[assembly: Guid("a480ef90-0eaa-4d9a-b271-47a9c47f6f7d")] [assembly: Guid("a480ef90-0eaa-4d9a-b271-47a9c47f6f7d")]
[assembly: AssemblyVersion("2.1.4.0")]
[assembly: AssemblyFileVersion("2.1.4.0")]
[assembly: AssemblyVersion("2.1.5.1")]
[assembly: AssemblyFileVersion("2.1.5.1")]

+ 2
- 2
Frameworks/MQTTnet.NetStandard/Implementations/MqttServerAdapter.cs View File

@@ -84,7 +84,7 @@ namespace MQTTnet.Implementations
try try
{ {
var clientSocket = await _defaultEndpointSocket.AcceptAsync(); var clientSocket = await _defaultEndpointSocket.AcceptAsync();
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new DefaultMqttV311PacketSerializer());
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer());
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter)); ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
} }
catch (Exception exception) catch (Exception exception)
@@ -105,7 +105,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); await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false);
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new DefaultMqttV311PacketSerializer());
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer());
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter)); ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
} }
catch (Exception exception) catch (Exception exception)


+ 10
- 4
Frameworks/MQTTnet.NetStandard/Implementations/MqttTcpChannel.cs View File

@@ -12,12 +12,11 @@ namespace MQTTnet.Implementations
{ {
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
{ {
private readonly Socket _socket;
private Socket _socket;
private SslStream _sslStream; private SslStream _sslStream;


public MqttTcpChannel() public MqttTcpChannel()
{ {
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
} }


public MqttTcpChannel(Socket socket, SslStream sslStream) public MqttTcpChannel(Socket socket, SslStream sslStream)
@@ -31,6 +30,11 @@ namespace MQTTnet.Implementations
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));
try try
{ {
if (_socket == null)
{
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
}

await _socket.ConnectAsync(options.Server, options.GetPort()); await _socket.ConnectAsync(options.Server, options.GetPort());
if (options.TlsOptions.UseTls) if (options.TlsOptions.UseTls)
@@ -49,8 +53,7 @@ namespace MQTTnet.Implementations
{ {
try try
{ {
_sslStream.Dispose();
_socket.Dispose();
Dispose();
return Task.FromResult(0); return Task.FromResult(0);
} }
catch (SocketException exception) catch (SocketException exception)
@@ -101,6 +104,9 @@ namespace MQTTnet.Implementations
{ {
_socket?.Dispose(); _socket?.Dispose();
_sslStream?.Dispose(); _sslStream?.Dispose();

_socket = null;
_sslStream = null;
} }


private static X509CertificateCollection LoadCertificates(MqttClientOptions options) private static X509CertificateCollection LoadCertificates(MqttClientOptions options)


+ 2
- 2
Frameworks/MQTTnet.NetStandard/MQTTnet.Netstandard.csproj View File

@@ -4,8 +4,8 @@
<TargetFramework>netstandard1.3</TargetFramework> <TargetFramework>netstandard1.3</TargetFramework>
<AssemblyName>MQTTnet</AssemblyName> <AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace> <RootNamespace>MQTTnet</RootNamespace>
<AssemblyVersion>2.1.4.0</AssemblyVersion>
<FileVersion>2.1.4.0</FileVersion>
<AssemblyVersion>2.1.5.1</AssemblyVersion>
<FileVersion>2.1.5.1</FileVersion>
<Version>0.0.0.0</Version> <Version>0.0.0.0</Version>
<Company /> <Company />
<Product /> <Product />


+ 2
- 2
Frameworks/MQTTnet.NetStandard/MqttClientFactory.cs View File

@@ -8,11 +8,11 @@ namespace MQTTnet
{ {
public class MqttClientFactory public class MqttClientFactory
{ {
public MqttClient CreateMqttClient(MqttClientOptions options)
public IMqttClient CreateMqttClient(MqttClientOptions options)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));


return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new DefaultMqttV311PacketSerializer()));
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttPacketSerializer()));
} }
} }
} }

+ 1
- 1
Frameworks/MQTTnet.NetStandard/MqttServerFactory.cs View File

@@ -8,7 +8,7 @@ namespace MQTTnet
{ {
public class MqttServerFactory public class MqttServerFactory
{ {
public MqttServer CreateMqttServer(MqttServerOptions options)
public IMqttServer CreateMqttServer(MqttServerOptions options)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));




+ 1
- 1
Frameworks/MQTTnet.UniversalWindows/Implementations/MqttServerAdapter.cs View File

@@ -52,7 +52,7 @@ namespace MQTTnet.Implementations
{ {
try try
{ {
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(args.Socket), new DefaultMqttV311PacketSerializer());
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer());
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(args.Socket.Information.RemoteAddress.ToString(), clientAdapter)); ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(args.Socket.Information.RemoteAddress.ToString(), clientAdapter));
} }
catch (Exception exception) catch (Exception exception)


+ 9
- 3
Frameworks/MQTTnet.UniversalWindows/Implementations/MqttTcpChannel.cs View File

@@ -15,11 +15,10 @@ namespace MQTTnet.Implementations
{ {
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
{ {
private readonly StreamSocket _socket;
private StreamSocket _socket;


public MqttTcpChannel() public MqttTcpChannel()
{ {
_socket = new StreamSocket();
} }


public MqttTcpChannel(StreamSocket socket) public MqttTcpChannel(StreamSocket socket)
@@ -32,6 +31,11 @@ namespace MQTTnet.Implementations
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));
try try
{ {
if (_socket == null)
{
_socket = new StreamSocket();
}

if (!options.TlsOptions.UseTls) if (!options.TlsOptions.UseTls)
{ {
await _socket.ConnectAsync(new HostName(options.Server), options.GetPort().ToString()); await _socket.ConnectAsync(new HostName(options.Server), options.GetPort().ToString());
@@ -59,7 +63,7 @@ namespace MQTTnet.Implementations
{ {
try try
{ {
_socket.Dispose();
Dispose();
return Task.FromResult(0); return Task.FromResult(0);
} }
catch (SocketException exception) catch (SocketException exception)
@@ -100,6 +104,8 @@ namespace MQTTnet.Implementations
public void Dispose() public void Dispose()
{ {
_socket?.Dispose(); _socket?.Dispose();

_socket = null;
} }


private static Certificate LoadCertificate(MqttClientOptions options) private static Certificate LoadCertificate(MqttClientOptions options)


+ 2
- 2
Frameworks/MQTTnet.UniversalWindows/MqttClientFactory.cs View File

@@ -8,11 +8,11 @@ namespace MQTTnet
{ {
public class MqttClientFactory public class MqttClientFactory
{ {
public MqttClient CreateMqttClient(MqttClientOptions options)
public IMqttClient CreateMqttClient(MqttClientOptions options)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));


return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new DefaultMqttV311PacketSerializer()));
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttPacketSerializer()));
} }
} }
} }

+ 1
- 1
Frameworks/MQTTnet.UniversalWindows/MqttServerFactory.cs View File

@@ -8,7 +8,7 @@ namespace MQTTnet
{ {
public class MqttServerFactory public class MqttServerFactory
{ {
public MqttServer CreateMqttServer(MqttServerOptions options)
public IMqttServer CreateMqttServer(MqttServerOptions options)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options == null) throw new ArgumentNullException(nameof(options));




+ 2
- 2
Frameworks/MQTTnet.UniversalWindows/Properties/AssemblyInfo.cs View File

@@ -10,5 +10,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
[assembly: AssemblyVersion("2.1.4.0")]
[assembly: AssemblyFileVersion("2.1.4.0")]
[assembly: AssemblyVersion("2.1.5.1")]
[assembly: AssemblyFileVersion("2.1.5.1")]

+ 3
- 0
MQTTnet.Core/Adapter/IMqttCommunicationAdapter.cs View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Core.Client; using MQTTnet.Core.Client;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
using MQTTnet.Core.Serializer;


namespace MQTTnet.Core.Adapter namespace MQTTnet.Core.Adapter
{ {
@@ -14,5 +15,7 @@ namespace MQTTnet.Core.Adapter
Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout); Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout);


Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout); Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout);

IMqttPacketSerializer PacketSerializer { get; }
} }
} }

+ 37
- 33
MQTTnet.Core/Adapter/MqttChannelCommunicationAdapter.cs View File

@@ -11,22 +11,19 @@ namespace MQTTnet.Core.Adapter
{ {
public class MqttChannelCommunicationAdapter : IMqttCommunicationAdapter public class MqttChannelCommunicationAdapter : IMqttCommunicationAdapter
{ {
private readonly IMqttPacketSerializer _serializer;
private readonly IMqttCommunicationChannel _channel; private readonly IMqttCommunicationChannel _channel;


public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer) public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer)
{ {
_channel = channel ?? throw new ArgumentNullException(nameof(channel)); _channel = channel ?? throw new ArgumentNullException(nameof(channel));
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
} }


public IMqttPacketSerializer PacketSerializer { get; }

public async Task ConnectAsync(MqttClientOptions options, TimeSpan timeout) public async Task ConnectAsync(MqttClientOptions options, TimeSpan timeout)
{ {
var task = _channel.ConnectAsync(options);
if (await Task.WhenAny(Task.Delay(timeout), task) != task)
{
throw new MqttCommunicationTimedOutException();
}
await ExecuteWithTimeoutAsync(_channel.ConnectAsync(options), timeout);
} }


public async Task DisconnectAsync() public async Task DisconnectAsync()
@@ -38,21 +35,7 @@ namespace MQTTnet.Core.Adapter
{ {
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"TX >>> {packet} [Timeout={timeout}]"); MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"TX >>> {packet} [Timeout={timeout}]");


bool hasTimeout;
try
{
var task = _serializer.SerializeAsync(packet, _channel);
hasTimeout = await Task.WhenAny(Task.Delay(timeout), task) != task;
}
catch (Exception exception)
{
throw new MqttCommunicationException(exception);
}

if (hasTimeout)
{
throw new MqttCommunicationTimedOutException();
}
await ExecuteWithTimeoutAsync(PacketSerializer.SerializeAsync(packet, _channel), timeout);
} }


public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout) public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout)
@@ -60,20 +43,11 @@ namespace MQTTnet.Core.Adapter
MqttBasePacket packet; MqttBasePacket packet;
if (timeout > TimeSpan.Zero) if (timeout > TimeSpan.Zero)
{ {
var workerTask = _serializer.DeserializeAsync(_channel);
var timeoutTask = Task.Delay(timeout);
var hasTimeout = Task.WhenAny(timeoutTask, workerTask) == timeoutTask;

if (hasTimeout)
{
throw new MqttCommunicationTimedOutException();
}

packet = workerTask.Result;
packet = await ExecuteWithTimeoutAsync(PacketSerializer.DeserializeAsync(_channel), timeout);
} }
else else
{ {
packet = await _serializer.DeserializeAsync(_channel);
packet = await PacketSerializer.DeserializeAsync(_channel);
} }


if (packet == null) if (packet == null)
@@ -84,5 +58,35 @@ namespace MQTTnet.Core.Adapter
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"RX <<< {packet}"); MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"RX <<< {packet}");
return packet; return packet;
} }

private static async Task<TResult> ExecuteWithTimeoutAsync<TResult>(Task<TResult> task, TimeSpan timeout)
{
var timeoutTask = Task.Delay(timeout);
if (await Task.WhenAny(timeoutTask, task) == timeoutTask)
{
throw new MqttCommunicationTimedOutException();
}

if (task.IsFaulted)
{
throw new MqttCommunicationException(task.Exception);
}

return task.Result;
}

private static async Task ExecuteWithTimeoutAsync(Task task, TimeSpan timeout)
{
var timeoutTask = Task.Delay(timeout);
if (await Task.WhenAny(timeoutTask, task) == timeoutTask)
{
throw new MqttCommunicationTimedOutException();
}

if (task.IsFaulted)
{
throw new MqttCommunicationException(task.Exception);
}
}
} }
} }

+ 24
- 0
MQTTnet.Core/Client/IMqttClient.cs View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Core.Packets;

namespace MQTTnet.Core.Client
{
public interface IMqttClient
{
bool IsConnected { get; }

event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
event EventHandler Connected;
event EventHandler Disconnected;

Task ConnectAsync(MqttApplicationMessage willApplicationMessage = null);
Task DisconnectAsync();
Task PublishAsync(MqttApplicationMessage applicationMessage);
Task<IList<MqttSubscribeResult>> SubscribeAsync(IList<TopicFilter> topicFilters);
Task<IList<MqttSubscribeResult>> SubscribeAsync(params TopicFilter[] topicFilters);
Task Unsubscribe(IList<string> topicFilters);
Task Unsubscribe(params string[] topicFilters);
}
}

+ 119
- 74
MQTTnet.Core/Client/MqttClient.cs View File

@@ -1,5 +1,4 @@
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;
@@ -13,15 +12,15 @@ using MQTTnet.Core.Protocol;


namespace MQTTnet.Core.Client namespace MQTTnet.Core.Client
{ {
public class MqttClient
public class MqttClient : IMqttClient
{ {
private readonly ConcurrentDictionary<ushort, MqttPublishPacket> _pendingExactlyOncePublishPackets = new ConcurrentDictionary<ushort, MqttPublishPacket>();
private readonly HashSet<ushort> _processedPublishPackets = new HashSet<ushort>();
private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>();


private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
private readonly MqttClientOptions _options; private readonly MqttClientOptions _options;
private readonly IMqttCommunicationAdapter _adapter; private readonly IMqttCommunicationAdapter _adapter;


private bool _disconnectedEventSuspended;
private int _latestPacketIdentifier; private int _latestPacketIdentifier;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;


@@ -29,6 +28,8 @@ namespace MQTTnet.Core.Client
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter)); _adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));

_adapter.PacketSerializer.ProtocolVersion = options.ProtocolVersion;
} }


public event EventHandler Connected; public event EventHandler Connected;
@@ -48,50 +49,64 @@ namespace MQTTnet.Core.Client
throw new MqttProtocolViolationException("It is not allowed to connect with a server after the connection is established."); throw new MqttProtocolViolationException("It is not allowed to connect with a server after the connection is established.");
} }


var connectPacket = new MqttConnectPacket
try
{ {
ClientId = _options.ClientId,
Username = _options.UserName,
Password = _options.Password,
CleanSession = _options.CleanSession,
KeepAlivePeriod = (ushort)_options.KeepAlivePeriod.TotalSeconds,
WillMessage = willApplicationMessage
};
_disconnectedEventSuspended = false;


await _adapter.ConnectAsync(_options, _options.DefaultCommunicationTimeout);
MqttTrace.Verbose(nameof(MqttClient), "Connection with server established.");
await _adapter.ConnectAsync(_options, _options.DefaultCommunicationTimeout);


_cancellationTokenSource = new CancellationTokenSource();
_latestPacketIdentifier = 0;
_processedPublishPackets.Clear();
_packetDispatcher.Reset();
IsConnected = true;
MqttTrace.Verbose(nameof(MqttClient), "Connection with server established.");


#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => ReceivePackets(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
var connectPacket = new MqttConnectPacket
{
ClientId = _options.ClientId,
Username = _options.UserName,
Password = _options.Password,
CleanSession = _options.CleanSession,
KeepAlivePeriod = (ushort)_options.KeepAlivePeriod.TotalSeconds,
WillMessage = willApplicationMessage
};

_cancellationTokenSource = new CancellationTokenSource();
_latestPacketIdentifier = 0;
_packetDispatcher.Reset();

StartReceivePackets();

var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket);
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
await DisconnectInternalAsync();
throw new MqttConnectingFailedException(response.ConnectReturnCode);
}


var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket);
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
await DisconnectAsync();
throw new MqttConnectingFailedException(response.ConnectReturnCode);
}
if (_options.KeepAlivePeriod != TimeSpan.Zero)
{
StartSendKeepAliveMessages();
}
MqttTrace.Verbose(nameof(MqttClient), "MQTT connection with server established.");


if (_options.KeepAlivePeriod != TimeSpan.Zero)
IsConnected = true;
Connected?.Invoke(this, EventArgs.Empty);
}
catch (Exception)
{ {
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => SendKeepAliveMessagesAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
await DisconnectInternalAsync();
throw;
} }

Connected?.Invoke(this, EventArgs.Empty);
} }


public async Task DisconnectAsync() public async Task DisconnectAsync()
{ {
await SendAsync(new MqttDisconnectPacket());
await DisconnectInternalAsync();
try
{
await SendAsync(new MqttDisconnectPacket());
}
finally
{
await DisconnectInternalAsync();
}
} }


public Task<IList<MqttSubscribeResult>> SubscribeAsync(params TopicFilter[] topicFilters) public Task<IList<MqttSubscribeResult>> SubscribeAsync(params TopicFilter[] topicFilters)
@@ -105,6 +120,7 @@ namespace MQTTnet.Core.Client
{ {
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
if (!topicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3]."); if (!topicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3].");

ThrowIfNotConnected(); ThrowIfNotConnected();


var subscribePacket = new MqttSubscribePacket var subscribePacket = new MqttSubscribePacket
@@ -154,6 +170,7 @@ namespace MQTTnet.Core.Client


if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{ {
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await SendAsync(publishPacket); await SendAsync(publishPacket);
} }
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
@@ -164,8 +181,8 @@ namespace MQTTnet.Core.Client
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{ {
publishPacket.PacketIdentifier = GetNewPacketIdentifier(); publishPacket.PacketIdentifier = GetNewPacketIdentifier();
await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket);
await SendAsync(publishPacket.CreateResponse<MqttPubCompPacket>());
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket);
await SendAndReceiveAsync<MqttPubCompPacket>(pubRecPacket.CreateResponse<MqttPubRelPacket>());
} }
} }


@@ -180,8 +197,9 @@ namespace MQTTnet.Core.Client
{ {
await _adapter.DisconnectAsync(); await _adapter.DisconnectAsync();
} }
catch
catch (Exception exception)
{ {
MqttTrace.Warning(nameof(MqttClient), exception, "Error while disconnecting.");
} }
finally finally
{ {
@@ -190,7 +208,12 @@ namespace MQTTnet.Core.Client
_cancellationTokenSource = null; _cancellationTokenSource = null;


IsConnected = false; IsConnected = false;
Disconnected?.Invoke(this, EventArgs.Empty);

if (!_disconnectedEventSuspended)
{
_disconnectedEventSuspended = true;
Disconnected?.Invoke(this, EventArgs.Empty);
}
} }
} }


@@ -208,14 +231,12 @@ namespace MQTTnet.Core.Client
return DisconnectAsync(); return DisconnectAsync();
} }


var publishPacket = mqttPacket as MqttPublishPacket;
if (publishPacket != null)
if (mqttPacket is MqttPublishPacket publishPacket)
{ {
return ProcessReceivedPublishPacket(publishPacket); return ProcessReceivedPublishPacket(publishPacket);
} }


var pubRelPacket = mqttPacket as MqttPubRelPacket;
if (pubRelPacket != null)
if (mqttPacket is MqttPubRelPacket pubRelPacket)
{ {
return ProcessReceivedPubRelPacket(pubRelPacket); return ProcessReceivedPubRelPacket(pubRelPacket);
} }
@@ -232,13 +253,16 @@ namespace MQTTnet.Core.Client


private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket) private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
{ {
if (publishPacket.QualityOfServiceLevel != MqttQualityOfServiceLevel.AtMostOnce)
var applicationMessage = publishPacket.ToApplicationMessage();

try
{ {
_processedPublishPackets.Add(publishPacket.PacketIdentifier);
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(applicationMessage));
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttClient), exception, "Unhandled exception while handling application message.");
} }

var applicationMessage = publishPacket.ToApplicationMessage();
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(applicationMessage));
} }


private Task ProcessReceivedPublishPacket(MqttPublishPacket publishPacket) private Task ProcessReceivedPublishPacket(MqttPublishPacket publishPacket)
@@ -257,24 +281,27 @@ namespace MQTTnet.Core.Client


if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{ {
_pendingExactlyOncePublishPackets[publishPacket.PacketIdentifier] = publishPacket;
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
lock (_unacknowledgedPublishPackets)
{
_unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier);
}

FireApplicationMessageReceivedEvent(publishPacket);
return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }); return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
} }


throw new InvalidOperationException();
throw new MqttCommunicationException("Received a not supported QoS level.");
} }


private async Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket) private async Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
{ {
MqttPublishPacket originalPublishPacket;
if (!_pendingExactlyOncePublishPackets.TryRemove(pubRelPacket.PacketIdentifier, out originalPublishPacket))
lock (_unacknowledgedPublishPackets)
{ {
throw new MqttCommunicationException();
_unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier);
} }


await SendAsync(originalPublishPacket.CreateResponse<MqttPubCompPacket>());

FireApplicationMessageReceivedEvent(originalPublishPacket);
await SendAsync(pubRelPacket.CreateResponse<MqttPubCompPacket>());
} }


private Task SendAsync(MqttBasePacket packet) private Task SendAsync(MqttBasePacket packet)
@@ -292,18 +319,15 @@ namespace MQTTnet.Core.Client
return false; return false;
} }


var pi1 = requestPacket as IPacketWithIdentifier;
var pi2 = p as IPacketWithIdentifier;
var pi1 = requestPacket as IMqttPacketWithIdentifier;
var pi2 = p as IMqttPacketWithIdentifier;


if (pi1 != null && pi2 != null)
if (pi1 == null || pi2 == null)
{ {
if (pi1.PacketIdentifier != pi2.PacketIdentifier)
{
return false;
}
return true;
} }


return true;
return pi1.PacketIdentifier == pi2.PacketIdentifier;
} }


await _adapter.SendPacketAsync(requestPacket, _options.DefaultCommunicationTimeout); await _adapter.SendPacketAsync(requestPacket, _options.DefaultCommunicationTimeout);
@@ -330,15 +354,16 @@ namespace MQTTnet.Core.Client
catch (MqttCommunicationException exception) catch (MqttCommunicationException exception)
{ {
MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication error while receiving packets."); MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication error while receiving packets.");
await DisconnectInternalAsync();
} }
catch (Exception exception) catch (Exception exception)
{ {
MqttTrace.Warning(nameof(MqttClient), exception, "Error while sending/receiving keep alive packets."); MqttTrace.Warning(nameof(MqttClient), exception, "Error while sending/receiving keep alive packets.");
await DisconnectInternalAsync();
} }
finally finally
{ {
MqttTrace.Information(nameof(MqttClient), "Stopped sending keep alive packets."); MqttTrace.Information(nameof(MqttClient), "Stopped sending keep alive packets.");
await DisconnectInternalAsync();
} }
} }


@@ -349,27 +374,47 @@ namespace MQTTnet.Core.Client
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var mqttPacket = await _adapter.ReceivePacketAsync(TimeSpan.Zero);
MqttTrace.Information(nameof(MqttClient), $"Received <<< {mqttPacket}");
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero);
MqttTrace.Information(nameof(MqttClient), $"Received <<< {packet}");


#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => ProcessReceivedPacketAsync(mqttPacket), cancellationToken);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
StartProcessReceivedPacket(packet, cancellationToken);
} }
} }
catch (MqttCommunicationException exception) catch (MqttCommunicationException exception)
{ {
MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication error while receiving packets.");
MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets.");
await DisconnectInternalAsync();
} }
catch (Exception exception) catch (Exception exception)
{ {
MqttTrace.Error(nameof(MqttClient), exception, "Error while receiving packets.");
MqttTrace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets.");
await DisconnectInternalAsync();
} }
finally finally
{ {
MqttTrace.Information(nameof(MqttClient), "Stopped receiving packets."); MqttTrace.Information(nameof(MqttClient), "Stopped receiving packets.");
await DisconnectInternalAsync();
} }
} }

private void StartProcessReceivedPacket(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);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}

private void StartReceivePackets()
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => ReceivePackets(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}

private void StartSendKeepAliveMessages()
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => SendKeepAliveMessagesAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
} }
} }

+ 5
- 2
MQTTnet.Core/Client/MqttClientOptions.cs View File

@@ -1,4 +1,5 @@
using System; using System;
using MQTTnet.Core.Serializer;


namespace MQTTnet.Core.Client namespace MQTTnet.Core.Client
{ {
@@ -7,8 +8,8 @@ namespace MQTTnet.Core.Client
public string Server { get; set; } public string Server { get; set; }


public int? Port { get; set; } public int? Port { get; set; }
public MqttClientTlsOptions TlsOptions { get; } = new MqttClientTlsOptions();
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();


public string UserName { get; set; } public string UserName { get; set; }


@@ -21,5 +22,7 @@ namespace MQTTnet.Core.Client
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5); public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5);


public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10); public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);

public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
} }
} }

+ 6
- 1
MQTTnet.Core/Exceptions/MqttCommunicationException.cs View File

@@ -4,7 +4,7 @@ namespace MQTTnet.Core.Exceptions
{ {
public class MqttCommunicationException : Exception public class MqttCommunicationException : Exception
{ {
public MqttCommunicationException()
protected MqttCommunicationException()
{ {
} }


@@ -17,5 +17,10 @@ namespace MQTTnet.Core.Exceptions
: base(message) : base(message)
{ {
} }

public MqttCommunicationException(string message, Exception innerException)
: base(message, innerException)
{
}
} }
} }

+ 2
- 2
MQTTnet.Core/MQTTnet.Core.csproj View File

@@ -16,8 +16,8 @@
<PackageIconUrl></PackageIconUrl> <PackageIconUrl></PackageIconUrl>
<RepositoryUrl></RepositoryUrl> <RepositoryUrl></RepositoryUrl>
<PackageTags></PackageTags> <PackageTags></PackageTags>
<FileVersion>2.1.4.0</FileVersion>
<AssemblyVersion>2.1.4.0</AssemblyVersion>
<FileVersion>2.1.5.1</FileVersion>
<AssemblyVersion>2.1.5.1</AssemblyVersion>
<PackageLicenseUrl></PackageLicenseUrl> <PackageLicenseUrl></PackageLicenseUrl>
</PropertyGroup> </PropertyGroup>




MQTTnet.Core/Packets/IPacketWithPacketIdentifier.cs → MQTTnet.Core/Packets/IMqttPacketWithIdentifier.cs View File

@@ -1,6 +1,6 @@
namespace MQTTnet.Core.Packets namespace MQTTnet.Core.Packets
{ {
public interface IPacketWithIdentifier
public interface IMqttPacketWithIdentifier
{ {
ushort PacketIdentifier { get; set; } ushort PacketIdentifier { get; set; }
} }

+ 1
- 20
MQTTnet.Core/Packets/MqttBasePacket.cs View File

@@ -1,25 +1,6 @@
using System;

namespace MQTTnet.Core.Packets
namespace MQTTnet.Core.Packets
{ {
public abstract class MqttBasePacket public abstract class MqttBasePacket
{ {
public TResponsePacket CreateResponse<TResponsePacket>()
{
var responsePacket = Activator.CreateInstance<TResponsePacket>();
var responsePacketWithIdentifier = responsePacket as IPacketWithIdentifier;
if (responsePacketWithIdentifier != null)
{
var requestPacketWithIdentifier = this as IPacketWithIdentifier;
if (requestPacketWithIdentifier == null)
{
throw new InvalidOperationException("Response packet has PacketIdentifier but request packet does not.");
}

responsePacketWithIdentifier.PacketIdentifier = requestPacketWithIdentifier.PacketIdentifier;
}

return responsePacket;
}
} }
} }

+ 1
- 1
MQTTnet.Core/Packets/MqttBasePublishPacket.cs View File

@@ -1,6 +1,6 @@
namespace MQTTnet.Core.Packets namespace MQTTnet.Core.Packets
{ {
public class MqttBasePublishPacket : MqttBasePacket, IPacketWithIdentifier
public class MqttBasePublishPacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort PacketIdentifier { get; set; }
} }


+ 5
- 1
MQTTnet.Core/Packets/MqttConnectPacket.cs View File

@@ -1,7 +1,11 @@
namespace MQTTnet.Core.Packets
using MQTTnet.Core.Serializer;

namespace MQTTnet.Core.Packets
{ {
public sealed class MqttConnectPacket: MqttBasePacket public sealed class MqttConnectPacket: MqttBasePacket
{ {
public MqttProtocolVersion ProtocolVersion { get; set; }

public string ClientId { get; set; } public string ClientId { get; set; }


public string Username { get; set; } public string Username { get; set; }


+ 27
- 0
MQTTnet.Core/Packets/MqttPacketExtensions.cs View File

@@ -0,0 +1,27 @@
using System;

namespace MQTTnet.Core.Packets
{
public static class MqttPacketExtensions
{
public static TResponsePacket CreateResponse<TResponsePacket>(this MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));

var responsePacket = Activator.CreateInstance<TResponsePacket>();

if (responsePacket is IMqttPacketWithIdentifier responsePacketWithIdentifier)
{
var requestPacketWithIdentifier = packet as IMqttPacketWithIdentifier;
if (requestPacketWithIdentifier == null)
{
throw new InvalidOperationException("Response packet has PacketIdentifier but request packet does not.");
}

responsePacketWithIdentifier.PacketIdentifier = requestPacketWithIdentifier.PacketIdentifier;
}

return responsePacket;
}
}
}

+ 1
- 1
MQTTnet.Core/Packets/MqttSubAckPacket.cs View File

@@ -4,7 +4,7 @@ using MQTTnet.Core.Protocol;


namespace MQTTnet.Core.Packets namespace MQTTnet.Core.Packets
{ {
public sealed class MqttSubAckPacket : MqttBasePacket, IPacketWithIdentifier
public sealed class MqttSubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort PacketIdentifier { get; set; }




+ 1
- 1
MQTTnet.Core/Packets/MqttSubscribePacket.cs View File

@@ -3,7 +3,7 @@ using System.Linq;


namespace MQTTnet.Core.Packets namespace MQTTnet.Core.Packets
{ {
public sealed class MqttSubscribePacket : MqttBasePacket, IPacketWithIdentifier
public sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort PacketIdentifier { get; set; }


+ 1
- 1
MQTTnet.Core/Packets/MqttUnsubAckPacket.cs View File

@@ -1,6 +1,6 @@
namespace MQTTnet.Core.Packets namespace MQTTnet.Core.Packets
{ {
public sealed class MqttUnsubAckPacket : MqttBasePacket, IPacketWithIdentifier
public sealed class MqttUnsubAckPacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort PacketIdentifier { get; set; }
} }


+ 1
- 1
MQTTnet.Core/Packets/MqttUnsubscribe.cs View File

@@ -2,7 +2,7 @@


namespace MQTTnet.Core.Packets namespace MQTTnet.Core.Packets
{ {
public sealed class MqttUnsubscribePacket : MqttBasePacket, IPacketWithIdentifier
public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort PacketIdentifier { get; set; }


+ 2
- 2
MQTTnet.Core/Serializer/ByteReader.cs View File

@@ -24,7 +24,7 @@ namespace MQTTnet.Core.Serializer
return result; return result;
} }


public byte Read(int count)
public int Read(int count)
{ {
if (_index + count > 8) if (_index + count > 8)
{ {
@@ -42,7 +42,7 @@ namespace MQTTnet.Core.Serializer
_index++; _index++;
} }


return (byte)result;
return result;
} }
} }
} }

+ 2
- 0
MQTTnet.Core/Serializer/IMqttPacketSerializer.cs View File

@@ -6,6 +6,8 @@ namespace MQTTnet.Core.Serializer
{ {
public interface IMqttPacketSerializer public interface IMqttPacketSerializer
{ {
MqttProtocolVersion ProtocolVersion { get; set; }

Task SerializeAsync(MqttBasePacket mqttPacket, IMqttCommunicationChannel destination); Task SerializeAsync(MqttBasePacket mqttPacket, IMqttCommunicationChannel destination);


Task<MqttBasePacket> DeserializeAsync(IMqttCommunicationChannel source); Task<MqttBasePacket> DeserializeAsync(IMqttCommunicationChannel source);


+ 1
- 1
MQTTnet.Core/Serializer/MqttPacketReader.cs View File

@@ -10,7 +10,7 @@ namespace MQTTnet.Core.Serializer
{ {
public sealed class MqttPacketReader : IDisposable public sealed class MqttPacketReader : IDisposable
{ {
private readonly MemoryStream _remainingData = new MemoryStream();
private readonly MemoryStream _remainingData = new MemoryStream(1024);
private readonly IMqttCommunicationChannel _source; private readonly IMqttCommunicationChannel _source;


private int _remainingLength; private int _remainingLength;


MQTTnet.Core/Serializer/DefaultMqttV311PacketSerializer.cs → MQTTnet.Core/Serializer/MqttPacketSerializer.cs View File

@@ -9,93 +9,84 @@ using MQTTnet.Core.Protocol;


namespace MQTTnet.Core.Serializer namespace MQTTnet.Core.Serializer
{ {
public sealed class DefaultMqttV311PacketSerializer : IMqttPacketSerializer
public sealed class MqttPacketSerializer : IMqttPacketSerializer
{ {
private static byte[] ProtocolVersionV311Name { get; } = Encoding.UTF8.GetBytes("MQTT");
private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIs");

public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;

public Task SerializeAsync(MqttBasePacket packet, IMqttCommunicationChannel destination) public Task SerializeAsync(MqttBasePacket packet, IMqttCommunicationChannel destination)
{ {
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
if (destination == null) throw new ArgumentNullException(nameof(destination)); if (destination == null) throw new ArgumentNullException(nameof(destination));


var connectPacket = packet as MqttConnectPacket;
if (connectPacket != null)
if (packet is MqttConnectPacket connectPacket)
{ {
return SerializeAsync(connectPacket, destination); return SerializeAsync(connectPacket, destination);
} }


var connAckPacket = packet as MqttConnAckPacket;
if (connAckPacket != null)
if (packet is MqttConnAckPacket connAckPacket)
{ {
return SerializeAsync(connAckPacket, destination); return SerializeAsync(connAckPacket, destination);
} }


var disconnectPacket = packet as MqttDisconnectPacket;
if (disconnectPacket != null)
if (packet is MqttDisconnectPacket disconnectPacket)
{ {
return SerializeAsync(disconnectPacket, destination); return SerializeAsync(disconnectPacket, destination);
} }


var pingReqPacket = packet as MqttPingReqPacket;
if (pingReqPacket != null)
if (packet is MqttPingReqPacket pingReqPacket)
{ {
return SerializeAsync(pingReqPacket, destination); return SerializeAsync(pingReqPacket, destination);
} }


var pingRespPacket = packet as MqttPingRespPacket;
if (pingRespPacket != null)
if (packet is MqttPingRespPacket pingRespPacket)
{ {
return SerializeAsync(pingRespPacket, destination); return SerializeAsync(pingRespPacket, destination);
} }


var publishPacket = packet as MqttPublishPacket;
if (publishPacket != null)
if (packet is MqttPublishPacket publishPacket)
{ {
return SerializeAsync(publishPacket, destination); return SerializeAsync(publishPacket, destination);
} }


var pubAckPacket = packet as MqttPubAckPacket;
if (pubAckPacket != null)
if (packet is MqttPubAckPacket pubAckPacket)
{ {
return SerializeAsync(pubAckPacket, destination); return SerializeAsync(pubAckPacket, destination);
} }


var pubRecPacket = packet as MqttPubRecPacket;
if (pubRecPacket != null)
if (packet is MqttPubRecPacket pubRecPacket)
{ {
return SerializeAsync(pubRecPacket, destination); return SerializeAsync(pubRecPacket, destination);
} }


var pubRelPacket = packet as MqttPubRelPacket;
if (pubRelPacket != null)
if (packet is MqttPubRelPacket pubRelPacket)
{ {
return SerializeAsync(pubRelPacket, destination); return SerializeAsync(pubRelPacket, destination);
} }


var pubCompPacket = packet as MqttPubCompPacket;
if (pubCompPacket != null)
if (packet is MqttPubCompPacket pubCompPacket)
{ {
return SerializeAsync(pubCompPacket, destination); return SerializeAsync(pubCompPacket, destination);
} }


var subscribePacket = packet as MqttSubscribePacket;
if (subscribePacket != null)
if (packet is MqttSubscribePacket subscribePacket)
{ {
return SerializeAsync(subscribePacket, destination); return SerializeAsync(subscribePacket, destination);
} }


var subAckPacket = packet as MqttSubAckPacket;
if (subAckPacket != null)
if (packet is MqttSubAckPacket subAckPacket)
{ {
return SerializeAsync(subAckPacket, destination); return SerializeAsync(subAckPacket, destination);
} }


var unsubscribePacket = packet as MqttUnsubscribePacket;
if (unsubscribePacket != null)
if (packet is MqttUnsubscribePacket unsubscribePacket)
{ {
return SerializeAsync(unsubscribePacket, destination); return SerializeAsync(unsubscribePacket, destination);
} }


var unsubAckPacket = packet as MqttUnsubAckPacket;
if (unsubAckPacket != null)
if (packet is MqttUnsubAckPacket unsubAckPacket)
{ {
return SerializeAsync(unsubAckPacket, destination); return SerializeAsync(unsubAckPacket, destination);
} }
@@ -206,7 +197,7 @@ namespace MQTTnet.Core.Serializer
} }
} }


private async Task<MqttBasePacket> DeserializeUnsubscribeAsync(MqttPacketReader reader)
private static async Task<MqttBasePacket> DeserializeUnsubscribeAsync(MqttPacketReader reader)
{ {
var packet = new MqttUnsubscribePacket var packet = new MqttUnsubscribePacket
{ {
@@ -221,7 +212,7 @@ namespace MQTTnet.Core.Serializer
return packet; return packet;
} }


private async Task<MqttBasePacket> DeserializeSubscribeAsync(MqttPacketReader reader)
private static async Task<MqttBasePacket> DeserializeSubscribeAsync(MqttPacketReader reader)
{ {
var packet = new MqttSubscribePacket var packet = new MqttSubscribePacket
{ {
@@ -238,7 +229,7 @@ namespace MQTTnet.Core.Serializer
return packet; return packet;
} }


private async Task<MqttBasePacket> DeserializePublishAsync(MqttPacketReader reader)
private static async Task<MqttBasePacket> DeserializePublishAsync(MqttPacketReader reader)
{ {
var fixedHeader = new ByteReader(reader.FixedHeader); var fixedHeader = new ByteReader(reader.FixedHeader);
var retain = fixedHeader.Read(); var retain = fixedHeader.Read();
@@ -266,18 +257,24 @@ namespace MQTTnet.Core.Serializer
return packet; return packet;
} }


private async Task<MqttBasePacket> DeserializeConnectAsync(MqttPacketReader reader)
private static async Task<MqttBasePacket> DeserializeConnectAsync(MqttPacketReader reader)
{ {
var packet = new MqttConnectPacket();

await reader.ReadRemainingDataByteAsync();
await reader.ReadRemainingDataByteAsync();
await reader.ReadRemainingDataAsync(2); // Skip 2 bytes


MqttProtocolVersion protocolVersion;
var protocolName = await reader.ReadRemainingDataAsync(4); var protocolName = await reader.ReadRemainingDataAsync(4);

if (Encoding.UTF8.GetString(protocolName, 0, protocolName.Length) != "MQTT")
if (protocolName.SequenceEqual(ProtocolVersionV310Name))
{
await reader.ReadRemainingDataAsync(2);
protocolVersion = MqttProtocolVersion.V310;
}
else if (protocolName.SequenceEqual(ProtocolVersionV311Name))
{ {
throw new MqttProtocolViolationException("Protocol name is not 'MQTT'.");
protocolVersion = MqttProtocolVersion.V311;
}
else
{
throw new MqttProtocolViolationException("Protocol name is not supported.");
} }


var protocolLevel = await reader.ReadRemainingDataByteAsync(); var protocolLevel = await reader.ReadRemainingDataByteAsync();
@@ -285,7 +282,13 @@ namespace MQTTnet.Core.Serializer


var connectFlagsReader = new ByteReader(connectFlags); var connectFlagsReader = new ByteReader(connectFlags);
connectFlagsReader.Read(); // Reserved. connectFlagsReader.Read(); // Reserved.
packet.CleanSession = connectFlagsReader.Read();

var packet = new MqttConnectPacket
{
ProtocolVersion = protocolVersion,
CleanSession = connectFlagsReader.Read()
};

var willFlag = connectFlagsReader.Read(); var willFlag = connectFlagsReader.Read();
var willQoS = connectFlagsReader.Read(2); var willQoS = connectFlagsReader.Read(2);
var willRetain = connectFlagsReader.Read(); var willRetain = connectFlagsReader.Read();
@@ -318,7 +321,7 @@ namespace MQTTnet.Core.Serializer
return packet; return packet;
} }


private async Task<MqttBasePacket> DeserializeSubAck(MqttPacketReader reader)
private static async Task<MqttBasePacket> DeserializeSubAck(MqttPacketReader reader)
{ {
var packet = new MqttSubAckPacket var packet = new MqttSubAckPacket
{ {
@@ -333,7 +336,7 @@ namespace MQTTnet.Core.Serializer
return packet; return packet;
} }


private async Task<MqttBasePacket> DeserializeConnAck(MqttPacketReader reader)
private static async Task<MqttBasePacket> DeserializeConnAck(MqttPacketReader reader)
{ {
var variableHeader1 = await reader.ReadRemainingDataByteAsync(); var variableHeader1 = await reader.ReadRemainingDataByteAsync();
var variableHeader2 = await reader.ReadRemainingDataByteAsync(); var variableHeader2 = await reader.ReadRemainingDataByteAsync();
@@ -347,7 +350,7 @@ namespace MQTTnet.Core.Serializer
return packet; return packet;
} }


private void ValidateConnectPacket(MqttConnectPacket packet)
private static void ValidateConnectPacket(MqttConnectPacket packet)
{ {
if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession) if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession)
{ {
@@ -355,7 +358,7 @@ namespace MQTTnet.Core.Serializer
} }
} }


private void ValidatePublishPacket(MqttPublishPacket packet)
private static void ValidatePublishPacket(MqttPublishPacket packet)
{ {
if (packet.QualityOfServiceLevel == 0 && packet.Dup) if (packet.QualityOfServiceLevel == 0 && packet.Dup)
{ {
@@ -363,8 +366,6 @@ namespace MQTTnet.Core.Serializer
} }
} }


private static readonly byte[] MqttPrefix = Encoding.UTF8.GetBytes("MQTT");

private Task SerializeAsync(MqttConnectPacket packet, IMqttCommunicationChannel destination) private Task SerializeAsync(MqttConnectPacket packet, IMqttCommunicationChannel destination)
{ {
ValidateConnectPacket(packet); ValidateConnectPacket(packet);
@@ -373,9 +374,19 @@ namespace MQTTnet.Core.Serializer
{ {
// Write variable header // Write variable header
output.Write(0x00, 0x04); // 3.1.2.1 Protocol Name output.Write(0x00, 0x04); // 3.1.2.1 Protocol Name
output.Write(MqttPrefix);
output.Write(0x04); // 3.1.2.2 Protocol Level

if (ProtocolVersion == MqttProtocolVersion.V311)
{
output.Write(ProtocolVersionV311Name);
output.Write(0x04); // 3.1.2.2 Protocol Level (4)
}
else
{
output.Write(ProtocolVersionV310Name);
output.Write(0x64);
output.Write(0x70);
output.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
connectFlags.Write(false); // Reserved connectFlags.Write(false); // Reserved
connectFlags.Write(packet.CleanSession); connectFlags.Write(packet.CleanSession);
@@ -425,8 +436,12 @@ namespace MQTTnet.Core.Serializer
using (var output = new MqttPacketWriter()) using (var output = new MqttPacketWriter())
{ {
var connectAcknowledgeFlags = new ByteWriter(); var connectAcknowledgeFlags = new ByteWriter();
connectAcknowledgeFlags.Write(packet.IsSessionPresent);


if (ProtocolVersion == MqttProtocolVersion.V311)
{
connectAcknowledgeFlags.Write(packet.IsSessionPresent);
}
output.Write(connectAcknowledgeFlags); output.Write(connectAcknowledgeFlags);
output.Write((byte)packet.ConnectReturnCode); output.Write((byte)packet.ConnectReturnCode);


@@ -435,22 +450,33 @@ namespace MQTTnet.Core.Serializer
} }
} }


private Task SerializeAsync(MqttDisconnectPacket packet, IMqttCommunicationChannel destination)
private static async Task SerializeAsync(MqttPubRelPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);

output.InjectFixedHeader(MqttControlPacketType.PubRel, 0x02);
await output.WriteToAsync(destination);
}
}

private static Task SerializeAsync(MqttDisconnectPacket packet, IMqttCommunicationChannel destination)
{ {
return SerializeEmptyPacketAsync(MqttControlPacketType.Disconnect, destination); return SerializeEmptyPacketAsync(MqttControlPacketType.Disconnect, destination);
} }


private Task SerializeAsync(MqttPingReqPacket packet, IMqttCommunicationChannel destination)
private static Task SerializeAsync(MqttPingReqPacket packet, IMqttCommunicationChannel destination)
{ {
return SerializeEmptyPacketAsync(MqttControlPacketType.PingReq, destination); return SerializeEmptyPacketAsync(MqttControlPacketType.PingReq, destination);
} }


private Task SerializeAsync(MqttPingRespPacket packet, IMqttCommunicationChannel destination)
private static Task SerializeAsync(MqttPingRespPacket packet, IMqttCommunicationChannel destination)
{ {
return SerializeEmptyPacketAsync(MqttControlPacketType.PingResp, destination); return SerializeEmptyPacketAsync(MqttControlPacketType.PingResp, destination);
} }


private Task SerializeAsync(MqttPublishPacket packet, IMqttCommunicationChannel destination)
private static Task SerializeAsync(MqttPublishPacket packet, IMqttCommunicationChannel destination)
{ {
ValidatePublishPacket(packet); ValidatePublishPacket(packet);


@@ -485,7 +511,7 @@ namespace MQTTnet.Core.Serializer
} }
} }


private Task SerializeAsync(MqttPubAckPacket packet, IMqttCommunicationChannel destination)
private static Task SerializeAsync(MqttPubAckPacket packet, IMqttCommunicationChannel destination)
{ {
using (var output = new MqttPacketWriter()) using (var output = new MqttPacketWriter())
{ {
@@ -496,7 +522,7 @@ namespace MQTTnet.Core.Serializer
} }
} }


private Task SerializeAsync(MqttPubRecPacket packet, IMqttCommunicationChannel destination)
private static Task SerializeAsync(MqttPubRecPacket packet, IMqttCommunicationChannel destination)
{ {
using (var output = new MqttPacketWriter()) using (var output = new MqttPacketWriter())
{ {
@@ -507,18 +533,7 @@ namespace MQTTnet.Core.Serializer
} }
} }


private async Task SerializeAsync(MqttPubRelPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);

output.InjectFixedHeader(MqttControlPacketType.PubRel, 0x02);
await output.WriteToAsync(destination);
}
}

private Task SerializeAsync(MqttPubCompPacket packet, IMqttCommunicationChannel destination)
private static Task SerializeAsync(MqttPubCompPacket packet, IMqttCommunicationChannel destination)
{ {
using (var output = new MqttPacketWriter()) using (var output = new MqttPacketWriter())
{ {
@@ -529,7 +544,7 @@ namespace MQTTnet.Core.Serializer
} }
} }


private Task SerializeAsync(MqttSubscribePacket packet, IMqttCommunicationChannel destination)
private static Task SerializeAsync(MqttSubscribePacket packet, IMqttCommunicationChannel destination)
{ {
using (var output = new MqttPacketWriter()) using (var output = new MqttPacketWriter())
{ {
@@ -549,7 +564,7 @@ namespace MQTTnet.Core.Serializer
} }
} }


private Task SerializeAsync(MqttSubAckPacket packet, IMqttCommunicationChannel destination)
private static Task SerializeAsync(MqttSubAckPacket packet, IMqttCommunicationChannel destination)
{ {
using (var output = new MqttPacketWriter()) using (var output = new MqttPacketWriter())
{ {
@@ -568,7 +583,7 @@ namespace MQTTnet.Core.Serializer
} }
} }


private Task SerializeAsync(MqttUnsubscribePacket packet, IMqttCommunicationChannel destination)
private static Task SerializeAsync(MqttUnsubscribePacket packet, IMqttCommunicationChannel destination)
{ {
using (var output = new MqttPacketWriter()) using (var output = new MqttPacketWriter())
{ {
@@ -587,7 +602,7 @@ namespace MQTTnet.Core.Serializer
} }
} }


private Task SerializeAsync(MqttUnsubAckPacket packet, IMqttCommunicationChannel destination)
private static Task SerializeAsync(MqttUnsubAckPacket packet, IMqttCommunicationChannel destination)
{ {
using (var output = new MqttPacketWriter()) using (var output = new MqttPacketWriter())
{ {
@@ -598,7 +613,7 @@ namespace MQTTnet.Core.Serializer
} }
} }


private Task SerializeEmptyPacketAsync(MqttControlPacketType type, IMqttCommunicationChannel destination)
private static Task SerializeEmptyPacketAsync(MqttControlPacketType type, IMqttCommunicationChannel destination)
{ {
using (var output = new MqttPacketWriter()) using (var output = new MqttPacketWriter())
{ {

+ 36
- 36
MQTTnet.Core/Serializer/MqttPacketWriter.cs View File

@@ -9,46 +9,13 @@ namespace MQTTnet.Core.Serializer
{ {
public sealed class MqttPacketWriter : IDisposable public sealed class MqttPacketWriter : IDisposable
{ {
private readonly MemoryStream _buffer = new MemoryStream(512);

public void InjectFixedHeader(byte fixedHeader)
{
if (_buffer.Length == 0)
{
Write(fixedHeader);
Write(0);
return;
}

var backupBuffer = _buffer.ToArray();
var remainingLength = (int)_buffer.Length;

_buffer.SetLength(0);

_buffer.WriteByte(fixedHeader);

// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
var x = remainingLength;
do
{
var encodedByte = x % 128;
x = x / 128;
if (x > 0)
{
encodedByte = encodedByte | 128;
}

_buffer.WriteByte((byte)encodedByte);
} while (x > 0);

_buffer.Write(backupBuffer, 0, backupBuffer.Length);
}
private readonly MemoryStream _buffer = new MemoryStream(1024);


public void InjectFixedHeader(MqttControlPacketType packetType, byte flags = 0) public void InjectFixedHeader(MqttControlPacketType packetType, byte flags = 0)
{ {
var fixedHeader = (byte)((byte)packetType << 4);
var fixedHeader = (int)packetType << 4;
fixedHeader |= flags; fixedHeader |= flags;
InjectFixedHeader(fixedHeader);
InjectFixedHeader((byte)fixedHeader);
} }


public void Write(byte value) public void Write(byte value)
@@ -101,5 +68,38 @@ namespace MQTTnet.Core.Serializer
{ {
_buffer?.Dispose(); _buffer?.Dispose();
} }

private void InjectFixedHeader(byte fixedHeader)
{
if (_buffer.Length == 0)
{
Write(fixedHeader);
Write(0);
return;
}

var backupBuffer = _buffer.ToArray();
var remainingLength = (int)_buffer.Length;

_buffer.SetLength(0);

_buffer.WriteByte(fixedHeader);

// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
var x = remainingLength;
do
{
var encodedByte = x % 128;
x = x / 128;
if (x > 0)
{
encodedByte = encodedByte | 128;
}

_buffer.WriteByte((byte)encodedByte);
} while (x > 0);

_buffer.Write(backupBuffer, 0, backupBuffer.Length);
}
} }
} }

+ 8
- 0
MQTTnet.Core/Serializer/MqttProtocolVersion.cs View File

@@ -0,0 +1,8 @@
namespace MQTTnet.Core.Serializer
{
public enum MqttProtocolVersion
{
V311,
V310
}
}

+ 11
- 0
MQTTnet.Core/Server/ConnectedMqttClient.cs View File

@@ -0,0 +1,11 @@
using MQTTnet.Core.Serializer;

namespace MQTTnet.Core.Server
{
public class ConnectedMqttClient
{
public string ClientId { get; set; }

public MqttProtocolVersion ProtocolVersion { get; set; }
}
}

+ 18
- 0
MQTTnet.Core/Server/IMqttServer.cs View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using MQTTnet.Core.Adapter;

namespace MQTTnet.Core.Server
{
public interface IMqttServer
{
event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
event EventHandler<MqttClientConnectedEventArgs> ClientConnected;

IList<ConnectedMqttClient> GetConnectedClients();
void InjectClient(string identifier, IMqttCommunicationAdapter adapter);
void Publish(MqttApplicationMessage applicationMessage);
void Start();
void Stop();
}
}

+ 3
- 4
MQTTnet.Core/Server/MqttClientMessageQueue.cs View File

@@ -35,7 +35,7 @@ namespace MQTTnet.Core.Server
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter)); _adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();


Task.Run(() => SendPendingPublishPacketsAsync(_cancellationTokenSource.Token));
Task.Run(() => SendPendingPublishPacketsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
} }


public void Stop() public void Stop()
@@ -45,14 +45,13 @@ namespace MQTTnet.Core.Server
_cancellationTokenSource = null; _cancellationTokenSource = null;
} }


public void Enqueue(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
public void Enqueue(MqttPublishPacket publishPacket)
{ {
if (senderClientSession == null) throw new ArgumentNullException(nameof(senderClientSession));
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));


lock (_pendingPublishPackets) lock (_pendingPublishPackets)
{ {
_pendingPublishPackets.Add(new MqttClientPublishPacketContext(senderClientSession, publishPacket));
_pendingPublishPackets.Add(new MqttClientPublishPacketContext(publishPacket));
_gate.Set(); _gate.Set();
} }
} }


+ 1
- 4
MQTTnet.Core/Server/MqttClientPublishPacketContext.cs View File

@@ -5,14 +5,11 @@ namespace MQTTnet.Core.Server
{ {
public sealed class MqttClientPublishPacketContext public sealed class MqttClientPublishPacketContext
{ {
public MqttClientPublishPacketContext(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
public MqttClientPublishPacketContext(MqttPublishPacket publishPacket)
{ {
SenderClientSession = senderClientSession ?? throw new ArgumentNullException(nameof(senderClientSession));
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket)); PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
} }


public MqttClientSession SenderClientSession { get; }

public MqttPublishPacket PublishPacket { get; } public MqttPublishPacket PublishPacket { get; }


public int SendTries { get; set; } public int SendTries { get; set; }


+ 48
- 42
MQTTnet.Core/Server/MqttClientSession.cs View File

@@ -1,5 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Core.Adapter; using MQTTnet.Core.Adapter;
@@ -13,15 +13,14 @@ namespace MQTTnet.Core.Server
{ {
public sealed class MqttClientSession : IDisposable public sealed class MqttClientSession : IDisposable
{ {
private readonly ConcurrentDictionary<ushort, MqttPublishPacket> _pendingIncomingPublications = new ConcurrentDictionary<ushort, MqttPublishPacket>();
private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>();


private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager(); private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager();
private readonly MqttClientMessageQueue _messageQueue; private readonly MqttClientMessageQueue _messageQueue;
private readonly Action<MqttClientSession, MqttPublishPacket> _publishPacketReceivedCallback; private readonly Action<MqttClientSession, MqttPublishPacket> _publishPacketReceivedCallback;
private readonly MqttServerOptions _options; private readonly MqttServerOptions _options;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private IMqttCommunicationAdapter _adapter;
private string _identifier; private string _identifier;
private MqttApplicationMessage _willApplicationMessage; private MqttApplicationMessage _willApplicationMessage;


@@ -36,7 +35,9 @@ namespace MQTTnet.Core.Server


public string ClientId { get; } public string ClientId { get; }


public bool IsConnected => _adapter != null;
public bool IsConnected => Adapter != null;

public IMqttCommunicationAdapter Adapter { get; private set; }


public async Task RunAsync(string identifier, MqttApplicationMessage willApplicationMessage, IMqttCommunicationAdapter adapter) public async Task RunAsync(string identifier, MqttApplicationMessage willApplicationMessage, IMqttCommunicationAdapter adapter)
{ {
@@ -47,7 +48,7 @@ namespace MQTTnet.Core.Server
try try
{ {
_identifier = identifier; _identifier = identifier;
_adapter = adapter;
Adapter = adapter;
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();


_messageQueue.Start(adapter); _messageQueue.Start(adapter);
@@ -73,23 +74,22 @@ namespace MQTTnet.Core.Server


_messageQueue.Stop(); _messageQueue.Stop();
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
_adapter = null;
Adapter = null;


MqttTrace.Information(nameof(MqttClientSession), $"Client '{_identifier}': Disconnected."); MqttTrace.Information(nameof(MqttClientSession), $"Client '{_identifier}': Disconnected.");
} }
} }


public void EnqueuePublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
public void EnqueuePublishPacket(MqttPublishPacket publishPacket)
{ {
if (senderClientSession == null) throw new ArgumentNullException(nameof(senderClientSession));
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));


if (!_subscriptionsManager.IsTopicSubscribed(publishPacket))
if (!_subscriptionsManager.IsSubscribed(publishPacket))
{ {
return; return;
} }


_messageQueue.Enqueue(senderClientSession, publishPacket);
_messageQueue.Enqueue(publishPacket);
MqttTrace.Verbose(nameof(MqttClientSession), $"Client '{_identifier}: Enqueued pending publish packet."); MqttTrace.Verbose(nameof(MqttClientSession), $"Client '{_identifier}: Enqueued pending publish packet.");
} }


@@ -101,39 +101,40 @@ namespace MQTTnet.Core.Server


private Task HandleIncomingPacketAsync(MqttBasePacket packet) private Task HandleIncomingPacketAsync(MqttBasePacket packet)
{ {
var subscribePacket = packet as MqttSubscribePacket;
if (subscribePacket != null)
if (packet is MqttSubscribePacket subscribePacket)
{ {
return _adapter.SendPacketAsync(_subscriptionsManager.Subscribe(subscribePacket), _options.DefaultCommunicationTimeout);
return Adapter.SendPacketAsync(_subscriptionsManager.Subscribe(subscribePacket), _options.DefaultCommunicationTimeout);
} }


var unsubscribePacket = packet as MqttUnsubscribePacket;
if (unsubscribePacket != null)
if (packet is MqttUnsubscribePacket unsubscribePacket)
{ {
return _adapter.SendPacketAsync(_subscriptionsManager.Unsubscribe(unsubscribePacket), _options.DefaultCommunicationTimeout);
return Adapter.SendPacketAsync(_subscriptionsManager.Unsubscribe(unsubscribePacket), _options.DefaultCommunicationTimeout);
} }


var publishPacket = packet as MqttPublishPacket;
if (publishPacket != null)
if (packet is MqttPublishPacket publishPacket)
{ {
return HandleIncomingPublishPacketAsync(publishPacket); return HandleIncomingPublishPacketAsync(publishPacket);
} }


var pubRelPacket = packet as MqttPubRelPacket;
if (pubRelPacket != null)
if (packet is MqttPubRelPacket pubRelPacket)
{ {
return HandleIncomingPubRelPacketAsync(pubRelPacket); return HandleIncomingPubRelPacketAsync(pubRelPacket);
} }


var pubAckPacket = packet as MqttPubAckPacket;
if (pubAckPacket != null)
if (packet is MqttPubRecPacket pubRecPacket)
{ {
return HandleIncomingPubAckPacketAsync(pubAckPacket);
return Adapter.SendPacketAsync(pubRecPacket.CreateResponse<MqttPubRelPacket>(), _options.DefaultCommunicationTimeout);
}

if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
{
// Discard message.
return Task.FromResult((object)null);
} }


if (packet is MqttPingReqPacket) if (packet is MqttPingReqPacket)
{ {
return _adapter.SendPacketAsync(new MqttPingRespPacket(), _options.DefaultCommunicationTimeout);
return Adapter.SendPacketAsync(new MqttPingRespPacket(), _options.DefaultCommunicationTimeout);
} }


if (packet is MqttDisconnectPacket || packet is MqttConnectPacket) if (packet is MqttDisconnectPacket || packet is MqttConnectPacket)
@@ -148,39 +149,44 @@ namespace MQTTnet.Core.Server
return Task.FromResult((object)null); return Task.FromResult((object)null);
} }


private async Task HandleIncomingPubAckPacketAsync(MqttPubAckPacket pubAckPacket)
{
await Task.FromResult((object)null);
}

private async Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket)
private Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket)
{ {
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{ {
_publishPacketReceivedCallback(this, publishPacket); _publishPacketReceivedCallback(this, publishPacket);
return Task.FromResult(0);
} }
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)

if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{ {
await _adapter.SendPacketAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
_publishPacketReceivedCallback(this, publishPacket); _publishPacketReceivedCallback(this, publishPacket);
return Adapter.SendPacketAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
} }
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)

if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{ {
_pendingIncomingPublications[publishPacket.PacketIdentifier] = publishPacket;
await _adapter.SendPacketAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
lock (_unacknowledgedPublishPackets)
{
_unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier);
}

_publishPacketReceivedCallback(this, publishPacket);

return Adapter.SendPacketAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
} }

throw new MqttCommunicationException("Received a not supported QoS level.");
} }


private async Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket)
private Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket)
{ {
MqttPublishPacket publishPacket;
if (!_pendingIncomingPublications.TryRemove(pubRelPacket.PacketIdentifier, out publishPacket))
lock (_unacknowledgedPublishPackets)
{ {
return;
_unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier);
} }


await _adapter.SendPacketAsync(new MqttPubCompPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
_publishPacketReceivedCallback(this, publishPacket);
return Adapter.SendPacketAsync(new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
} }
} }
} }

+ 25
- 8
MQTTnet.Core/Server/MqttClientSessionsManager.cs View File

@@ -22,7 +22,7 @@ namespace MQTTnet.Core.Server
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
} }


public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;


public async Task RunClientSessionAsync(MqttClientConnectedEventArgs eventArgs) public async Task RunClientSessionAsync(MqttClientConnectedEventArgs eventArgs)
{ {
@@ -34,6 +34,9 @@ namespace MQTTnet.Core.Server
throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
} }


// Switch to the required protocol version before sending any response.
eventArgs.ClientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;

var connectReturnCode = ValidateConnection(connectPacket); var connectReturnCode = ValidateConnection(connectPacket);
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{ {
@@ -73,11 +76,15 @@ namespace MQTTnet.Core.Server
} }
} }


public IList<string> GetConnectedClients()
public IList<ConnectedMqttClient> GetConnectedClients()
{ {
lock (_syncRoot) lock (_syncRoot)
{ {
return _clientSessions.Where(s => s.Value.IsConnected).Select(s => s.Key).ToList();
return _clientSessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
{
ClientId = s.Value.ClientId,
ProtocolVersion = s.Value.Adapter.PacketSerializer.ProtocolVersion
}).ToList();
} }
} }


@@ -127,14 +134,24 @@ namespace MQTTnet.Core.Server
} }
} }


private void DispatchPublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
public void DispatchPublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
{ {
var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession.ClientId, publishPacket.ToApplicationMessage());
ApplicationMessageReceived?.Invoke(this, eventArgs);
try
{
var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, publishPacket.ToApplicationMessage());
ApplicationMessageReceived?.Invoke(this, eventArgs);
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttClientSessionsManager), exception, "Error while processing application message");
}


foreach (var clientSession in _clientSessions.Values.ToList())
lock (_syncRoot)
{ {
clientSession.EnqueuePublishPacket(senderClientSession, publishPacket);
foreach (var clientSession in _clientSessions.Values.ToList())
{
clientSession.EnqueuePublishPacket(publishPacket);
}
} }
} }
} }

+ 27
- 18
MQTTnet.Core/Server/MqttClientSubscriptionsManager.cs View File

@@ -1,5 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol; using MQTTnet.Core.Protocol;


@@ -7,17 +7,21 @@ namespace MQTTnet.Core.Server
{ {
public sealed class MqttClientSubscriptionsManager public sealed class MqttClientSubscriptionsManager
{ {
private readonly ConcurrentDictionary<string, MqttQualityOfServiceLevel> _subscribedTopics = new ConcurrentDictionary<string, MqttQualityOfServiceLevel>();
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscribedTopics = new Dictionary<string, MqttQualityOfServiceLevel>();


public MqttSubAckPacket Subscribe(MqttSubscribePacket subscribePacket) public MqttSubAckPacket Subscribe(MqttSubscribePacket subscribePacket)
{ {
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));


var responsePacket = subscribePacket.CreateResponse<MqttSubAckPacket>(); var responsePacket = subscribePacket.CreateResponse<MqttSubAckPacket>();
foreach (var topicFilter in subscribePacket.TopicFilters)

lock (_subscribedTopics)
{ {
_subscribedTopics[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
responsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS1); // TODO: Add support for QoS 2.
foreach (var topicFilter in subscribePacket.TopicFilters)
{
_subscribedTopics[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
responsePacket.SubscribeReturnCodes.Add(MqttSubscribeReturnCode.SuccessMaximumQoS1); // TODO: Add support for QoS 2.
}
} }


return responsePacket; return responsePacket;
@@ -27,32 +31,37 @@ namespace MQTTnet.Core.Server
{ {
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));


foreach (var topicFilter in unsubscribePacket.TopicFilters)
lock (_subscribedTopics)
{ {
MqttQualityOfServiceLevel _;
_subscribedTopics.TryRemove(topicFilter, out _);
foreach (var topicFilter in unsubscribePacket.TopicFilters)
{
_subscribedTopics.Remove(topicFilter);
}
} }


return unsubscribePacket.CreateResponse<MqttUnsubAckPacket>(); return unsubscribePacket.CreateResponse<MqttUnsubAckPacket>();
} }


public bool IsTopicSubscribed(MqttPublishPacket publishPacket)
public bool IsSubscribed(MqttPublishPacket publishPacket)
{ {
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));


foreach (var subscribedTopic in _subscribedTopics)
lock (_subscribedTopics)
{ {
if (!MqttTopicFilterComparer.IsMatch(publishPacket.Topic, subscribedTopic.Key))
foreach (var subscribedTopic in _subscribedTopics)
{ {
continue;
}
if (publishPacket.QualityOfServiceLevel > subscribedTopic.Value)
{
continue;
}


if (subscribedTopic.Value < publishPacket.QualityOfServiceLevel)
{
continue;
}
if (!MqttTopicFilterComparer.IsMatch(publishPacket.Topic, subscribedTopic.Key))
{
continue;
}


return true;
return true;
}
} }


return false; return false;


+ 10
- 2
MQTTnet.Core/Server/MqttServer.cs View File

@@ -4,10 +4,11 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Core.Adapter; using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Internal;


namespace MQTTnet.Core.Server namespace MQTTnet.Core.Server
{ {
public sealed class MqttServer
public sealed class MqttServer : IMqttServer
{ {
private readonly MqttClientSessionsManager _clientSessionsManager; private readonly MqttClientSessionsManager _clientSessionsManager;
private readonly ICollection<IMqttServerAdapter> _adapters; private readonly ICollection<IMqttServerAdapter> _adapters;
@@ -24,7 +25,7 @@ namespace MQTTnet.Core.Server
_clientSessionsManager.ApplicationMessageReceived += (s, e) => ApplicationMessageReceived?.Invoke(s, e); _clientSessionsManager.ApplicationMessageReceived += (s, e) => ApplicationMessageReceived?.Invoke(s, e);
} }


public IList<string> GetConnectedClients()
public IList<ConnectedMqttClient> GetConnectedClients()
{ {
return _clientSessionsManager.GetConnectedClients(); return _clientSessionsManager.GetConnectedClients();
} }
@@ -33,6 +34,13 @@ namespace MQTTnet.Core.Server


public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;


public void Publish(MqttApplicationMessage applicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));

_clientSessionsManager.DispatchPublishPacket(null, applicationMessage.ToPublishPacket());
}

public void InjectClient(string identifier, IMqttCommunicationAdapter adapter) public void InjectClient(string identifier, IMqttCommunicationAdapter adapter)
{ {
if (adapter == null) throw new ArgumentNullException(nameof(adapter)); if (adapter == null) throw new ArgumentNullException(nameof(adapter));


MQTTnet.Core/Server/DefaultEndpointOptions.cs → MQTTnet.Core/Server/MqttServerDefaultEndpointOptions.cs View File

@@ -1,6 +1,6 @@
namespace MQTTnet.Core.Server namespace MQTTnet.Core.Server
{ {
public sealed class DefaultEndpointOptions
public sealed class MqttServerDefaultEndpointOptions
{ {
public bool IsEnabled { get; set; } = true; public bool IsEnabled { get; set; } = true;



+ 2
- 2
MQTTnet.Core/Server/MqttServerOptions.cs View File

@@ -6,13 +6,13 @@ namespace MQTTnet.Core.Server
{ {
public sealed class MqttServerOptions public sealed class MqttServerOptions
{ {
public DefaultEndpointOptions DefaultEndpointOptions { get; } = new DefaultEndpointOptions();
public MqttServerDefaultEndpointOptions DefaultEndpointOptions { get; } = new MqttServerDefaultEndpointOptions();


public MqttServerTlsEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsEndpointOptions(); public MqttServerTlsEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsEndpointOptions();
public int ConnectionBacklog { get; set; } = 10; public int ConnectionBacklog { get; set; } = 10;


public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15);


public Func<MqttConnectPacket, MqttConnectReturnCode> ConnectionValidator { get; set; } public Func<MqttConnectPacket, MqttConnectReturnCode> ConnectionValidator { get; set; }
} }


+ 0
- 19
MQTTnet.Core/Server/MqttServerTlsEndpointOptionsExtensions.cs View File

@@ -1,19 +0,0 @@
using System;

namespace MQTTnet.Core.Server
{
public static class MqttServerTlsEndpointOptionsExtensions
{
public static int GetPort(this DefaultEndpointOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));

if (!options.Port.HasValue)
{
return 1883;
}

return options.Port.Value;
}
}
}

+ 6
- 1
MQTTnet.sln View File

@@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26430.15
VisualStudioVersion = 15.0.26430.16
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}"
EndProject EndProject
@@ -27,6 +27,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1
Build\MQTTnet.nuspec = Build\MQTTnet.nuspec Build\MQTTnet.nuspec = Build\MQTTnet.nuspec
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU


+ 27
- 20
README.md View File

@@ -5,38 +5,47 @@
[![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet) [![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet)


# MQTTnet # MQTTnet
MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server. The implementation is based on the documentation from http://mqtt.org/.
MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). The implementation is based on the documentation from http://mqtt.org/.


## Features
* MQTT client included
* MQTT server (broker) included
* TLS 1.2 support for client and server (but not UWP servers)
# Features

## General
* Async support * Async support
* Rx support (via another project)
* List of connected clients available (server only)
* TLS 1.2 support for client and server (but not UWP servers)
* Extensible communication channels (i.e. In-Memory, TCP, TCP+SSL, WebSockets (not included in this project)) * Extensible communication channels (i.e. In-Memory, TCP, TCP+SSL, WebSockets (not included in this project))
* Access to internal trace messages
* Extensible client credential validation (server only)
* Unit tested (48+ tests)
* Interfaces included for mocking and testing
* Lightweight (only the low level implementation of MQTT, no overhead) * Lightweight (only the low level implementation of MQTT, no overhead)
* Access to internal trace messages
* Unit tested (50+ tests)

## Client
* Rx support (via another project)


## Supported frameworks
## Server (broker)
* List of connected clients available
* Supports connected clients with different protocol versions at the same time
* Able to publish its own messages (no loopback client required)
* Able to receive every messages (no loopback client required)
* Extensible client credential validation

# Supported frameworks
* .NET Standard 1.3+ * .NET Standard 1.3+
* .NET Core 1.1+ * .NET Core 1.1+
* .NET Core App 1.1+ * .NET Core App 1.1+
* .NET Framework 4.5.2+ (x86, x64, AnyCPU) * .NET Framework 4.5.2+ (x86, x64, AnyCPU)
* Universal Windows (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU) * Universal Windows (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU)


## Supported MQTT versions
# Supported MQTT versions
* 3.1.1 * 3.1.1
* 3.1.0


## Nuget
# Nuget
This library is available as a nuget package: https://www.nuget.org/packages/MQTTnet/ This library is available as a nuget package: https://www.nuget.org/packages/MQTTnet/


## Contributions
# Contributions
If you want to contribute to this project just create a pull request. If you want to contribute to this project just create a pull request.


## References
# References
This library is used in the following projects: This library is used in the following projects:


* MQTT Client Rx (Wrapper for Reactive Extensions, https://github.com/1iveowl/MQTTClient.rx) * MQTT Client Rx (Wrapper for Reactive Extensions, https://github.com/1iveowl/MQTTClient.rx)
@@ -44,8 +53,8 @@ This library is used in the following projects:


If you use this library and want to see your project here please let me know. If you use this library and want to see your project here please let me know.


# MqttClient
## Example
# Examples
## MqttClient


```csharp ```csharp
var options = new MqttClientOptions var options = new MqttClientOptions
@@ -117,9 +126,7 @@ while (true)
} }
``` ```


# MqttServer

## Example
## MqttServer


```csharp ```csharp
var options = new MqttServerOptions var options = new MqttServerOptions


+ 1
- 1
Tests/MQTTnet.Core.Tests/MQTTnet.Core.Tests.csproj View File

@@ -86,7 +86,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="ByteReaderTests.cs" /> <Compile Include="ByteReaderTests.cs" />
<Compile Include="ByteWriterTests.cs" /> <Compile Include="ByteWriterTests.cs" />
<Compile Include="DefaultMqttV311PacketSerializerTests.cs" />
<Compile Include="MqttPacketSerializerTests.cs" />
<Compile Include="MqttServerTests.cs" /> <Compile Include="MqttServerTests.cs" />
<Compile Include="MqttSubscriptionsManagerTests.cs" /> <Compile Include="MqttSubscriptionsManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />


Tests/MQTTnet.Core.Tests/DefaultMqttV311PacketSerializerTests.cs → Tests/MQTTnet.Core.Tests/MqttPacketSerializerTests.cs View File

@@ -12,8 +12,23 @@ using MQTTnet.Core.Serializer;
namespace MQTTnet.Core.Tests namespace MQTTnet.Core.Tests
{ {
[TestClass] [TestClass]
public class DefaultMqttV311PacketSerializerTests
public class MqttPacketSerializerTests
{ {
[TestMethod]
public void SerializeV310_MqttConnectPacket()
{
var p = new MqttConnectPacket
{
ClientId = "XYZ",
Password = "PASS",
Username = "USER",
KeepAlivePeriod = 123,
CleanSession = true
};

SerializeAndCompare(p, "EB0ABE1RSXNkcAPCAHsAA1hZWgAEVVNFUgAEUEFTUw==", MqttProtocolVersion.V310);
}

[TestMethod] [TestMethod]
public void SerializeV311_MqttConnectPacket() public void SerializeV311_MqttConnectPacket()
{ {
@@ -96,6 +111,17 @@ namespace MQTTnet.Core.Tests
SerializeAndCompare(p, "IAIBBQ=="); SerializeAndCompare(p, "IAIBBQ==");
} }


[TestMethod]
public void SerializeV310_MqttConnAckPacket()
{
var p = new MqttConnAckPacket
{
ConnectReturnCode = MqttConnectReturnCode.ConnectionAccepted
};

SerializeAndCompare(p, "IAIAAA==", MqttProtocolVersion.V310);
}

[TestMethod] [TestMethod]
public void DeserializeV311_MqttConnAckPacket() public void DeserializeV311_MqttConnAckPacket()
{ {
@@ -403,9 +429,9 @@ namespace MQTTnet.Core.Tests
} }
} }


private void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value)
private void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
{ {
var serializer = new DefaultMqttV311PacketSerializer();
var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion };
var channel = new TestChannel(); var channel = new TestChannel();
serializer.SerializeAsync(packet, channel).Wait(); serializer.SerializeAsync(packet, channel).Wait();
var buffer = channel.ToArray(); var buffer = channel.ToArray();
@@ -415,7 +441,7 @@ namespace MQTTnet.Core.Tests


private void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value) private void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value)
{ {
var serializer = new DefaultMqttV311PacketSerializer();
var serializer = new MqttPacketSerializer();


var channel1 = new TestChannel(); var channel1 = new TestChannel();
serializer.SerializeAsync(packet, channel1).Wait(); serializer.SerializeAsync(packet, channel1).Wait();

+ 58
- 1
Tests/MQTTnet.Core.Tests/MqttServerTests.cs View File

@@ -68,7 +68,64 @@ namespace MQTTnet.Core.Tests
Assert.AreEqual(1, receivedMessagesCount); Assert.AreEqual(1, receivedMessagesCount);
} }


private MqttClient ConnectTestClient(string clientId, MqttApplicationMessage willMessage, MqttServer server)
[TestMethod]
public async Task MqttServer_Unsubscribe()
{
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() });
s.Start();

var c1 = ConnectTestClient("c1", null, s);
var c2 = ConnectTestClient("c2", null, s);

var receivedMessagesCount = 0;
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
var message = new MqttApplicationMessage("a", new byte[0], MqttQualityOfServiceLevel.AtLeastOnce, false);

await c2.PublishAsync(message);
Assert.AreEqual(0, receivedMessagesCount);

await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce));
await c2.PublishAsync(message);

await Task.Delay(500);
Assert.AreEqual(1, receivedMessagesCount);

await c1.Unsubscribe("a");
await c2.PublishAsync(message);

await Task.Delay(500);
Assert.AreEqual(1, receivedMessagesCount);

s.Stop();
await Task.Delay(500);

Assert.AreEqual(1, receivedMessagesCount);
}

[TestMethod]
public async Task MqttServer_Publish()
{
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() });
s.Start();

var c1 = ConnectTestClient("c1", null, s);

var receivedMessagesCount = 0;
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;

var message = new MqttApplicationMessage("a", new byte[0], MqttQualityOfServiceLevel.AtLeastOnce, false);
await c1.SubscribeAsync(new TopicFilter("a", MqttQualityOfServiceLevel.AtLeastOnce));

s.Publish(message);
await Task.Delay(500);

s.Stop();

Assert.AreEqual(1, receivedMessagesCount);
}

private static MqttClient ConnectTestClient(string clientId, MqttApplicationMessage willMessage, MqttServer server)
{ {
var adapterA = new TestMqttCommunicationAdapter(); var adapterA = new TestMqttCommunicationAdapter();
var adapterB = new TestMqttCommunicationAdapter(); var adapterB = new TestMqttCommunicationAdapter();


+ 4
- 4
Tests/MQTTnet.Core.Tests/MqttSubscriptionsManagerTests.cs View File

@@ -24,7 +24,7 @@ namespace MQTTnet.Core.Tests
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
}; };


Assert.IsTrue(sm.IsTopicSubscribed(pp));
Assert.IsTrue(sm.IsSubscribed(pp));
} }


[TestMethod] [TestMethod]
@@ -43,7 +43,7 @@ namespace MQTTnet.Core.Tests
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
}; };


Assert.IsFalse(sm.IsTopicSubscribed(pp));
Assert.IsFalse(sm.IsSubscribed(pp));
} }


[TestMethod] [TestMethod]
@@ -62,13 +62,13 @@ namespace MQTTnet.Core.Tests
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
}; };


Assert.IsTrue(sm.IsTopicSubscribed(pp));
Assert.IsTrue(sm.IsSubscribed(pp));


var up = new MqttUnsubscribePacket(); var up = new MqttUnsubscribePacket();
up.TopicFilters.Add("A/B/C"); up.TopicFilters.Add("A/B/C");
sm.Unsubscribe(up); sm.Unsubscribe(up);


Assert.IsFalse(sm.IsTopicSubscribed(pp));
Assert.IsFalse(sm.IsSubscribed(pp));
} }
} }
} }

+ 3
- 0
Tests/MQTTnet.Core.Tests/TestMqttCommunicationAdapter.cs View File

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using MQTTnet.Core.Adapter; using MQTTnet.Core.Adapter;
using MQTTnet.Core.Client; using MQTTnet.Core.Client;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
using MQTTnet.Core.Serializer;


namespace MQTTnet.Core.Tests namespace MQTTnet.Core.Tests
{ {
@@ -13,6 +14,8 @@ namespace MQTTnet.Core.Tests


public TestMqttCommunicationAdapter Partner { get; set; } public TestMqttCommunicationAdapter Partner { get; set; }


public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer();

public async Task ConnectAsync(MqttClientOptions options, TimeSpan timeout) public async Task ConnectAsync(MqttClientOptions options, TimeSpan timeout)
{ {
await Task.FromResult(0); await Task.FromResult(0);


+ 1
- 1
Tests/MQTTnet.TestApp.UniversalWindows/MainPage.xaml.cs View File

@@ -8,7 +8,7 @@ namespace MQTTnet.TestApp.UniversalWindows
{ {
public sealed partial class MainPage public sealed partial class MainPage
{ {
private MqttClient _mqttClient;
private IMqttClient _mqttClient;


public MainPage() public MainPage()
{ {


Loading…
Cancel
Save