mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7d0ad5f43 | ||
|
|
59f07c868d | ||
|
|
4517d80704 | ||
|
|
9bca4aa53f | ||
|
|
4c4539509c | ||
|
|
af3cebb266 | ||
|
|
d22528ca46 | ||
|
|
67267c05fd | ||
|
|
d23ab88add | ||
|
|
1d78d675f7 | ||
|
|
3079ad37ce | ||
|
|
c7b7d56229 | ||
|
|
c97b97c677 | ||
|
|
96a62551b2 | ||
|
|
300c9b9ccb | ||
|
|
342085db40 | ||
|
|
862f76fea2 | ||
|
|
3cce33415d | ||
|
|
d09455f78e | ||
|
|
e947881aca | ||
|
|
077c9ea00d | ||
|
|
f8af4597f2 | ||
|
|
32dc96ae93 | ||
|
|
153e83ee3e | ||
|
|
6402dfd9ea | ||
|
|
98bafb1f26 | ||
|
|
6d92937892 | ||
|
|
28d31d0cf5 | ||
|
|
07fd509e74 | ||
|
|
37a5dc7abe | ||
|
|
8604097ada | ||
|
|
ea587d97c8 | ||
|
|
fb272b07aa | ||
|
|
7067a8e411 | ||
|
|
50800b1d44 | ||
|
|
dcf6f94d77 | ||
|
|
8131995006 | ||
|
|
7de1634a36 | ||
|
|
b252781f8d | ||
|
|
023cb12079 | ||
|
|
b5b87f5272 | ||
|
|
f287bd4d1d | ||
|
|
222a154290 | ||
|
|
9b8af2455f | ||
|
|
493c934463 | ||
|
|
83f1d74a76 | ||
|
|
e7c8d1c1c1 | ||
|
|
305578e7e6 | ||
|
|
651f0aa15c | ||
|
|
51e7cd725e | ||
|
|
32473af7c2 | ||
|
|
b5fc6e3d12 | ||
|
|
dc65bdb913 | ||
|
|
d4ef1a7302 | ||
|
|
f8c01b2168 | ||
|
|
d7e2b18247 | ||
|
|
12a275918e | ||
|
|
eda8bff10b | ||
|
|
76105de4c7 | ||
|
|
eed56cdf55 | ||
|
|
da7adc1068 | ||
|
|
f1ff947697 | ||
|
|
9250853854 | ||
|
|
d1b634511d | ||
|
|
cf7b7191f8 | ||
|
|
0dcd8227da | ||
|
|
089eb9b604 | ||
|
|
f04a9c4f3e | ||
|
|
e2d15c8764 | ||
|
|
9a25fb63ad | ||
|
|
0e48076d25 | ||
|
|
a956de3df7 | ||
|
|
455a047242 | ||
|
|
27bb22de09 | ||
|
|
285c70f24f | ||
|
|
c7c1691d75 | ||
|
|
6c7e70f78a | ||
|
|
97338b016d | ||
|
|
473c8e0a15 | ||
|
|
090e59e99a | ||
|
|
5712bc11d2 | ||
|
|
323339b0b2 | ||
|
|
c001897590 | ||
|
|
8fd3e5070d | ||
|
|
01e739d524 | ||
|
|
16a184a92f | ||
|
|
1a1c0beabc | ||
|
|
ff0b022357 | ||
|
|
c0e6e54496 | ||
|
|
16bc9cb203 | ||
|
|
196a325f7f | ||
|
|
da70dc0565 | ||
|
|
f7a8b764d0 | ||
|
|
16949ffb21 | ||
|
|
de0dfb9265 | ||
|
|
9cde49f5f8 | ||
|
|
644bcfba27 | ||
|
|
a4d393ae28 | ||
|
|
a7a5eed0f2 | ||
|
|
80a6e6809c | ||
|
|
861efa5e75 | ||
|
|
6c8f85afe9 | ||
|
|
6b1a6c054e | ||
|
|
f2502ac431 | ||
|
|
76151deba0 | ||
|
|
4a6790f0aa | ||
|
|
95298202a3 | ||
|
|
1c30d14910 | ||
|
|
8aec1583df | ||
|
|
04036cf63a | ||
|
|
68aae70b44 | ||
|
|
0907eadb55 | ||
|
|
77818d669f | ||
|
|
70aee177c3 | ||
|
|
5e417affa5 | ||
|
|
783ee4328a | ||
|
|
caa857f318 | ||
|
|
20a1b77b58 | ||
|
|
960900dafa | ||
|
|
9bf287a1ac | ||
|
|
f886b0eed1 | ||
|
|
1f1f2d2e90 | ||
|
|
8ad619ebc1 | ||
|
|
85f1a31be3 | ||
|
|
4a57d050f6 | ||
|
|
3a1e32f5aa | ||
|
|
b407221ce6 | ||
|
|
487a97c31e |
@@ -0,0 +1,8 @@
|
||||
exclude:
|
||||
- /Frameworks/.*
|
||||
- /MQTTnet.TestApp.NetCore/Program.cs
|
||||
- /Tests/MQTTnet.TestApp.NetFramework/.*
|
||||
- /Tests/MQTTnet.TestApp.UniversalWindows/.*
|
||||
component_depth: 1
|
||||
languages:
|
||||
- csharp
|
||||
@@ -286,4 +286,6 @@ __pycache__/
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
|
||||
Build/nuget.exe
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
language: csharp
|
||||
solution: MQTTnet.sln
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- dotnet: 2.0.0
|
||||
mono: none
|
||||
dist: trusty
|
||||
env: DOTNETCORE=1 # optional, can be used to take different code paths in your script
|
||||
- mono: latest
|
||||
+15
-5
@@ -2,7 +2,7 @@
|
||||
<package >
|
||||
<metadata>
|
||||
<id>MQTTnet</id>
|
||||
<version>2.2.1</version>
|
||||
<version>2.4.0</version>
|
||||
<authors>Christian Kratky</authors>
|
||||
<owners>Christian Kratky</owners>
|
||||
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
|
||||
@@ -10,19 +10,29 @@
|
||||
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
|
||||
<releaseNotes>* [Server] Fixed a bug in connection management (Thanks to rydergillen-compacSort)
|
||||
<releaseNotes>* [Server] Added an event which is fired when a client has disconnected.
|
||||
* [Server] Added support for retained application messages
|
||||
* [Server] Added support for saving and loading retained messages
|
||||
* [Server] The client connection is now closed if sending of one pending application message has failed
|
||||
* [Server] Fixed handling of _Dup_ flag (Thanks to haeberle)
|
||||
* [Core] Optimized exception handling
|
||||
* [Core] Mono is now also supported (Thanks to JTrotta)
|
||||
* [Client] The options are now passed in _ConnectAsync_ (Breaking change! Read Wiki for examples)
|
||||
* [Core] Trace class renamed to _MqttNetTrace_ (Breaking change!)
|
||||
* [Client] Extended certificate validation options (Breaking change!)
|
||||
* [Client] Added static certificate validation callback (NetFramework, NetStandard) / ignorable certificate errors (UniversalWindows) to _MqttTcpChannel_
|
||||
</releaseNotes>
|
||||
<copyright>Copyright Christian Kratky 2016-2017</copyright>
|
||||
<tags>MQTT MQTTClient MQTTServer MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Queue Hardware Arduino</tags>
|
||||
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M</tags>
|
||||
<dependencies>
|
||||
|
||||
<group targetFramework="netstandard1.3">
|
||||
<dependency id="NETStandard.Library" version="1.6.1" />
|
||||
<dependency id="System.Net.Security" version="4.3.1" />
|
||||
<dependency id="System.Net.Security" version="4.3.2" />
|
||||
</group>
|
||||
|
||||
<group targetFramework="uap10.0">
|
||||
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.3.3" />
|
||||
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.0" />
|
||||
</group>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Serializer;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory
|
||||
{
|
||||
public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (options is MqttClientTcpOptions tcpOptions)
|
||||
{
|
||||
return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
|
||||
}
|
||||
|
||||
if (options is MqttClientWebSocketOptions webSocketOptions)
|
||||
{
|
||||
return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,9 +22,9 @@ namespace MQTTnet.Implementations
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
|
||||
public event Action<IMqttCommunicationAdapter> ClientAccepted;
|
||||
|
||||
public void Start(MqttServerOptions options)
|
||||
public Task StartAsync(MqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
@@ -57,9 +57,11 @@ namespace MQTTnet.Implementations
|
||||
|
||||
Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
public Task StopAsync()
|
||||
{
|
||||
_isRunning = false;
|
||||
|
||||
@@ -72,11 +74,13 @@ namespace MQTTnet.Implementations
|
||||
|
||||
_tlsEndpointSocket?.Dispose();
|
||||
_tlsEndpointSocket = null;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
StopAsync();
|
||||
}
|
||||
|
||||
private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken)
|
||||
@@ -85,16 +89,18 @@ namespace MQTTnet.Implementations
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null);
|
||||
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer());
|
||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
|
||||
var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false);
|
||||
|
||||
var tcpChannel = new MqttTcpChannel(clientSocket, null);
|
||||
var clientAdapter = new MqttChannelCommunicationAdapter(tcpChannel, new MqttPacketSerializer());
|
||||
ClientAccepted?.Invoke(clientAdapter);
|
||||
}
|
||||
catch (Exception exception) when (!(exception is ObjectDisposedException))
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
|
||||
MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
|
||||
|
||||
//excessive CPU consumed if in endless loop of socket errors
|
||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,20 +111,21 @@ namespace MQTTnet.Implementations
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null);
|
||||
var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null).ConfigureAwait(false);
|
||||
|
||||
var sslStream = new SslStream(new NetworkStream(clientSocket));
|
||||
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false);
|
||||
|
||||
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer());
|
||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
|
||||
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
|
||||
|
||||
var tcpChannel = new MqttTcpChannel(clientSocket, sslStream);
|
||||
var clientAdapter = new MqttChannelCommunicationAdapter(tcpChannel, new MqttPacketSerializer());
|
||||
ClientAccepted?.Invoke(clientAdapter);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint.");
|
||||
MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint.");
|
||||
|
||||
//excessive CPU consumed if in endless loop of socket errors
|
||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,116 +6,131 @@ using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Exceptions;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
|
||||
{
|
||||
private readonly MqttClientTcpOptions _options;
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
|
||||
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
|
||||
|
||||
private Socket _socket;
|
||||
private SslStream _sslStream;
|
||||
|
||||
public MqttTcpChannel()
|
||||
/// <summary>
|
||||
/// called on client sockets are created in connect
|
||||
/// </summary>
|
||||
public MqttTcpChannel(MqttClientTcpOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// called on server, sockets are passed in
|
||||
/// connect will not be called
|
||||
/// </summary>
|
||||
public MqttTcpChannel(Socket socket, SslStream sslStream)
|
||||
{
|
||||
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
|
||||
_sslStream = sslStream;
|
||||
|
||||
CreateStreams(socket, sslStream);
|
||||
}
|
||||
|
||||
public async Task ConnectAsync(MqttClientOptions options)
|
||||
public Stream SendStream { get; private set; }
|
||||
public Stream ReceiveStream { get; private set; }
|
||||
public Stream RawReceiveStream { get; private set; }
|
||||
|
||||
public static Func<X509Certificate, X509Chain, SslPolicyErrors, MqttClientTcpOptions, bool> CustomCertificateValidationCallback { get; set; }
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
try
|
||||
if (_socket == null)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
}
|
||||
|
||||
await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, options.Server, options.GetPort(), null);
|
||||
|
||||
if (options.TlsOptions.UseTls)
|
||||
{
|
||||
_sslStream = new SslStream(new NetworkStream(_socket, true));
|
||||
await _sslStream.AuthenticateAsClientAsync(options.Server, LoadCertificates(options), SslProtocols.Tls12, options.TlsOptions.CheckCertificateRevocation);
|
||||
}
|
||||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
}
|
||||
catch (SocketException exception)
|
||||
|
||||
await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, _options.Server, _options.GetPort(), null).ConfigureAwait(false);
|
||||
|
||||
if (_options.TlsOptions.UseTls)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
|
||||
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
CreateStreams(_socket, _sslStream);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Dispose();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
catch (SocketException exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public Task WriteAsync(byte[] buffer)
|
||||
{
|
||||
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
try
|
||||
{
|
||||
if (_sslStream != null)
|
||||
{
|
||||
return _sslStream.WriteAsync(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
return Task.Factory.FromAsync(
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
_socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, null, null),
|
||||
_socket.EndSend);
|
||||
|
||||
}
|
||||
catch (SocketException exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public Task ReadAsync(byte[] buffer)
|
||||
{
|
||||
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
try
|
||||
{
|
||||
if (_sslStream != null)
|
||||
{
|
||||
return _sslStream.ReadAsync(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
return Task.Factory.FromAsync(
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
_socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, null, null),
|
||||
_socket.EndReceive);
|
||||
}
|
||||
catch (SocketException exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
Dispose();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_socket?.Dispose();
|
||||
_sslStream?.Dispose();
|
||||
RawReceiveStream?.Dispose();
|
||||
RawReceiveStream = null;
|
||||
|
||||
ReceiveStream?.Dispose();
|
||||
ReceiveStream = null;
|
||||
|
||||
SendStream?.Dispose();
|
||||
SendStream = null;
|
||||
|
||||
_socket?.Dispose();
|
||||
_socket = null;
|
||||
|
||||
_sslStream?.Dispose();
|
||||
_sslStream = null;
|
||||
}
|
||||
|
||||
private void CreateStreams(Socket socket, Stream sslStream)
|
||||
{
|
||||
RawReceiveStream = sslStream ?? new NetworkStream(socket);
|
||||
|
||||
//cannot use this as default buffering prevents from receiving the first connect message
|
||||
//need two streams otherwise read and write have to be synchronized
|
||||
SendStream = new BufferedStream(RawReceiveStream, BufferSize);
|
||||
ReceiveStream = new BufferedStream(RawReceiveStream, BufferSize);
|
||||
}
|
||||
|
||||
private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (CustomCertificateValidationCallback != null)
|
||||
{
|
||||
return CustomCertificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _options);
|
||||
}
|
||||
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.RevocationStatusUnknown))
|
||||
{
|
||||
if (!_options.TlsOptions.IgnoreCertificateRevocationErrors)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.PartialChain))
|
||||
{
|
||||
if (!_options.TlsOptions.IgnoreCertificateChainErrors)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return _options.TlsOptions.AllowUntrustedCertificates;
|
||||
}
|
||||
|
||||
private static X509CertificateCollection LoadCertificates(MqttClientOptions options)
|
||||
{
|
||||
var certificates = new X509CertificateCollection();
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Client;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttWebSocketChannel : IMqttCommunicationChannel, IDisposable
|
||||
{
|
||||
private readonly MqttClientWebSocketOptions _options;
|
||||
|
||||
private ClientWebSocket _webSocket;
|
||||
|
||||
public MqttWebSocketChannel(MqttClientWebSocketOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public Stream RawReceiveStream { get; private set; }
|
||||
public Stream SendStream => RawReceiveStream;
|
||||
public Stream ReceiveStream => RawReceiveStream;
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
var uri = _options.Uri;
|
||||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
uri = "ws://" + uri;
|
||||
}
|
||||
|
||||
_webSocket = new ClientWebSocket();
|
||||
_webSocket.Options.KeepAliveInterval = _options.KeepAlivePeriod;
|
||||
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false);
|
||||
RawReceiveStream = new WebSocketStream(_webSocket);
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
RawReceiveStream = null;
|
||||
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_webSocket?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class WebSocketStream : Stream
|
||||
{
|
||||
private readonly ClientWebSocket _webSocket;
|
||||
|
||||
public WebSocketStream(ClientWebSocket webSocket)
|
||||
{
|
||||
_webSocket = webSocket;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentOffset = offset;
|
||||
var targetOffset = offset + count;
|
||||
while (_webSocket.State == WebSocketState.Open && currentOffset < targetOffset)
|
||||
{
|
||||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, currentOffset, count), cancellationToken).ConfigureAwait(false);
|
||||
currentOffset += response.Count;
|
||||
|
||||
if (response.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return currentOffset - offset;
|
||||
}
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,9 @@
|
||||
<Reference Include="System.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Implementations\MqttCommunicationAdapterFactory.cs" />
|
||||
<Compile Include="Implementations\MqttWebSocketChannel.cs" />
|
||||
<Compile Include="Implementations\WebSocketStream.cs" />
|
||||
<Compile Include="MqttClientFactory.cs" />
|
||||
<Compile Include="MqttServerFactory.cs" />
|
||||
<Compile Include="Implementations\MqttServerAdapter.cs" />
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Serializer;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public class MqttClientFactory
|
||||
public class MqttClientFactory : IMqttClientFactory
|
||||
{
|
||||
public IMqttClient CreateMqttClient(MqttClientOptions options)
|
||||
public IMqttClient CreateMqttClient()
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttPacketSerializer()));
|
||||
return new MqttClient(new MqttCommunicationAdapterFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public class MqttServerFactory
|
||||
public class MqttServerFactory : IMqttServerFactory
|
||||
{
|
||||
public IMqttServer CreateMqttServer(MqttServerOptions options)
|
||||
{
|
||||
|
||||
@@ -11,5 +11,5 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("a480ef90-0eaa-4d9a-b271-47a9c47f6f7d")]
|
||||
[assembly: AssemblyVersion("2.2.1.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.1.0")]
|
||||
[assembly: AssemblyVersion("2.4.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.4.0.0")]
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Serializer;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory
|
||||
{
|
||||
public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (options is MqttClientTcpOptions tcpOptions)
|
||||
{
|
||||
return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
|
||||
}
|
||||
|
||||
if (options is MqttClientWebSocketOptions webSocketOptions)
|
||||
{
|
||||
return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,12 @@ namespace MQTTnet.Implementations
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
|
||||
public event Action<IMqttCommunicationAdapter> ClientAccepted;
|
||||
|
||||
public void Start(MqttServerOptions options)
|
||||
public Task StartAsync(MqttServerOptions options)
|
||||
{
|
||||
if (_isRunning) throw new InvalidOperationException("Server is already started.");
|
||||
|
||||
_isRunning = true;
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
@@ -55,9 +56,11 @@ namespace MQTTnet.Implementations
|
||||
|
||||
Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
public Task StopAsync()
|
||||
{
|
||||
_isRunning = false;
|
||||
|
||||
@@ -70,11 +73,13 @@ namespace MQTTnet.Implementations
|
||||
|
||||
_tlsEndpointSocket?.Dispose();
|
||||
_tlsEndpointSocket = null;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
StopAsync();
|
||||
}
|
||||
|
||||
private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken)
|
||||
@@ -83,16 +88,16 @@ namespace MQTTnet.Implementations
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientSocket = await _defaultEndpointSocket.AcceptAsync();
|
||||
var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false);
|
||||
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer());
|
||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
|
||||
ClientAccepted?.Invoke(clientAdapter);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
|
||||
MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
|
||||
|
||||
//excessive CPU consumed if in endless loop of socket errors
|
||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,20 +108,20 @@ namespace MQTTnet.Implementations
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientSocket = await _tlsEndpointSocket.AcceptAsync();
|
||||
var clientSocket = await _tlsEndpointSocket.AcceptAsync().ConfigureAwait(false);
|
||||
|
||||
var sslStream = new SslStream(new NetworkStream(clientSocket));
|
||||
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false);
|
||||
|
||||
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
|
||||
|
||||
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer());
|
||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
|
||||
ClientAccepted?.Invoke(clientAdapter);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint.");
|
||||
MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint.");
|
||||
|
||||
//excessive CPU consumed if in endless loop of socket errors
|
||||
Thread.Sleep(TimeSpan.FromSeconds(1));
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,109 +6,110 @@ using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Exceptions;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
|
||||
{
|
||||
private readonly MqttClientTcpOptions _options;
|
||||
|
||||
private Socket _socket;
|
||||
private SslStream _sslStream;
|
||||
|
||||
public MqttTcpChannel()
|
||||
|
||||
/// <summary>
|
||||
/// called on client sockets are created in connect
|
||||
/// </summary>
|
||||
public MqttTcpChannel(MqttClientTcpOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// called on server, sockets are passed in
|
||||
/// connect will not be called
|
||||
/// </summary>
|
||||
public MqttTcpChannel(Socket socket, SslStream sslStream)
|
||||
{
|
||||
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
|
||||
_sslStream = sslStream;
|
||||
ReceiveStream = (Stream)sslStream ?? new NetworkStream(socket);
|
||||
}
|
||||
|
||||
public async Task ConnectAsync(MqttClientOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
try
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
}
|
||||
public Stream SendStream => ReceiveStream;
|
||||
public Stream ReceiveStream { get; private set; }
|
||||
public Stream RawReceiveStream => ReceiveStream;
|
||||
|
||||
await _socket.ConnectAsync(options.Server, options.GetPort());
|
||||
|
||||
if (options.TlsOptions.UseTls)
|
||||
{
|
||||
_sslStream = new SslStream(new NetworkStream(_socket, true));
|
||||
await _sslStream.AuthenticateAsClientAsync(options.Server, LoadCertificates(options), SslProtocols.Tls12, options.TlsOptions.CheckCertificateRevocation);
|
||||
}
|
||||
}
|
||||
catch (SocketException exception)
|
||||
public static Func<X509Certificate, X509Chain, SslPolicyErrors, MqttClientTcpOptions, bool> CustomCertificateValidationCallback { get; set; }
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
|
||||
}
|
||||
|
||||
await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false);
|
||||
|
||||
if (_options.TlsOptions.UseTls)
|
||||
{
|
||||
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
|
||||
ReceiveStream = _sslStream;
|
||||
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(_options), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReceiveStream = new NetworkStream(_socket);
|
||||
}
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Dispose();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
catch (SocketException exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public Task WriteAsync(byte[] buffer)
|
||||
{
|
||||
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
try
|
||||
{
|
||||
if (_sslStream != null)
|
||||
{
|
||||
return _sslStream.WriteAsync(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
return _socket.SendAsync(new ArraySegment<byte>(buffer), SocketFlags.None);
|
||||
}
|
||||
catch (SocketException exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public Task ReadAsync(byte[] buffer)
|
||||
{
|
||||
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
try
|
||||
{
|
||||
if (_sslStream != null)
|
||||
{
|
||||
return _sslStream.ReadAsync(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
return _socket.ReceiveAsync(new ArraySegment<byte>(buffer), SocketFlags.None);
|
||||
}
|
||||
catch (SocketException exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
Dispose();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_socket?.Dispose();
|
||||
_sslStream?.Dispose();
|
||||
|
||||
_socket = null;
|
||||
|
||||
_sslStream?.Dispose();
|
||||
_sslStream = null;
|
||||
}
|
||||
|
||||
private bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
if (CustomCertificateValidationCallback != null)
|
||||
{
|
||||
return CustomCertificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _options);
|
||||
}
|
||||
|
||||
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.RevocationStatusUnknown))
|
||||
{
|
||||
if (!_options.TlsOptions.IgnoreCertificateRevocationErrors)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.PartialChain))
|
||||
{
|
||||
if (!_options.TlsOptions.IgnoreCertificateChainErrors)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return _options.TlsOptions.AllowUntrustedCertificates;
|
||||
}
|
||||
|
||||
private static X509CertificateCollection LoadCertificates(MqttClientOptions options)
|
||||
{
|
||||
var certificates = new X509CertificateCollection();
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Client;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttWebSocketChannel : IMqttCommunicationChannel, IDisposable
|
||||
{
|
||||
private readonly MqttClientWebSocketOptions _options;
|
||||
private ClientWebSocket _webSocket;
|
||||
|
||||
public MqttWebSocketChannel(MqttClientWebSocketOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public Stream SendStream => RawReceiveStream;
|
||||
public Stream ReceiveStream => RawReceiveStream;
|
||||
public Stream RawReceiveStream { get; private set; }
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
var uri = _options.Uri;
|
||||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
uri = "ws://" + uri;
|
||||
}
|
||||
|
||||
_webSocket = new ClientWebSocket();
|
||||
_webSocket.Options.KeepAliveInterval = _options.KeepAlivePeriod;
|
||||
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);
|
||||
RawReceiveStream = new WebSocketStream(_webSocket);
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
RawReceiveStream = null;
|
||||
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_webSocket?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class WebSocketStream : Stream
|
||||
{
|
||||
private readonly ClientWebSocket _webSocket;
|
||||
|
||||
public WebSocketStream(ClientWebSocket webSocket)
|
||||
{
|
||||
_webSocket = webSocket;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentOffset = offset;
|
||||
var targetOffset = offset + count;
|
||||
while (_webSocket.State == WebSocketState.Open && currentOffset < targetOffset)
|
||||
{
|
||||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, currentOffset, count), cancellationToken).ConfigureAwait(false);
|
||||
currentOffset += response.Count;
|
||||
|
||||
if (response.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return currentOffset - offset;
|
||||
}
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
<TargetFramework>netstandard1.3</TargetFramework>
|
||||
<AssemblyName>MQTTnet</AssemblyName>
|
||||
<RootNamespace>MQTTnet</RootNamespace>
|
||||
<AssemblyVersion>2.2.1.0</AssemblyVersion>
|
||||
<FileVersion>2.2.1.0</FileVersion>
|
||||
<AssemblyVersion>2.4.0.0</AssemblyVersion>
|
||||
<FileVersion>2.4.0.0</FileVersion>
|
||||
<Version>0.0.0.0</Version>
|
||||
<Company />
|
||||
<Product />
|
||||
@@ -23,7 +23,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.1" />
|
||||
<PackageReference Include="System.Net.Security" Version="4.3.2" />
|
||||
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
|
||||
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Serializer;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public class MqttClientFactory
|
||||
public class MqttClientFactory : IMqttClientFactory
|
||||
{
|
||||
public IMqttClient CreateMqttClient(MqttClientOptions options)
|
||||
public IMqttClient CreateMqttClient()
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttPacketSerializer()));
|
||||
return new MqttClient(new MqttCommunicationAdapterFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public class MqttServerFactory
|
||||
public class MqttServerFactory : IMqttServerFactory
|
||||
{
|
||||
public IMqttServer CreateMqttServer(MqttServerOptions options)
|
||||
{
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Serializer;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory
|
||||
{
|
||||
public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (options is MqttClientTcpOptions tcpOptions)
|
||||
{
|
||||
return new MqttChannelCommunicationAdapter(new MqttTcpChannel(tcpOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
|
||||
}
|
||||
|
||||
if (options is MqttClientWebSocketOptions webSocketOptions)
|
||||
{
|
||||
return new MqttChannelCommunicationAdapter(new MqttWebSocketChannel(webSocketOptions), new MqttPacketSerializer { ProtocolVersion = options.ProtocolVersion });
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Diagnostics;
|
||||
using MQTTnet.Core.Serializer;
|
||||
@@ -10,23 +11,24 @@ namespace MQTTnet.Implementations
|
||||
public class MqttServerAdapter : IMqttServerAdapter, IDisposable
|
||||
{
|
||||
private StreamSocketListener _defaultEndpointSocket;
|
||||
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
|
||||
public event Action<IMqttCommunicationAdapter> ClientAccepted;
|
||||
|
||||
public void Start(MqttServerOptions options)
|
||||
public async Task StartAsync(MqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (_isRunning) throw new InvalidOperationException("Server is already started.");
|
||||
|
||||
_isRunning = true;
|
||||
|
||||
if (options.DefaultEndpointOptions.IsEnabled)
|
||||
{
|
||||
_defaultEndpointSocket = new StreamSocketListener();
|
||||
_defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket).GetAwaiter().GetResult();
|
||||
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync;
|
||||
await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket);
|
||||
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync;
|
||||
}
|
||||
|
||||
if (options.TlsEndpointOptions.IsEnabled)
|
||||
@@ -35,17 +37,19 @@ namespace MQTTnet.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
public Task StopAsync()
|
||||
{
|
||||
_isRunning = false;
|
||||
|
||||
_defaultEndpointSocket?.Dispose();
|
||||
_defaultEndpointSocket = null;
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
StopAsync();
|
||||
}
|
||||
|
||||
private void AcceptDefaultEndpointConnectionsAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
|
||||
@@ -53,11 +57,11 @@ namespace MQTTnet.Implementations
|
||||
try
|
||||
{
|
||||
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer());
|
||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(args.Socket.Information.RemoteAddress.ToString(), clientAdapter));
|
||||
ClientAccepted?.Invoke(clientAdapter);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
|
||||
MqttNetTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,113 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Networking;
|
||||
using Windows.Networking.Sockets;
|
||||
using Windows.Security.Cryptography.Certificates;
|
||||
using Windows.Storage.Streams;
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Exceptions;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
|
||||
{
|
||||
private readonly MqttClientTcpOptions _options;
|
||||
private StreamSocket _socket;
|
||||
|
||||
public MqttTcpChannel()
|
||||
public MqttTcpChannel(MqttClientTcpOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public MqttTcpChannel(StreamSocket socket)
|
||||
{
|
||||
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
|
||||
|
||||
CreateStreams();
|
||||
}
|
||||
|
||||
public async Task ConnectAsync(MqttClientOptions options)
|
||||
public Stream SendStream { get; private set; }
|
||||
public Stream ReceiveStream { get; private set; }
|
||||
public Stream RawReceiveStream { get; private set; }
|
||||
|
||||
public static Func<MqttClientTcpOptions, IEnumerable<ChainValidationResult>> CustomIgnorableServerCertificateErrorsResolver { get; set; }
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
try
|
||||
if (_socket == null)
|
||||
{
|
||||
if (_socket == null)
|
||||
{
|
||||
_socket = new StreamSocket();
|
||||
}
|
||||
|
||||
if (!options.TlsOptions.UseTls)
|
||||
{
|
||||
await _socket.ConnectAsync(new HostName(options.Server), options.GetPort().ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
_socket.Control.ClientCertificate = LoadCertificate(options);
|
||||
|
||||
if (!options.TlsOptions.CheckCertificateRevocation)
|
||||
{
|
||||
_socket.Control.IgnorableServerCertificateErrors.Add(ChainValidationResult.IncompleteChain);
|
||||
_socket.Control.IgnorableServerCertificateErrors.Add(ChainValidationResult.RevocationInformationMissing);
|
||||
}
|
||||
|
||||
await _socket.ConnectAsync(new HostName(options.Server), options.GetPort().ToString(), SocketProtectionLevel.Tls12);
|
||||
}
|
||||
_socket = new StreamSocket();
|
||||
}
|
||||
catch (SocketException exception)
|
||||
|
||||
if (!_options.TlsOptions.UseTls)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
await _socket.ConnectAsync(new HostName(_options.Server), _options.GetPort().ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
_socket.Control.ClientCertificate = LoadCertificate(_options);
|
||||
|
||||
foreach (var ignorableChainValidationResult in ResolveIgnorableServerCertificateErrors())
|
||||
{
|
||||
_socket.Control.IgnorableServerCertificateErrors.Add(ignorableChainValidationResult);
|
||||
}
|
||||
|
||||
await _socket.ConnectAsync(new HostName(_options.Server), _options.GetPort().ToString(), SocketProtectionLevel.Tls12);
|
||||
}
|
||||
|
||||
CreateStreams();
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Dispose();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
catch (SocketException exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteAsync(byte[] buffer)
|
||||
{
|
||||
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
try
|
||||
{
|
||||
await _socket.OutputStream.WriteAsync(buffer.AsBuffer());
|
||||
await _socket.OutputStream.FlushAsync();
|
||||
}
|
||||
catch (SocketException exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ReadAsync(byte[] buffer)
|
||||
{
|
||||
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
try
|
||||
{
|
||||
await _socket.InputStream.ReadAsync(buffer.AsBuffer(), (uint)buffer.Length, InputStreamOptions.None);
|
||||
}
|
||||
catch (SocketException exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
Dispose();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_socket?.Dispose();
|
||||
RawReceiveStream?.Dispose();
|
||||
RawReceiveStream = null;
|
||||
|
||||
SendStream?.Dispose();
|
||||
SendStream = null;
|
||||
|
||||
ReceiveStream?.Dispose();
|
||||
ReceiveStream = null;
|
||||
|
||||
_socket?.Dispose();
|
||||
_socket = null;
|
||||
}
|
||||
|
||||
private void CreateStreams()
|
||||
{
|
||||
SendStream = _socket.OutputStream.AsStreamForWrite();
|
||||
ReceiveStream = _socket.InputStream.AsStreamForRead();
|
||||
RawReceiveStream = ReceiveStream;
|
||||
}
|
||||
|
||||
private static Certificate LoadCertificate(MqttClientOptions options)
|
||||
{
|
||||
if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any())
|
||||
@@ -122,5 +103,34 @@ namespace MQTTnet.Implementations
|
||||
|
||||
return new Certificate(options.TlsOptions.Certificates.First().AsBuffer());
|
||||
}
|
||||
|
||||
private IEnumerable<ChainValidationResult> ResolveIgnorableServerCertificateErrors()
|
||||
{
|
||||
if (CustomIgnorableServerCertificateErrorsResolver != null)
|
||||
{
|
||||
return CustomIgnorableServerCertificateErrorsResolver(_options);
|
||||
}
|
||||
|
||||
var result = new List<ChainValidationResult>();
|
||||
|
||||
if (_options.TlsOptions.IgnoreCertificateRevocationErrors)
|
||||
{
|
||||
result.Add(ChainValidationResult.RevocationInformationMissing);
|
||||
//_socket.Control.IgnorableServerCertificateErrors.Add(ChainValidationResult.Revoked); Not supported.
|
||||
result.Add(ChainValidationResult.RevocationFailure);
|
||||
}
|
||||
|
||||
if (_options.TlsOptions.IgnoreCertificateChainErrors)
|
||||
{
|
||||
result.Add(ChainValidationResult.IncompleteChain);
|
||||
}
|
||||
|
||||
if (_options.TlsOptions.AllowUntrustedCertificates)
|
||||
{
|
||||
result.Add(ChainValidationResult.Untrusted);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Client;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public sealed class MqttWebSocketChannel : IMqttCommunicationChannel, IDisposable
|
||||
{
|
||||
private readonly MqttClientWebSocketOptions _options;
|
||||
private ClientWebSocket _webSocket = new ClientWebSocket();
|
||||
|
||||
public MqttWebSocketChannel(MqttClientWebSocketOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public Stream RawReceiveStream { get; private set; }
|
||||
public Stream SendStream => RawReceiveStream;
|
||||
public Stream ReceiveStream => RawReceiveStream;
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
var uri = _options.Uri;
|
||||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
uri = "ws://" + uri;
|
||||
}
|
||||
|
||||
_webSocket = new ClientWebSocket();
|
||||
_webSocket.Options.KeepAliveInterval = _options.KeepAlivePeriod;
|
||||
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
RawReceiveStream = new WebSocketStream(_webSocket);
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
RawReceiveStream = null;
|
||||
|
||||
if (_webSocket == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_webSocket?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Implementations
|
||||
{
|
||||
public class WebSocketStream : Stream
|
||||
{
|
||||
private readonly ClientWebSocket _webSocket;
|
||||
|
||||
public WebSocketStream(ClientWebSocket webSocket)
|
||||
{
|
||||
_webSocket = webSocket;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
var currentOffset = offset;
|
||||
var targetOffset = offset + count;
|
||||
while (_webSocket.State == WebSocketState.Open && currentOffset < targetOffset)
|
||||
{
|
||||
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, currentOffset, count), cancellationToken).ConfigureAwait(false);
|
||||
currentOffset += response.Count;
|
||||
|
||||
if (response.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return currentOffset - offset;
|
||||
}
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,11 +110,14 @@
|
||||
<DocumentationFile>bin\x64\Release\MQTTnet.XML</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Implementations\MqttCommunicationAdapterFactory.cs" />
|
||||
<Compile Include="Implementations\MqttWebSocketChannel.cs" />
|
||||
<Compile Include="MqttClientFactory.cs" />
|
||||
<Compile Include="Implementations\MqttServerAdapter.cs" />
|
||||
<Compile Include="MqttServerFactory.cs" />
|
||||
<Compile Include="Implementations\MqttTcpChannel.cs" />
|
||||
<Compile Include="MqttServerFactory.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Implementations\WebSocketStream.cs" />
|
||||
<EmbeddedResource Include="Properties\MQTTnet.Universal.rd.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -125,7 +128,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
|
||||
<Version>5.3.3</Version>
|
||||
<Version>5.4.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '14.0' ">
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Serializer;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public class MqttClientFactory
|
||||
public class MqttClientFactory : IMqttClientFactory
|
||||
{
|
||||
public IMqttClient CreateMqttClient(MqttClientOptions options)
|
||||
public IMqttClient CreateMqttClient()
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttPacketSerializer()));
|
||||
return new MqttClient(new MqttCommunicationAdapterFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet
|
||||
{
|
||||
public class MqttServerFactory
|
||||
public class MqttServerFactory : IMqttServerFactory
|
||||
{
|
||||
public IMqttServer CreateMqttServer(MqttServerOptions options)
|
||||
{
|
||||
|
||||
@@ -10,5 +10,5 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: AssemblyVersion("2.2.1.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.1.0")]
|
||||
[assembly: AssemblyVersion("2.4.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.4.0.0")]
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Packets;
|
||||
using MQTTnet.Core.Serializer;
|
||||
|
||||
@@ -8,14 +9,14 @@ namespace MQTTnet.Core.Adapter
|
||||
{
|
||||
public interface IMqttCommunicationAdapter
|
||||
{
|
||||
Task ConnectAsync(MqttClientOptions options, TimeSpan timeout);
|
||||
|
||||
Task DisconnectAsync();
|
||||
|
||||
Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout);
|
||||
|
||||
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout);
|
||||
|
||||
IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
Task ConnectAsync(TimeSpan timeout);
|
||||
|
||||
Task DisconnectAsync(TimeSpan timeout);
|
||||
|
||||
Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets);
|
||||
|
||||
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Server;
|
||||
|
||||
namespace MQTTnet.Core.Adapter
|
||||
{
|
||||
public interface IMqttServerAdapter
|
||||
{
|
||||
event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
|
||||
event Action<IMqttCommunicationAdapter> ClientAccepted;
|
||||
|
||||
void Start(MqttServerOptions options);
|
||||
|
||||
void Stop();
|
||||
Task StartAsync(MqttServerOptions options);
|
||||
Task StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Diagnostics;
|
||||
using MQTTnet.Core.Exceptions;
|
||||
using MQTTnet.Core.Internal;
|
||||
using MQTTnet.Core.Packets;
|
||||
using MQTTnet.Core.Serializer;
|
||||
|
||||
@@ -12,6 +15,7 @@ namespace MQTTnet.Core.Adapter
|
||||
public class MqttChannelCommunicationAdapter : IMqttCommunicationAdapter
|
||||
{
|
||||
private readonly IMqttCommunicationChannel _channel;
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer)
|
||||
{
|
||||
@@ -21,72 +25,185 @@ namespace MQTTnet.Core.Adapter
|
||||
|
||||
public IMqttPacketSerializer PacketSerializer { get; }
|
||||
|
||||
public async Task ConnectAsync(MqttClientOptions options, TimeSpan timeout)
|
||||
public async Task ConnectAsync(TimeSpan timeout)
|
||||
{
|
||||
await ExecuteWithTimeoutAsync(_channel.ConnectAsync(options), timeout);
|
||||
try
|
||||
{
|
||||
await _channel.ConnectAsync().TimeoutAfter(timeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (MqttCommunicationTimedOutException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (MqttCommunicationException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
public async Task DisconnectAsync(TimeSpan timeout)
|
||||
{
|
||||
await _channel.DisconnectAsync();
|
||||
try
|
||||
{
|
||||
await _channel.DisconnectAsync().TimeoutAfter(timeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (MqttCommunicationTimedOutException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (MqttCommunicationException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout)
|
||||
public async Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
|
||||
{
|
||||
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"TX >>> {packet} [Timeout={timeout}]");
|
||||
await _semaphore.WaitAsync(cancellationToken);
|
||||
|
||||
await ExecuteWithTimeoutAsync(PacketSerializer.SerializeAsync(packet, _channel), timeout);
|
||||
try
|
||||
{
|
||||
foreach (var packet in packets)
|
||||
{
|
||||
if (packet == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
MqttNetTrace.Information(nameof(MqttChannelCommunicationAdapter), "TX >>> {0} [Timeout={1}]", packet, timeout);
|
||||
|
||||
var writeBuffer = PacketSerializer.Serialize(packet);
|
||||
await _channel.SendStream.WriteAsync(writeBuffer, 0, writeBuffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (timeout > TimeSpan.Zero)
|
||||
{
|
||||
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (MqttCommunicationTimedOutException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (MqttCommunicationException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout)
|
||||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
MqttBasePacket packet;
|
||||
if (timeout > TimeSpan.Zero)
|
||||
try
|
||||
{
|
||||
packet = await ExecuteWithTimeoutAsync(PacketSerializer.DeserializeAsync(_channel), timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
packet = await PacketSerializer.DeserializeAsync(_channel);
|
||||
}
|
||||
ReceivedMqttPacket receivedMqttPacket;
|
||||
if (timeout > TimeSpan.Zero)
|
||||
{
|
||||
receivedMqttPacket = await ReceiveAsync(_channel.RawReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (packet == null)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Received malformed packet.");
|
||||
}
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"RX <<< {packet}");
|
||||
return packet;
|
||||
var packet = PacketSerializer.Deserialize(receivedMqttPacket);
|
||||
if (packet == null)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Received malformed packet.");
|
||||
}
|
||||
|
||||
MqttNetTrace.Information(nameof(MqttChannelCommunicationAdapter), "RX <<< {0}", packet);
|
||||
return packet;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (MqttCommunicationTimedOutException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (MqttCommunicationException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<TResult> ExecuteWithTimeoutAsync<TResult>(Task<TResult> task, TimeSpan timeout)
|
||||
private static async Task<ReceivedMqttPacket> ReceiveAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var timeoutTask = Task.Delay(timeout);
|
||||
if (await Task.WhenAny(timeoutTask, task) == timeoutTask)
|
||||
var header = MqttPacketReader.ReadHeaderFromSource(stream, cancellationToken);
|
||||
|
||||
if (header.BodyLength == 0)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException();
|
||||
return new ReceivedMqttPacket(header, new MemoryStream(0));
|
||||
}
|
||||
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
throw new MqttCommunicationException(task.Exception);
|
||||
}
|
||||
var body = new byte[header.BodyLength];
|
||||
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
private static async Task ExecuteWithTimeoutAsync(Task task, TimeSpan timeout)
|
||||
{
|
||||
var timeoutTask = Task.Delay(timeout);
|
||||
if (await Task.WhenAny(timeoutTask, task) == timeoutTask)
|
||||
var offset = 0;
|
||||
do
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException();
|
||||
}
|
||||
var readBytesCount = await stream.ReadAsync(body, offset, body.Length - offset, cancellationToken).ConfigureAwait(false);
|
||||
offset += readBytesCount;
|
||||
} while (offset < header.BodyLength);
|
||||
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
throw new MqttCommunicationException(task.Exception);
|
||||
}
|
||||
return new ReceivedMqttPacket(header, new MemoryStream(body, 0, body.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
using System;
|
||||
using MQTTnet.Core.Server;
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Core.Adapter
|
||||
{
|
||||
public class MqttClientConnectedEventArgs : EventArgs
|
||||
{
|
||||
public MqttClientConnectedEventArgs(string identifier, IMqttCommunicationAdapter clientAdapter)
|
||||
public MqttClientConnectedEventArgs(ConnectedMqttClient client)
|
||||
{
|
||||
Identifier = identifier ?? throw new ArgumentNullException(nameof(identifier));
|
||||
ClientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
||||
Client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
public string Identifier { get; }
|
||||
|
||||
public IMqttCommunicationAdapter ClientAdapter { get; }
|
||||
public ConnectedMqttClient Client { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using MQTTnet.Core.Server;
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Core.Adapter
|
||||
{
|
||||
public class MqttClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public MqttClientDisconnectedEventArgs(ConnectedMqttClient client)
|
||||
{
|
||||
Client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
public ConnectedMqttClient Client { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Packets;
|
||||
|
||||
namespace MQTTnet.Core.Adapter
|
||||
{
|
||||
public static class MqttCommunicationAdapterExtensions
|
||||
{
|
||||
public static Task SendPacketsAsync(this IMqttCommunicationAdapter adapter, TimeSpan timeout, CancellationToken cancellationToken, params MqttBasePacket[] packets)
|
||||
{
|
||||
return adapter.SendPacketsAsync(timeout, cancellationToken, packets);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace MQTTnet.Core.Adapter
|
||||
{
|
||||
public class MqttConnectingFailedException : MqttCommunicationException
|
||||
{
|
||||
public MqttConnectingFailedException(MqttConnectReturnCode returnCode)
|
||||
public MqttConnectingFailedException(MqttConnectReturnCode returnCode)
|
||||
: base($"Connecting with MQTT server failed ({returnCode}).")
|
||||
{
|
||||
ReturnCode = returnCode;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using MQTTnet.Core.Packets;
|
||||
|
||||
namespace MQTTnet.Core.Adapter
|
||||
{
|
||||
public class ReceivedMqttPacket
|
||||
{
|
||||
public ReceivedMqttPacket(MqttPacketHeader header, MemoryStream body)
|
||||
{
|
||||
Header = header ?? throw new ArgumentNullException(nameof(header));
|
||||
Body = body ?? throw new ArgumentNullException(nameof(body));
|
||||
}
|
||||
|
||||
public MqttPacketHeader Header { get; }
|
||||
|
||||
public MemoryStream Body { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Client;
|
||||
using System.IO;
|
||||
|
||||
namespace MQTTnet.Core.Channel
|
||||
{
|
||||
public interface IMqttCommunicationChannel
|
||||
{
|
||||
Task ConnectAsync(MqttClientOptions options);
|
||||
Stream SendStream { get; }
|
||||
Stream ReceiveStream { get; }
|
||||
Stream RawReceiveStream { get; }
|
||||
|
||||
Task ConnectAsync();
|
||||
Task DisconnectAsync();
|
||||
|
||||
Task WriteAsync(byte[] buffer);
|
||||
|
||||
Task ReadAsync(byte[] buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ namespace MQTTnet.Core.Client
|
||||
event EventHandler Connected;
|
||||
event EventHandler Disconnected;
|
||||
|
||||
Task ConnectAsync(MqttApplicationMessage willApplicationMessage = null);
|
||||
Task ConnectAsync(MqttClientOptions options);
|
||||
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);
|
||||
|
||||
Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters);
|
||||
Task UnsubscribeAsync(IEnumerable<string> topicFilters);
|
||||
|
||||
Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MQTTnet.Core.Client
|
||||
{
|
||||
public interface IMqttClientFactory
|
||||
{
|
||||
IMqttClient CreateMqttClient();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using MQTTnet.Core.Adapter;
|
||||
|
||||
namespace MQTTnet.Core.Client
|
||||
{
|
||||
public interface IMqttCommunicationAdapterFactory
|
||||
{
|
||||
IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options);
|
||||
}
|
||||
}
|
||||
+203
-166
@@ -15,174 +15,198 @@ namespace MQTTnet.Core.Client
|
||||
public class MqttClient : IMqttClient
|
||||
{
|
||||
private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>();
|
||||
|
||||
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
||||
private readonly MqttClientOptions _options;
|
||||
private readonly IMqttCommunicationAdapter _adapter;
|
||||
private readonly IMqttCommunicationAdapterFactory _communicationChannelFactory;
|
||||
|
||||
private bool _disconnectedEventSuspended;
|
||||
private MqttClientOptions _options;
|
||||
private bool _isReceivingPackets;
|
||||
private int _latestPacketIdentifier;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private IMqttCommunicationAdapter _adapter;
|
||||
|
||||
public MqttClient(MqttClientOptions options, IMqttCommunicationAdapter adapter)
|
||||
public MqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
|
||||
|
||||
_adapter.PacketSerializer.ProtocolVersion = options.ProtocolVersion;
|
||||
_communicationChannelFactory = communicationChannelFactory ?? throw new ArgumentNullException(nameof(communicationChannelFactory));
|
||||
}
|
||||
|
||||
public event EventHandler Connected;
|
||||
|
||||
public event EventHandler Disconnected;
|
||||
|
||||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
|
||||
|
||||
public bool IsConnected { get; private set; }
|
||||
public bool IsConnected => _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested;
|
||||
|
||||
public async Task ConnectAsync(MqttApplicationMessage willApplicationMessage = null)
|
||||
public async Task ConnectAsync(MqttClientOptions options)
|
||||
{
|
||||
MqttTrace.Verbose(nameof(MqttClient), "Trying to connect.");
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (IsConnected)
|
||||
{
|
||||
throw new MqttProtocolViolationException("It is not allowed to connect with a server after the connection is established.");
|
||||
}
|
||||
ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
|
||||
|
||||
try
|
||||
{
|
||||
_disconnectedEventSuspended = false;
|
||||
|
||||
await _adapter.ConnectAsync(_options, _options.DefaultCommunicationTimeout);
|
||||
|
||||
MqttTrace.Verbose(nameof(MqttClient), "Connection with server established.");
|
||||
|
||||
var connectPacket = new MqttConnectPacket
|
||||
{
|
||||
ClientId = _options.ClientId,
|
||||
Username = _options.UserName,
|
||||
Password = _options.Password,
|
||||
CleanSession = _options.CleanSession,
|
||||
KeepAlivePeriod = (ushort)_options.KeepAlivePeriod.TotalSeconds,
|
||||
WillMessage = willApplicationMessage
|
||||
};
|
||||
|
||||
_options = options;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_latestPacketIdentifier = 0;
|
||||
_packetDispatcher.Reset();
|
||||
|
||||
StartReceivePackets();
|
||||
_adapter = _communicationChannelFactory.CreateMqttCommunicationAdapter(options);
|
||||
|
||||
MqttNetTrace.Verbose(nameof(MqttClient), "Trying to connect with server.");
|
||||
await _adapter.ConnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
|
||||
MqttNetTrace.Verbose(nameof(MqttClient), "Connection with server established.");
|
||||
|
||||
var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket);
|
||||
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
||||
{
|
||||
await DisconnectInternalAsync();
|
||||
throw new MqttConnectingFailedException(response.ConnectReturnCode);
|
||||
}
|
||||
await SetupIncomingPacketProcessingAsync();
|
||||
await AuthenticateAsync(options.WillMessage);
|
||||
|
||||
MqttNetTrace.Verbose(nameof(MqttClient), "MQTT connection with server established.");
|
||||
|
||||
if (_options.KeepAlivePeriod != TimeSpan.Zero)
|
||||
{
|
||||
StartSendKeepAliveMessages();
|
||||
StartSendKeepAliveMessages(_cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
MqttTrace.Verbose(nameof(MqttClient), "MQTT connection with server established.");
|
||||
|
||||
IsConnected = true;
|
||||
Connected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await DisconnectInternalAsync();
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await SendAsync(new MqttDisconnectPacket());
|
||||
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DisconnectInternalAsync();
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IList<MqttSubscribeResult>> SubscribeAsync(params TopicFilter[] topicFilters)
|
||||
public async Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
|
||||
{
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
return SubscribeAsync(topicFilters.ToList());
|
||||
}
|
||||
|
||||
public async Task<IList<MqttSubscribeResult>> SubscribeAsync(IList<TopicFilter> 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].");
|
||||
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var subscribePacket = new MqttSubscribePacket
|
||||
{
|
||||
PacketIdentifier = GetNewPacketIdentifier(),
|
||||
TopicFilters = topicFilters
|
||||
TopicFilters = topicFilters.ToList()
|
||||
};
|
||||
|
||||
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket);
|
||||
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket).ConfigureAwait(false);
|
||||
|
||||
if (response.SubscribeReturnCodes.Count != topicFilters.Count)
|
||||
if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count)
|
||||
{
|
||||
throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1].");
|
||||
}
|
||||
|
||||
return topicFilters.Select((t, i) => new MqttSubscribeResult(t, response.SubscribeReturnCodes[i])).ToList();
|
||||
return subscribePacket.TopicFilters.Select((t, i) => new MqttSubscribeResult(t, response.SubscribeReturnCodes[i])).ToList();
|
||||
}
|
||||
|
||||
public Task Unsubscribe(params string[] topicFilters)
|
||||
public async Task UnsubscribeAsync(IEnumerable<string> topicFilters)
|
||||
{
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
return Unsubscribe(topicFilters.ToList());
|
||||
}
|
||||
|
||||
public async Task Unsubscribe(IList<string> 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.10.3-2].");
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var unsubscribePacket = new MqttUnsubscribePacket
|
||||
{
|
||||
PacketIdentifier = GetNewPacketIdentifier(),
|
||||
TopicFilters = topicFilters
|
||||
TopicFilters = topicFilters.ToList()
|
||||
};
|
||||
|
||||
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket);
|
||||
}
|
||||
|
||||
public async Task PublishAsync(MqttApplicationMessage applicationMessage)
|
||||
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var publishPacket = applicationMessage.ToPublishPacket();
|
||||
var publishPackets = applicationMessages.Select(m => m.ToPublishPacket());
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||
foreach (var qosGroup in publishPackets.GroupBy(p => p.QualityOfServiceLevel))
|
||||
{
|
||||
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
|
||||
await SendAsync(publishPacket);
|
||||
var qosPackets = qosGroup.ToArray();
|
||||
switch (qosGroup.Key)
|
||||
{
|
||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||
{
|
||||
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
|
||||
await _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, qosPackets);
|
||||
break;
|
||||
}
|
||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||
{
|
||||
foreach (var publishPacket in qosPackets)
|
||||
{
|
||||
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
|
||||
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||
{
|
||||
foreach (var publishPacket in qosPackets)
|
||||
{
|
||||
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
|
||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false);
|
||||
await SendAndReceiveAsync<MqttPubCompPacket>(pubRecPacket.CreateResponse<MqttPubRelPacket>()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
}
|
||||
|
||||
private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
|
||||
{
|
||||
var connectPacket = new MqttConnectPacket
|
||||
{
|
||||
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
|
||||
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket);
|
||||
ClientId = _options.ClientId,
|
||||
Username = _options.UserName,
|
||||
Password = _options.Password,
|
||||
CleanSession = _options.CleanSession,
|
||||
KeepAlivePeriod = (ushort)_options.KeepAlivePeriod.TotalSeconds,
|
||||
WillMessage = willApplicationMessage
|
||||
};
|
||||
|
||||
var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket).ConfigureAwait(false);
|
||||
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
||||
{
|
||||
throw new MqttConnectingFailedException(response.ConnectReturnCode);
|
||||
}
|
||||
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
|
||||
}
|
||||
|
||||
private async Task SetupIncomingPacketProcessingAsync()
|
||||
{
|
||||
_isReceivingPackets = false;
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
Task.Factory.StartNew(
|
||||
() => ReceivePackets(_cancellationTokenSource.Token),
|
||||
_cancellationTokenSource.Token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default).ConfigureAwait(false);
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
|
||||
while (!_isReceivingPackets && _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
|
||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket);
|
||||
await SendAndReceiveAsync<MqttPubCompPacket>(pubRecPacket.CreateResponse<MqttPubRelPacket>());
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(100));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,92 +215,102 @@ namespace MQTTnet.Core.Client
|
||||
if (!IsConnected) throw new MqttCommunicationException("The client is not connected.");
|
||||
}
|
||||
|
||||
private void ThrowIfConnected(string message)
|
||||
{
|
||||
if (IsConnected) throw new MqttProtocolViolationException(message);
|
||||
}
|
||||
|
||||
private async Task DisconnectInternalAsync()
|
||||
{
|
||||
var cts = _cancellationTokenSource;
|
||||
if (cts == null || cts.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cts.Cancel(false);
|
||||
cts.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
try
|
||||
{
|
||||
await _adapter.DisconnectAsync();
|
||||
await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
|
||||
MqttNetTrace.Information(nameof(MqttClient), "Disconnected from adapter.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Warning(nameof(MqttClient), exception, "Error while disconnecting.");
|
||||
MqttNetTrace.Warning(nameof(MqttClient), exception, "Error while disconnecting from adapter.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cancellationTokenSource?.Cancel(false);
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
IsConnected = false;
|
||||
|
||||
if (!_disconnectedEventSuspended)
|
||||
{
|
||||
_disconnectedEventSuspended = true;
|
||||
Disconnected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
Disconnected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPacketAsync(MqttBasePacket mqttPacket)
|
||||
private async Task ProcessReceivedPacketAsync(MqttBasePacket packet)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (mqttPacket is MqttPingReqPacket)
|
||||
MqttNetTrace.Information(nameof(MqttClient), "Received <<< {0}", packet);
|
||||
|
||||
if (packet is MqttPingReqPacket)
|
||||
{
|
||||
return SendAsync(new MqttPingRespPacket());
|
||||
await SendAsync(new MqttPingRespPacket());
|
||||
return;
|
||||
}
|
||||
|
||||
if (mqttPacket is MqttDisconnectPacket)
|
||||
if (packet is MqttDisconnectPacket)
|
||||
{
|
||||
return DisconnectAsync();
|
||||
await DisconnectAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mqttPacket is MqttPublishPacket publishPacket)
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
{
|
||||
return ProcessReceivedPublishPacket(publishPacket);
|
||||
await ProcessReceivedPublishPacket(publishPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mqttPacket is MqttPubRelPacket pubRelPacket)
|
||||
if (packet is MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
return ProcessReceivedPubRelPacket(pubRelPacket);
|
||||
await ProcessReceivedPubRelPacket(pubRelPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
_packetDispatcher.Dispatch(mqttPacket);
|
||||
_packetDispatcher.Dispatch(packet);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttClient), exception, "Error while processing received packet.");
|
||||
MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while processing received packet.");
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
|
||||
{
|
||||
var applicationMessage = publishPacket.ToApplicationMessage();
|
||||
|
||||
try
|
||||
{
|
||||
var applicationMessage = publishPacket.ToApplicationMessage();
|
||||
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(applicationMessage));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttClient), exception, "Unhandled exception while handling application message.");
|
||||
MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while handling application message.");
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPublishPacket(MqttPublishPacket publishPacket)
|
||||
private async Task ProcessReceivedPublishPacket(MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return Task.FromResult(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
{
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
|
||||
await SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
|
||||
return;
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
|
||||
@@ -288,50 +322,33 @@ namespace MQTTnet.Core.Client
|
||||
}
|
||||
|
||||
FireApplicationMessageReceivedEvent(publishPacket);
|
||||
return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
|
||||
await SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
|
||||
return;
|
||||
}
|
||||
|
||||
throw new MqttCommunicationException("Received a not supported QoS level.");
|
||||
}
|
||||
|
||||
private async Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
|
||||
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
lock (_unacknowledgedPublishPackets)
|
||||
{
|
||||
_unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier);
|
||||
}
|
||||
|
||||
await SendAsync(pubRelPacket.CreateResponse<MqttPubCompPacket>());
|
||||
return SendAsync(pubRelPacket.CreateResponse<MqttPubCompPacket>());
|
||||
}
|
||||
|
||||
private Task SendAsync(MqttBasePacket packet)
|
||||
{
|
||||
return _adapter.SendPacketAsync(packet, _options.DefaultCommunicationTimeout);
|
||||
return _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, packet);
|
||||
}
|
||||
|
||||
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
|
||||
{
|
||||
bool ResponsePacketSelector(MqttBasePacket p)
|
||||
{
|
||||
var p1 = p as TResponsePacket;
|
||||
if (p1 == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var pi1 = requestPacket as IMqttPacketWithIdentifier;
|
||||
var pi2 = p as IMqttPacketWithIdentifier;
|
||||
|
||||
if (pi1 == null || pi2 == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return pi1.PacketIdentifier == pi2.PacketIdentifier;
|
||||
}
|
||||
|
||||
await _adapter.SendPacketAsync(requestPacket, _options.DefaultCommunicationTimeout);
|
||||
return (TResponsePacket)await _packetDispatcher.WaitForPacketAsync(ResponsePacketSelector, _options.DefaultCommunicationTimeout);
|
||||
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(requestPacket, typeof(TResponsePacket), _options.DefaultCommunicationTimeout);
|
||||
await _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false);
|
||||
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private ushort GetNewPacketIdentifier()
|
||||
@@ -341,79 +358,99 @@ namespace MQTTnet.Core.Client
|
||||
|
||||
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
MqttTrace.Information(nameof(MqttClient), "Start sending keep alive packets.");
|
||||
MqttNetTrace.Information(nameof(MqttClient), "Start sending keep alive packets.");
|
||||
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.Delay(_options.KeepAlivePeriod, cancellationToken);
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket());
|
||||
await Task.Delay(_options.KeepAlivePeriod, cancellationToken).ConfigureAwait(false);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication error while receiving packets.");
|
||||
await DisconnectInternalAsync();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending/receiving keep alive packets.");
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Warning(nameof(MqttClient), exception, "Error while sending/receiving keep alive packets.");
|
||||
await DisconnectInternalAsync();
|
||||
MqttNetTrace.Warning(nameof(MqttClient), exception, "Unhandled exception while sending/receiving keep alive packets.");
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
MqttTrace.Information(nameof(MqttClient), "Stopped sending keep alive packets.");
|
||||
MqttNetTrace.Information(nameof(MqttClient), "Stopped sending keep alive packets.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReceivePackets(CancellationToken cancellationToken)
|
||||
{
|
||||
MqttTrace.Information(nameof(MqttClient), "Start receiving packets.");
|
||||
MqttNetTrace.Information(nameof(MqttClient), "Start receiving packets.");
|
||||
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero);
|
||||
MqttTrace.Information(nameof(MqttClient), $"Received <<< {packet}");
|
||||
_isReceivingPackets = true;
|
||||
|
||||
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartProcessReceivedPacket(packet, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets.");
|
||||
await DisconnectInternalAsync();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets.");
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets.");
|
||||
await DisconnectInternalAsync();
|
||||
MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets.");
|
||||
await DisconnectInternalAsync().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
MqttTrace.Information(nameof(MqttClient), "Stopped receiving packets.");
|
||||
MqttNetTrace.Information(nameof(MqttClient), "Stopped receiving packets.");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
Task.Run(() => ProcessReceivedPacketAsync(packet), cancellationToken).ConfigureAwait(false);
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
}
|
||||
|
||||
private void StartReceivePackets()
|
||||
private void StartSendKeepAliveMessages(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(() => 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);
|
||||
Task.Factory.StartNew(() => SendKeepAliveMessagesAsync(cancellationToken), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false);
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Packets;
|
||||
|
||||
namespace MQTTnet.Core.Client
|
||||
{
|
||||
public static class MqttClientExtensions
|
||||
{
|
||||
public static Task PublishAsync(this IMqttClient client, params MqttApplicationMessage[] applicationMessages)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||
|
||||
return client.PublishAsync(applicationMessages);
|
||||
}
|
||||
|
||||
public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, params TopicFilter[] topicFilters)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
return client.SubscribeAsync(topicFilters.ToList());
|
||||
}
|
||||
|
||||
public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
|
||||
|
||||
return client.UnsubscribeAsync(topicFilters.ToList());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,12 @@ using MQTTnet.Core.Serializer;
|
||||
|
||||
namespace MQTTnet.Core.Client
|
||||
{
|
||||
public sealed class MqttClientOptions
|
||||
public abstract class MqttClientOptions
|
||||
{
|
||||
public string Server { get; set; }
|
||||
|
||||
public int? Port { get; set; }
|
||||
|
||||
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
||||
|
||||
public MqttApplicationMessage WillMessage { get; set; }
|
||||
|
||||
public string UserName { get; set; }
|
||||
|
||||
public string Password { get; set; }
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace MQTTnet.Core.Client
|
||||
{
|
||||
public class MqttClientTcpOptions : MqttClientOptions
|
||||
{
|
||||
public string Server { get; set; }
|
||||
|
||||
public int? Port { get; set; }
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace MQTTnet.Core.Client
|
||||
{
|
||||
public static class MqttClientOptionsExtensions
|
||||
public static class MqttClientTcpOptionsExtensions
|
||||
{
|
||||
public static int GetPort(this MqttClientOptions options)
|
||||
public static int GetPort(this MqttClientTcpOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
@@ -6,7 +6,11 @@ namespace MQTTnet.Core.Client
|
||||
{
|
||||
public bool UseTls { get; set; }
|
||||
|
||||
public bool CheckCertificateRevocation { get; set; }
|
||||
public bool IgnoreCertificateRevocationErrors { get; set; }
|
||||
|
||||
public bool IgnoreCertificateChainErrors { get; set; }
|
||||
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
|
||||
public List<byte[]> Certificates { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MQTTnet.Core.Client
|
||||
{
|
||||
public class MqttClientWebSocketOptions : MqttClientOptions
|
||||
{
|
||||
public string Uri { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Packets;
|
||||
|
||||
namespace MQTTnet.Core.Client
|
||||
{
|
||||
public class MqttPacketAwaiter : TaskCompletionSource<MqttBasePacket>
|
||||
{
|
||||
public MqttPacketAwaiter(Func<MqttBasePacket, bool> packetSelector)
|
||||
{
|
||||
PacketSelector = packetSelector ?? throw new ArgumentNullException(nameof(packetSelector));
|
||||
}
|
||||
|
||||
public Func<MqttBasePacket, bool> PacketSelector { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,111 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Diagnostics;
|
||||
using MQTTnet.Core.Exceptions;
|
||||
using MQTTnet.Core.Internal;
|
||||
using MQTTnet.Core.Packets;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace MQTTnet.Core.Client
|
||||
{
|
||||
public class MqttPacketDispatcher
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly List<MqttBasePacket> _receivedPackets = new List<MqttBasePacket>();
|
||||
private readonly List<MqttPacketAwaiter> _packetAwaiters = new List<MqttPacketAwaiter>();
|
||||
private readonly ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>> _packetByResponseType = new ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>>();
|
||||
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _packetByResponseTypeAndIdentifier = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>();
|
||||
|
||||
public async Task<MqttBasePacket> WaitForPacketAsync(Func<MqttBasePacket, bool> selector, TimeSpan timeout)
|
||||
public async Task<MqttBasePacket> WaitForPacketAsync(MqttBasePacket request, Type responseType, TimeSpan timeout)
|
||||
{
|
||||
if (selector == null) throw new ArgumentNullException(nameof(selector));
|
||||
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
var packetAwaiter = AddPacketAwaiter(selector);
|
||||
DispatchPendingPackets();
|
||||
|
||||
var hasTimeout = await Task.WhenAny(Task.Delay(timeout), packetAwaiter.Task) != packetAwaiter.Task;
|
||||
RemovePacketAwaiter(packetAwaiter);
|
||||
|
||||
if (hasTimeout)
|
||||
var packetAwaiter = AddPacketAwaiter(request, responseType);
|
||||
try
|
||||
{
|
||||
MqttTrace.Warning(nameof(MqttPacketDispatcher), "Timeout while waiting for packet.");
|
||||
throw new MqttCommunicationTimedOutException();
|
||||
return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (MqttCommunicationTimedOutException)
|
||||
{
|
||||
MqttNetTrace.Warning(nameof(MqttPacketDispatcher), "Timeout while waiting for packet of type '{0}'.", responseType.Name);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
RemovePacketAwaiter(request, responseType);
|
||||
}
|
||||
|
||||
return packetAwaiter.Task.Result;
|
||||
}
|
||||
|
||||
public void Dispatch(MqttBasePacket packet)
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
|
||||
var packetDispatched = false;
|
||||
foreach (var packetAwaiter in GetPacketAwaiters())
|
||||
var type = packet.GetType();
|
||||
if (packet is IMqttPacketWithIdentifier withIdentifier)
|
||||
{
|
||||
if (packetAwaiter.PacketSelector(packet))
|
||||
if (_packetByResponseTypeAndIdentifier.TryGetValue(type, out var byid))
|
||||
{
|
||||
packetAwaiter.SetResult(packet);
|
||||
packetDispatched = true;
|
||||
break;
|
||||
if (byid.TryRemove(withIdentifier.PacketIdentifier, out var tcs))
|
||||
{
|
||||
tcs.TrySetResult(packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_packetByResponseType.TryRemove(type, out var tcs))
|
||||
{
|
||||
tcs.TrySetResult(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (!packetDispatched)
|
||||
{
|
||||
_receivedPackets.Add(packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
_receivedPackets.Remove(packet);
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched.");
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_packetAwaiters.Clear();
|
||||
_receivedPackets.Clear();
|
||||
}
|
||||
_packetByResponseTypeAndIdentifier.Clear();
|
||||
_packetByResponseType.Clear();
|
||||
}
|
||||
|
||||
private List<MqttPacketAwaiter> GetPacketAwaiters()
|
||||
private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(MqttBasePacket request, Type responseType)
|
||||
{
|
||||
lock (_packetAwaiters)
|
||||
var tcs = new TaskCompletionSource<MqttBasePacket>();
|
||||
|
||||
if (request is IMqttPacketWithIdentifier requestWithIdentifier)
|
||||
{
|
||||
return new List<MqttPacketAwaiter>(_packetAwaiters);
|
||||
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
|
||||
byId[requestWithIdentifier.PacketIdentifier] = tcs;
|
||||
}
|
||||
else
|
||||
{
|
||||
_packetByResponseType[responseType] = tcs;
|
||||
}
|
||||
|
||||
return tcs;
|
||||
}
|
||||
|
||||
private MqttPacketAwaiter AddPacketAwaiter(Func<MqttBasePacket, bool> selector)
|
||||
private void RemovePacketAwaiter(MqttBasePacket request, Type responseType)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
if (request is IMqttPacketWithIdentifier requestWithIdentifier)
|
||||
{
|
||||
var packetAwaiter = new MqttPacketAwaiter(selector);
|
||||
_packetAwaiters.Add(packetAwaiter);
|
||||
return packetAwaiter;
|
||||
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
|
||||
byId.TryRemove(requestWithIdentifier.PacketIdentifier, out var _);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePacketAwaiter(MqttPacketAwaiter packetAwaiter)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
else
|
||||
{
|
||||
_packetAwaiters.Remove(packetAwaiter);
|
||||
}
|
||||
}
|
||||
|
||||
private void DispatchPendingPackets()
|
||||
{
|
||||
List<MqttBasePacket> receivedPackets;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
receivedPackets = new List<MqttBasePacket>(_receivedPackets);
|
||||
}
|
||||
|
||||
foreach (var pendingPacket in receivedPackets)
|
||||
{
|
||||
Dispatch(pendingPacket);
|
||||
_packetByResponseType.TryRemove(responseType, out var _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Core.Diagnostics
|
||||
{
|
||||
public static class MqttNetTrace
|
||||
{
|
||||
public static event EventHandler<MqttNetTraceMessagePublishedEventArgs> TraceMessagePublished;
|
||||
|
||||
public static void Verbose(string source, string message, params object[] parameters)
|
||||
{
|
||||
Publish(source, MqttNetTraceLevel.Verbose, null, message, parameters);
|
||||
}
|
||||
|
||||
public static void Information(string source, string message, params object[] parameters)
|
||||
{
|
||||
Publish(source, MqttNetTraceLevel.Information, null, message, parameters);
|
||||
}
|
||||
|
||||
public static void Warning(string source, string message, params object[] parameters)
|
||||
{
|
||||
Publish(source, MqttNetTraceLevel.Warning, null, message, parameters);
|
||||
}
|
||||
|
||||
public static void Warning(string source, Exception exception, string message, params object[] parameters)
|
||||
{
|
||||
Publish(source, MqttNetTraceLevel.Warning, exception, message, parameters);
|
||||
}
|
||||
|
||||
public static void Error(string source, string message, params object[] parameters)
|
||||
{
|
||||
Publish(source, MqttNetTraceLevel.Error, null, message, parameters);
|
||||
}
|
||||
|
||||
public static void Error(string source, Exception exception, string message, params object[] parameters)
|
||||
{
|
||||
Publish(source, MqttNetTraceLevel.Error, exception, message, parameters);
|
||||
}
|
||||
|
||||
private static void Publish(string source, MqttNetTraceLevel traceLevel, Exception exception, string message, params object[] parameters)
|
||||
{
|
||||
var handler = TraceMessagePublished;
|
||||
if (handler == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (parameters?.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
message = string.Format(message, parameters);
|
||||
}
|
||||
catch (Exception formatException)
|
||||
{
|
||||
Error(nameof(MqttNetTrace), formatException, "Error while tracing message: " + message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handler.Invoke(null, new MqttNetTraceMessagePublishedEventArgs(Environment.CurrentManagedThreadId, source, traceLevel, message, exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
namespace MQTTnet.Core.Diagnostics
|
||||
{
|
||||
public enum MqttTraceLevel
|
||||
public enum MqttNetTraceLevel
|
||||
{
|
||||
Verbose,
|
||||
Information,
|
||||
+3
-3
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace MQTTnet.Core.Diagnostics
|
||||
{
|
||||
public sealed class MqttTraceMessagePublishedEventArgs : EventArgs
|
||||
public sealed class MqttNetTraceMessagePublishedEventArgs : EventArgs
|
||||
{
|
||||
public MqttTraceMessagePublishedEventArgs(int threadId, string source, MqttTraceLevel level, string message, Exception exception)
|
||||
public MqttNetTraceMessagePublishedEventArgs(int threadId, string source, MqttNetTraceLevel level, string message, Exception exception)
|
||||
{
|
||||
ThreadId = threadId;
|
||||
Source = source;
|
||||
@@ -17,7 +17,7 @@ namespace MQTTnet.Core.Diagnostics
|
||||
|
||||
public string Source { get; }
|
||||
|
||||
public MqttTraceLevel Level { get; }
|
||||
public MqttNetTraceLevel Level { get; }
|
||||
|
||||
public string Message { get; }
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Core.Diagnostics
|
||||
{
|
||||
public static class MqttTrace
|
||||
{
|
||||
public static event EventHandler<MqttTraceMessagePublishedEventArgs> TraceMessagePublished;
|
||||
|
||||
public static void Verbose(string source, string message)
|
||||
{
|
||||
Publish(source, MqttTraceLevel.Verbose, null, message);
|
||||
}
|
||||
|
||||
public static void Information(string source, string message)
|
||||
{
|
||||
Publish(source, MqttTraceLevel.Information, null, message);
|
||||
}
|
||||
|
||||
public static void Warning(string source, string message)
|
||||
{
|
||||
Publish(source, MqttTraceLevel.Warning, null, message);
|
||||
}
|
||||
|
||||
public static void Warning(string source, Exception exception, string message)
|
||||
{
|
||||
Publish(source, MqttTraceLevel.Warning, exception, message);
|
||||
}
|
||||
|
||||
public static void Error(string source, string message)
|
||||
{
|
||||
Publish(source, MqttTraceLevel.Error, null, message);
|
||||
}
|
||||
|
||||
public static void Error(string source, Exception exception, string message)
|
||||
{
|
||||
Publish(source, MqttTraceLevel.Error, exception, message);
|
||||
}
|
||||
|
||||
private static void Publish(string source, MqttTraceLevel traceLevel, Exception exception, string message)
|
||||
{
|
||||
TraceMessagePublished?.Invoke(null, new MqttTraceMessagePublishedEventArgs(Environment.CurrentManagedThreadId, source, traceLevel, message, exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace MQTTnet.Core.Exceptions
|
||||
{
|
||||
public sealed class MqttProtocolViolationException : Exception
|
||||
{
|
||||
public MqttProtocolViolationException(string message)
|
||||
public MqttProtocolViolationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Core.Internal
|
||||
{
|
||||
public sealed class AsyncGate
|
||||
{
|
||||
private readonly Queue<TaskCompletionSource<bool>> _waitingTasks = new Queue<TaskCompletionSource<bool>>();
|
||||
|
||||
public Task WaitOneAsync()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
lock (_waitingTasks)
|
||||
{
|
||||
_waitingTasks.Enqueue(tcs);
|
||||
}
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public void Set()
|
||||
{
|
||||
lock (_waitingTasks)
|
||||
{
|
||||
if (_waitingTasks.Count > 0)
|
||||
{
|
||||
_waitingTasks.Dequeue().SetResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Exceptions;
|
||||
|
||||
namespace MQTTnet.Core.Internal
|
||||
{
|
||||
public static class TaskExtensions
|
||||
{
|
||||
public static async Task TimeoutAfter(this Task task, TimeSpan timeout)
|
||||
{
|
||||
using (var cancellationTokenSource = new CancellationTokenSource())
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeoutTask = Task.Delay(timeout, cancellationTokenSource.Token);
|
||||
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
|
||||
|
||||
if (finishedTask == timeoutTask)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException();
|
||||
}
|
||||
|
||||
if (task.IsCanceled)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
throw new MqttCommunicationException(task.Exception.GetBaseException());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout)
|
||||
{
|
||||
using (var cancellationTokenSource = new CancellationTokenSource())
|
||||
{
|
||||
try
|
||||
{
|
||||
var timeoutTask = Task.Delay(timeout, cancellationTokenSource.Token);
|
||||
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
|
||||
|
||||
if (finishedTask == timeoutTask)
|
||||
{
|
||||
throw new MqttCommunicationTimedOutException();
|
||||
}
|
||||
|
||||
if (task.IsCanceled)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
throw new MqttCommunicationException(task.Exception.GetBaseException());
|
||||
}
|
||||
|
||||
return task.Result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
<AssemblyName>MQTTnet.Core</AssemblyName>
|
||||
<RootNamespace>MQTTnet.Core</RootNamespace>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
<DebugType>Full</DebugType>
|
||||
<Description></Description>
|
||||
<Product></Product>
|
||||
<Company></Company>
|
||||
@@ -16,8 +17,8 @@
|
||||
<PackageIconUrl></PackageIconUrl>
|
||||
<RepositoryUrl></RepositoryUrl>
|
||||
<PackageTags></PackageTags>
|
||||
<FileVersion>2.2.1.0</FileVersion>
|
||||
<AssemblyVersion>2.2.1.0</AssemblyVersion>
|
||||
<FileVersion>2.4.0.0</FileVersion>
|
||||
<AssemblyVersion>2.4.0.0</AssemblyVersion>
|
||||
<PackageLicenseUrl></PackageLicenseUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using MQTTnet.Core.Protocol;
|
||||
|
||||
namespace MQTTnet.Core
|
||||
{
|
||||
public class MqttApplicationMessageFactory
|
||||
{
|
||||
public MqttApplicationMessage CreateApplicationMessage(string topic, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
|
||||
{
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
return CreateApplicationMessage(topic, new byte[0], qualityOfServiceLevel, retain);
|
||||
}
|
||||
|
||||
public MqttApplicationMessage CreateApplicationMessage(string topic, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
|
||||
{
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
if (payload == null)
|
||||
{
|
||||
payload = new byte[0];
|
||||
}
|
||||
|
||||
return new MqttApplicationMessage(topic, payload, qualityOfServiceLevel, retain);
|
||||
}
|
||||
|
||||
public MqttApplicationMessage CreateApplicationMessage<TPayload>(string topic, TPayload payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
|
||||
{
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
var payloadString = Convert.ToString(payload, CultureInfo.InvariantCulture);
|
||||
var payloadBuffer = string.IsNullOrEmpty(payloadString) ? new byte[0] : Encoding.UTF8.GetBytes(payloadString);
|
||||
|
||||
return CreateApplicationMessage(topic, payloadBuffer, qualityOfServiceLevel, retain);
|
||||
}
|
||||
|
||||
public MqttApplicationMessage CreateApplicationMessage(string topic, Stream payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
|
||||
{
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
|
||||
byte[] payloadBuffer;
|
||||
if (payload == null || payload.Length == 0)
|
||||
{
|
||||
payloadBuffer = new byte[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
payloadBuffer = new byte[payload.Length - payload.Position];
|
||||
payload.Read(payloadBuffer, 0, payloadBuffer.Length);
|
||||
}
|
||||
|
||||
return CreateApplicationMessage(topic, payloadBuffer, qualityOfServiceLevel, retain);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace MQTTnet.Core.Packets
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(MqttConnAckPacket)}: [ConnectReturnCode={ConnectReturnCode}] [IsSessionPresent={IsSessionPresent}]";
|
||||
return nameof(MqttConnAckPacket) + ": [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace MQTTnet.Core.Packets
|
||||
{
|
||||
public sealed class MqttConnectPacket: MqttBasePacket
|
||||
public sealed class MqttConnectPacket : MqttBasePacket
|
||||
{
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; }
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace MQTTnet.Core.Packets
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(MqttConnectPacket)} [ClientId={ClientId}] [Username={Username}] [Password={Password}] [KeepAlivePeriod={KeepAlivePeriod}] [CleanSession={CleanSession}]";
|
||||
return nameof(MqttConnectPacket) + ": [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using MQTTnet.Core.Protocol;
|
||||
|
||||
namespace MQTTnet.Core.Packets
|
||||
{
|
||||
public class MqttPacketHeader
|
||||
{
|
||||
public MqttControlPacketType ControlPacketType { get; set; }
|
||||
|
||||
public byte FixedHeader { get; set; }
|
||||
|
||||
public int BodyLength { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text;
|
||||
using System;
|
||||
using MQTTnet.Core.Protocol;
|
||||
|
||||
namespace MQTTnet.Core.Packets
|
||||
@@ -17,8 +17,13 @@ namespace MQTTnet.Core.Packets
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"{nameof(MqttPublishPacket)} [Topic={Topic}] [Payload={Encoding.UTF8.GetString(Payload, 0, Payload.Length)}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]";
|
||||
return nameof(MqttPublishPacket) +
|
||||
": [Topic=" + Topic + "]" +
|
||||
" [Payload=" + Convert.ToBase64String(Payload) + "]" +
|
||||
" [QoSLevel=" + QualityOfServiceLevel + "]" +
|
||||
" [Dup=" + Dup + "]" +
|
||||
" [Retain=" + Retain + "]" +
|
||||
" [PacketIdentifier=" + PacketIdentifier + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,7 @@ namespace MQTTnet.Core.Packets
|
||||
public override string ToString()
|
||||
{
|
||||
var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString()));
|
||||
return
|
||||
$"{nameof(MqttSubAckPacket)} [PacketIdentifier={PacketIdentifier}] [SubscribeReturnCodes={subscribeReturnCodesText}]";
|
||||
return nameof(MqttSubAckPacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [SubscribeReturnCodes=" + subscribeReturnCodesText + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,13 @@ namespace MQTTnet.Core.Packets
|
||||
public sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
|
||||
{
|
||||
public ushort PacketIdentifier { get; set; }
|
||||
|
||||
|
||||
public IList<TopicFilter> TopicFilters { get; set; } = new List<TopicFilter>();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var topicFiltersText = string.Join(",", TopicFilters.Select(f => $"{f.Topic}@{f.QualityOfServiceLevel}"));
|
||||
return
|
||||
$"{nameof(MqttSubscribePacket)} [PacketIdentifier={PacketIdentifier}] [TopicFilters={topicFiltersText}]";
|
||||
var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel));
|
||||
return nameof(MqttSubscribePacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace MQTTnet.Core.Packets
|
||||
public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
|
||||
{
|
||||
public ushort PacketIdentifier { get; set; }
|
||||
|
||||
|
||||
public IList<string> TopicFilters { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Packets;
|
||||
|
||||
namespace MQTTnet.Core.Serializer
|
||||
@@ -8,8 +7,8 @@ namespace MQTTnet.Core.Serializer
|
||||
{
|
||||
MqttProtocolVersion ProtocolVersion { get; set; }
|
||||
|
||||
Task SerializeAsync(MqttBasePacket mqttPacket, IMqttCommunicationChannel destination);
|
||||
byte[] Serialize(MqttBasePacket mqttPacket);
|
||||
|
||||
Task<MqttBasePacket> DeserializeAsync(IMqttCommunicationChannel source);
|
||||
MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +1,44 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Exceptions;
|
||||
using MQTTnet.Core.Protocol;
|
||||
using MQTTnet.Core.Packets;
|
||||
|
||||
namespace MQTTnet.Core.Serializer
|
||||
{
|
||||
public sealed class MqttPacketReader : IDisposable
|
||||
public sealed class MqttPacketReader : BinaryReader
|
||||
{
|
||||
private readonly MemoryStream _remainingData = new MemoryStream(1024);
|
||||
private readonly IMqttCommunicationChannel _source;
|
||||
|
||||
private int _remainingLength;
|
||||
|
||||
public MqttPacketReader(IMqttCommunicationChannel source)
|
||||
private readonly ReceivedMqttPacket _receivedMqttPacket;
|
||||
|
||||
public MqttPacketReader(ReceivedMqttPacket receivedMqttPacket)
|
||||
: base(receivedMqttPacket.Body, Encoding.UTF8, true)
|
||||
{
|
||||
_source = source ?? throw new ArgumentNullException(nameof(source));
|
||||
_receivedMqttPacket = receivedMqttPacket;
|
||||
}
|
||||
|
||||
public MqttControlPacketType ControlPacketType { get; private set; }
|
||||
public bool EndOfRemainingData => BaseStream.Position == _receivedMqttPacket.Header.BodyLength;
|
||||
|
||||
public byte FixedHeader { get; private set; }
|
||||
|
||||
public bool EndOfRemainingData => _remainingData.Position == _remainingData.Length;
|
||||
|
||||
public async Task ReadToEndAsync()
|
||||
public static MqttPacketHeader ReadHeaderFromSource(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
await ReadFixedHeaderAsync();
|
||||
await ReadRemainingLengthAsync();
|
||||
var fixedHeader = (byte)stream.ReadByte();
|
||||
var controlPacketType = (MqttControlPacketType)(fixedHeader >> 4);
|
||||
var bodyLength = ReadBodyLengthFromSource(stream, cancellationToken);
|
||||
|
||||
if (_remainingLength == 0)
|
||||
return new MqttPacketHeader
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var buffer = new byte[_remainingLength];
|
||||
await ReadFromSourceAsync(buffer);
|
||||
|
||||
_remainingData.Write(buffer, 0, buffer.Length);
|
||||
_remainingData.Position = 0;
|
||||
FixedHeader = fixedHeader,
|
||||
ControlPacketType = controlPacketType,
|
||||
BodyLength = bodyLength
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<byte> ReadRemainingDataByteAsync()
|
||||
public override ushort ReadUInt16()
|
||||
{
|
||||
return (await ReadRemainingDataAsync(1))[0];
|
||||
}
|
||||
|
||||
public async Task<ushort> ReadRemainingDataUShortAsync()
|
||||
{
|
||||
var buffer = await ReadRemainingDataAsync(2);
|
||||
var buffer = ReadBytes(2);
|
||||
|
||||
var temp = buffer[0];
|
||||
buffer[0] = buffer[1];
|
||||
@@ -59,32 +47,24 @@ namespace MQTTnet.Core.Serializer
|
||||
return BitConverter.ToUInt16(buffer, 0);
|
||||
}
|
||||
|
||||
public async Task<string> ReadRemainingDataStringWithLengthPrefixAsync()
|
||||
public string ReadStringWithLengthPrefix()
|
||||
{
|
||||
var buffer = await ReadRemainingDataWithLengthPrefixAsync();
|
||||
var buffer = ReadWithLengthPrefix();
|
||||
return Encoding.UTF8.GetString(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
public async Task<byte[]> ReadRemainingDataWithLengthPrefixAsync()
|
||||
public byte[] ReadWithLengthPrefix()
|
||||
{
|
||||
var length = await ReadRemainingDataUShortAsync();
|
||||
return await ReadRemainingDataAsync(length);
|
||||
var length = ReadUInt16();
|
||||
return ReadBytes(length);
|
||||
}
|
||||
|
||||
public Task<byte[]> ReadRemainingDataAsync()
|
||||
public byte[] ReadRemainingData()
|
||||
{
|
||||
return ReadRemainingDataAsync(_remainingLength - (int)_remainingData.Position);
|
||||
return ReadBytes(_receivedMqttPacket.Header.BodyLength - (int)BaseStream.Position);
|
||||
}
|
||||
|
||||
public async Task<byte[]> ReadRemainingDataAsync(int length)
|
||||
{
|
||||
var buffer = new byte[length];
|
||||
await _remainingData.ReadAsync(buffer, 0, buffer.Length);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private async Task ReadRemainingLengthAsync()
|
||||
private static int ReadBodyLengthFromSource(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
|
||||
var multiplier = 1;
|
||||
@@ -92,49 +72,20 @@ namespace MQTTnet.Core.Serializer
|
||||
byte encodedByte;
|
||||
do
|
||||
{
|
||||
encodedByte = await ReadStreamByteAsync();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
encodedByte = (byte)stream.ReadByte();
|
||||
value += (encodedByte & 127) * multiplier;
|
||||
multiplier *= 128;
|
||||
if (multiplier > 128 * 128 * 128)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Remaining length is ivalid.");
|
||||
throw new MqttProtocolViolationException("Remaining length is invalid.");
|
||||
}
|
||||
} while ((encodedByte & 128) != 0);
|
||||
|
||||
_remainingLength = value;
|
||||
}
|
||||
|
||||
private Task ReadFromSourceAsync(byte[] buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _source.ReadAsync(buffer);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<byte> ReadStreamByteAsync()
|
||||
{
|
||||
var buffer = new byte[1];
|
||||
await ReadFromSourceAsync(buffer);
|
||||
return buffer[0];
|
||||
}
|
||||
|
||||
private async Task ReadFixedHeaderAsync()
|
||||
{
|
||||
FixedHeader = await ReadStreamByteAsync();
|
||||
|
||||
var byteReader = new ByteReader(FixedHeader);
|
||||
byteReader.Read(4);
|
||||
ControlPacketType = (MqttControlPacketType)byteReader.Read(4);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_remainingData?.Dispose();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Exceptions;
|
||||
using MQTTnet.Core.Packets;
|
||||
using MQTTnet.Core.Protocol;
|
||||
@@ -16,232 +17,251 @@ namespace MQTTnet.Core.Serializer
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||
|
||||
public Task SerializeAsync(MqttBasePacket packet, IMqttCommunicationChannel destination)
|
||||
public byte[] Serialize(MqttBasePacket packet)
|
||||
{
|
||||
if (packet == null) throw new ArgumentNullException(nameof(packet));
|
||||
if (destination == null) throw new ArgumentNullException(nameof(destination));
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
using (var writer = new MqttPacketWriter(stream))
|
||||
{
|
||||
var header = new List<byte> { SerializePacket(packet, writer) };
|
||||
var body = stream.ToArray();
|
||||
MqttPacketWriter.BuildLengthHeader(body.Length, header);
|
||||
var headerArray = header.ToArray();
|
||||
var writeBuffer = new byte[header.Count + body.Length];
|
||||
Buffer.BlockCopy(headerArray, 0, writeBuffer, 0, headerArray.Length);
|
||||
Buffer.BlockCopy(body, 0, writeBuffer, headerArray.Length, body.Length);
|
||||
|
||||
return writeBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
if (packet is MqttConnectPacket connectPacket)
|
||||
{
|
||||
return SerializeAsync(connectPacket, destination);
|
||||
return Serialize(connectPacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttConnAckPacket connAckPacket)
|
||||
{
|
||||
return SerializeAsync(connAckPacket, destination);
|
||||
return Serialize(connAckPacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttDisconnectPacket disconnectPacket)
|
||||
{
|
||||
return SerializeAsync(disconnectPacket, destination);
|
||||
return Serialize(disconnectPacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttPingReqPacket pingReqPacket)
|
||||
{
|
||||
return SerializeAsync(pingReqPacket, destination);
|
||||
return Serialize(pingReqPacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttPingRespPacket pingRespPacket)
|
||||
{
|
||||
return SerializeAsync(pingRespPacket, destination);
|
||||
return Serialize(pingRespPacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
{
|
||||
return SerializeAsync(publishPacket, destination);
|
||||
return Serialize(publishPacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttPubAckPacket pubAckPacket)
|
||||
{
|
||||
return SerializeAsync(pubAckPacket, destination);
|
||||
return Serialize(pubAckPacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttPubRecPacket pubRecPacket)
|
||||
{
|
||||
return SerializeAsync(pubRecPacket, destination);
|
||||
return Serialize(pubRecPacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
return SerializeAsync(pubRelPacket, destination);
|
||||
return Serialize(pubRelPacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttPubCompPacket pubCompPacket)
|
||||
{
|
||||
return SerializeAsync(pubCompPacket, destination);
|
||||
return Serialize(pubCompPacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttSubscribePacket subscribePacket)
|
||||
{
|
||||
return SerializeAsync(subscribePacket, destination);
|
||||
return Serialize(subscribePacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttSubAckPacket subAckPacket)
|
||||
{
|
||||
return SerializeAsync(subAckPacket, destination);
|
||||
return Serialize(subAckPacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttUnsubscribePacket unsubscribePacket)
|
||||
{
|
||||
return SerializeAsync(unsubscribePacket, destination);
|
||||
return Serialize(unsubscribePacket, writer);
|
||||
}
|
||||
|
||||
if (packet is MqttUnsubAckPacket unsubAckPacket)
|
||||
{
|
||||
return SerializeAsync(unsubAckPacket, destination);
|
||||
return Serialize(unsubAckPacket, writer);
|
||||
}
|
||||
|
||||
throw new MqttProtocolViolationException("Packet type invalid.");
|
||||
}
|
||||
|
||||
public async Task<MqttBasePacket> DeserializeAsync(IMqttCommunicationChannel source)
|
||||
public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket)
|
||||
{
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket));
|
||||
|
||||
using (var mqttPacketReader = new MqttPacketReader(source))
|
||||
using (var reader = new MqttPacketReader(receivedMqttPacket))
|
||||
{
|
||||
await mqttPacketReader.ReadToEndAsync();
|
||||
|
||||
switch (mqttPacketReader.ControlPacketType)
|
||||
{
|
||||
case MqttControlPacketType.Connect:
|
||||
{
|
||||
return await DeserializeConnectAsync(mqttPacketReader);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.ConnAck:
|
||||
{
|
||||
return await DeserializeConnAck(mqttPacketReader);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.Disconnect:
|
||||
{
|
||||
return new MqttDisconnectPacket();
|
||||
}
|
||||
|
||||
case MqttControlPacketType.Publish:
|
||||
{
|
||||
return await DeserializePublishAsync(mqttPacketReader);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PubAck:
|
||||
{
|
||||
return new MqttPubAckPacket
|
||||
{
|
||||
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
|
||||
};
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PubRec:
|
||||
{
|
||||
return new MqttPubRecPacket
|
||||
{
|
||||
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
|
||||
};
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PubRel:
|
||||
{
|
||||
return new MqttPubRelPacket
|
||||
{
|
||||
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
|
||||
};
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PubComp:
|
||||
{
|
||||
return new MqttPubCompPacket
|
||||
{
|
||||
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
|
||||
};
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PingReq:
|
||||
{
|
||||
return new MqttPingReqPacket();
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PingResp:
|
||||
{
|
||||
return new MqttPingRespPacket();
|
||||
}
|
||||
|
||||
case MqttControlPacketType.Subscribe:
|
||||
{
|
||||
return await DeserializeSubscribeAsync(mqttPacketReader);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.SubAck:
|
||||
{
|
||||
return await DeserializeSubAck(mqttPacketReader);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.Unsubscibe:
|
||||
{
|
||||
return await DeserializeUnsubscribeAsync(mqttPacketReader);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.UnsubAck:
|
||||
{
|
||||
return new MqttUnsubAckPacket
|
||||
{
|
||||
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Packet type ({(int)mqttPacketReader.ControlPacketType}) not supported.");
|
||||
}
|
||||
}
|
||||
return Deserialize(receivedMqttPacket.Header, reader);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<MqttBasePacket> DeserializeUnsubscribeAsync(MqttPacketReader reader)
|
||||
private static MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader)
|
||||
{
|
||||
switch (header.ControlPacketType)
|
||||
{
|
||||
case MqttControlPacketType.Connect:
|
||||
{
|
||||
return DeserializeConnect(reader);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.ConnAck:
|
||||
{
|
||||
return DeserializeConnAck(reader);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.Disconnect:
|
||||
{
|
||||
return new MqttDisconnectPacket();
|
||||
}
|
||||
|
||||
case MqttControlPacketType.Publish:
|
||||
{
|
||||
return DeserializePublish(reader, header);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PubAck:
|
||||
{
|
||||
return new MqttPubAckPacket
|
||||
{
|
||||
PacketIdentifier = reader.ReadUInt16()
|
||||
};
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PubRec:
|
||||
{
|
||||
return new MqttPubRecPacket
|
||||
{
|
||||
PacketIdentifier = reader.ReadUInt16()
|
||||
};
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PubRel:
|
||||
{
|
||||
return new MqttPubRelPacket
|
||||
{
|
||||
PacketIdentifier = reader.ReadUInt16()
|
||||
};
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PubComp:
|
||||
{
|
||||
return new MqttPubCompPacket
|
||||
{
|
||||
PacketIdentifier = reader.ReadUInt16()
|
||||
};
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PingReq:
|
||||
{
|
||||
return new MqttPingReqPacket();
|
||||
}
|
||||
|
||||
case MqttControlPacketType.PingResp:
|
||||
{
|
||||
return new MqttPingRespPacket();
|
||||
}
|
||||
|
||||
case MqttControlPacketType.Subscribe:
|
||||
{
|
||||
return DeserializeSubscribe(reader);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.SubAck:
|
||||
{
|
||||
return DeserializeSubAck(reader);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.Unsubscibe:
|
||||
{
|
||||
return DeserializeUnsubscribe(reader);
|
||||
}
|
||||
|
||||
case MqttControlPacketType.UnsubAck:
|
||||
{
|
||||
return new MqttUnsubAckPacket
|
||||
{
|
||||
PacketIdentifier = reader.ReadUInt16()
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new MqttProtocolViolationException($"Packet type ({(int)header.ControlPacketType}) not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static MqttBasePacket DeserializeUnsubscribe(MqttPacketReader reader)
|
||||
{
|
||||
var packet = new MqttUnsubscribePacket
|
||||
{
|
||||
PacketIdentifier = await reader.ReadRemainingDataUShortAsync(),
|
||||
PacketIdentifier = reader.ReadUInt16(),
|
||||
};
|
||||
|
||||
while (!reader.EndOfRemainingData)
|
||||
{
|
||||
packet.TopicFilters.Add(await reader.ReadRemainingDataStringWithLengthPrefixAsync());
|
||||
packet.TopicFilters.Add(reader.ReadStringWithLengthPrefix());
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static async Task<MqttBasePacket> DeserializeSubscribeAsync(MqttPacketReader reader)
|
||||
private static MqttBasePacket DeserializeSubscribe(MqttPacketReader reader)
|
||||
{
|
||||
var packet = new MqttSubscribePacket
|
||||
{
|
||||
PacketIdentifier = await reader.ReadRemainingDataUShortAsync(),
|
||||
PacketIdentifier = reader.ReadUInt16()
|
||||
};
|
||||
|
||||
while (!reader.EndOfRemainingData)
|
||||
{
|
||||
packet.TopicFilters.Add(new TopicFilter(
|
||||
await reader.ReadRemainingDataStringWithLengthPrefixAsync(),
|
||||
(MqttQualityOfServiceLevel)await reader.ReadRemainingDataByteAsync()));
|
||||
reader.ReadStringWithLengthPrefix(),
|
||||
(MqttQualityOfServiceLevel)reader.ReadByte()));
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static async Task<MqttBasePacket> DeserializePublishAsync(MqttPacketReader reader)
|
||||
private static MqttBasePacket DeserializePublish(MqttPacketReader reader, MqttPacketHeader mqttPacketHeader)
|
||||
{
|
||||
var fixedHeader = new ByteReader(reader.FixedHeader);
|
||||
var fixedHeader = new ByteReader(mqttPacketHeader.FixedHeader);
|
||||
var retain = fixedHeader.Read();
|
||||
var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2);
|
||||
var dup = fixedHeader.Read();
|
||||
|
||||
var topic = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
|
||||
var topic = reader.ReadStringWithLengthPrefix();
|
||||
|
||||
ushort packetIdentifier = 0;
|
||||
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
packetIdentifier = await reader.ReadRemainingDataUShortAsync();
|
||||
packetIdentifier = reader.ReadUInt16();
|
||||
}
|
||||
|
||||
var packet = new MqttPublishPacket
|
||||
@@ -250,22 +270,22 @@ namespace MQTTnet.Core.Serializer
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
Dup = dup,
|
||||
Topic = topic,
|
||||
Payload = await reader.ReadRemainingDataAsync(),
|
||||
Payload = reader.ReadRemainingData(),
|
||||
PacketIdentifier = packetIdentifier
|
||||
};
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static async Task<MqttBasePacket> DeserializeConnectAsync(MqttPacketReader reader)
|
||||
private static MqttBasePacket DeserializeConnect(MqttPacketReader reader)
|
||||
{
|
||||
await reader.ReadRemainingDataAsync(2); // Skip 2 bytes
|
||||
reader.ReadBytes(2); // Skip 2 bytes
|
||||
|
||||
MqttProtocolVersion protocolVersion;
|
||||
var protocolName = await reader.ReadRemainingDataAsync(4);
|
||||
var protocolName = reader.ReadBytes(4);
|
||||
if (protocolName.SequenceEqual(ProtocolVersionV310Name))
|
||||
{
|
||||
await reader.ReadRemainingDataAsync(2);
|
||||
reader.ReadBytes(2);
|
||||
protocolVersion = MqttProtocolVersion.V310;
|
||||
}
|
||||
else if (protocolName.SequenceEqual(ProtocolVersionV311Name))
|
||||
@@ -277,8 +297,8 @@ namespace MQTTnet.Core.Serializer
|
||||
throw new MqttProtocolViolationException("Protocol name is not supported.");
|
||||
}
|
||||
|
||||
var protocolLevel = await reader.ReadRemainingDataByteAsync();
|
||||
var connectFlags = await reader.ReadRemainingDataByteAsync();
|
||||
var protocolLevel = reader.ReadByte();
|
||||
var connectFlags = reader.ReadByte();
|
||||
|
||||
var connectFlagsReader = new ByteReader(connectFlags);
|
||||
connectFlagsReader.Read(); // Reserved.
|
||||
@@ -295,51 +315,51 @@ namespace MQTTnet.Core.Serializer
|
||||
var passwordFlag = connectFlagsReader.Read();
|
||||
var usernameFlag = connectFlagsReader.Read();
|
||||
|
||||
packet.KeepAlivePeriod = await reader.ReadRemainingDataUShortAsync();
|
||||
packet.ClientId = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
|
||||
packet.KeepAlivePeriod = reader.ReadUInt16();
|
||||
packet.ClientId = reader.ReadStringWithLengthPrefix();
|
||||
|
||||
if (willFlag)
|
||||
{
|
||||
packet.WillMessage = new MqttApplicationMessage(
|
||||
await reader.ReadRemainingDataStringWithLengthPrefixAsync(),
|
||||
await reader.ReadRemainingDataWithLengthPrefixAsync(),
|
||||
reader.ReadStringWithLengthPrefix(),
|
||||
reader.ReadWithLengthPrefix(),
|
||||
(MqttQualityOfServiceLevel)willQoS,
|
||||
willRetain);
|
||||
}
|
||||
|
||||
if (usernameFlag)
|
||||
{
|
||||
packet.Username = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
|
||||
packet.Username = reader.ReadStringWithLengthPrefix();
|
||||
}
|
||||
|
||||
if (passwordFlag)
|
||||
{
|
||||
packet.Password = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
|
||||
packet.Password = reader.ReadStringWithLengthPrefix();
|
||||
}
|
||||
|
||||
ValidateConnectPacket(packet);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static async Task<MqttBasePacket> DeserializeSubAck(MqttPacketReader reader)
|
||||
private static MqttBasePacket DeserializeSubAck(MqttPacketReader reader)
|
||||
{
|
||||
var packet = new MqttSubAckPacket
|
||||
{
|
||||
PacketIdentifier = await reader.ReadRemainingDataUShortAsync()
|
||||
PacketIdentifier = reader.ReadUInt16()
|
||||
};
|
||||
|
||||
while (!reader.EndOfRemainingData)
|
||||
{
|
||||
packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)await reader.ReadRemainingDataByteAsync());
|
||||
packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)reader.ReadByte());
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static async Task<MqttBasePacket> DeserializeConnAck(MqttPacketReader reader)
|
||||
private static MqttBasePacket DeserializeConnAck(MqttPacketReader reader)
|
||||
{
|
||||
var variableHeader1 = await reader.ReadRemainingDataByteAsync();
|
||||
var variableHeader2 = await reader.ReadRemainingDataByteAsync();
|
||||
var variableHeader1 = reader.ReadByte();
|
||||
var variableHeader2 = reader.ReadByte();
|
||||
|
||||
var packet = new MqttConnAckPacket
|
||||
{
|
||||
@@ -366,260 +386,223 @@ namespace MQTTnet.Core.Serializer
|
||||
}
|
||||
}
|
||||
|
||||
private Task SerializeAsync(MqttConnectPacket packet, IMqttCommunicationChannel destination)
|
||||
private byte Serialize(MqttConnectPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
ValidateConnectPacket(packet);
|
||||
|
||||
using (var output = new MqttPacketWriter())
|
||||
// Write variable header
|
||||
writer.Write(0x00, 0x04); // 3.1.2.1 Protocol Name
|
||||
if (ProtocolVersion == MqttProtocolVersion.V311)
|
||||
{
|
||||
// Write variable header
|
||||
output.Write(0x00, 0x04); // 3.1.2.1 Protocol Name
|
||||
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
|
||||
connectFlags.Write(false); // Reserved
|
||||
connectFlags.Write(packet.CleanSession);
|
||||
connectFlags.Write(packet.WillMessage != null);
|
||||
|
||||
if (packet.WillMessage != null)
|
||||
{
|
||||
connectFlags.Write((int)packet.WillMessage.QualityOfServiceLevel, 2);
|
||||
connectFlags.Write(packet.WillMessage.Retain);
|
||||
}
|
||||
else
|
||||
{
|
||||
connectFlags.Write(0, 2);
|
||||
connectFlags.Write(false);
|
||||
}
|
||||
|
||||
connectFlags.Write(packet.Password != null);
|
||||
connectFlags.Write(packet.Username != null);
|
||||
|
||||
output.Write(connectFlags);
|
||||
output.Write(packet.KeepAlivePeriod);
|
||||
output.WriteWithLengthPrefix(packet.ClientId);
|
||||
|
||||
if (packet.WillMessage != null)
|
||||
{
|
||||
output.WriteWithLengthPrefix(packet.WillMessage.Topic);
|
||||
output.WriteWithLengthPrefix(packet.WillMessage.Payload);
|
||||
}
|
||||
|
||||
if (packet.Username != null)
|
||||
{
|
||||
output.WriteWithLengthPrefix(packet.Username);
|
||||
}
|
||||
|
||||
if (packet.Password != null)
|
||||
{
|
||||
output.WriteWithLengthPrefix(packet.Password);
|
||||
}
|
||||
|
||||
output.InjectFixedHeader(MqttControlPacketType.Connect);
|
||||
return output.WriteToAsync(destination);
|
||||
writer.Write(ProtocolVersionV311Name);
|
||||
writer.Write(0x04); // 3.1.2.2 Protocol Level (4)
|
||||
}
|
||||
}
|
||||
|
||||
private Task SerializeAsync(MqttConnAckPacket packet, IMqttCommunicationChannel destination)
|
||||
{
|
||||
using (var output = new MqttPacketWriter())
|
||||
else
|
||||
{
|
||||
var connectAcknowledgeFlags = new ByteWriter();
|
||||
|
||||
if (ProtocolVersion == MqttProtocolVersion.V311)
|
||||
{
|
||||
connectAcknowledgeFlags.Write(packet.IsSessionPresent);
|
||||
}
|
||||
|
||||
output.Write(connectAcknowledgeFlags);
|
||||
output.Write((byte)packet.ConnectReturnCode);
|
||||
|
||||
output.InjectFixedHeader(MqttControlPacketType.ConnAck);
|
||||
return output.WriteToAsync(destination);
|
||||
writer.Write(ProtocolVersionV310Name);
|
||||
writer.Write(0x64, 0x70, 0x03); // Protocol Level (0x03)
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SerializeAsync(MqttPubRelPacket packet, IMqttCommunicationChannel destination)
|
||||
{
|
||||
using (var output = new MqttPacketWriter())
|
||||
var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags
|
||||
connectFlags.Write(false); // Reserved
|
||||
connectFlags.Write(packet.CleanSession);
|
||||
connectFlags.Write(packet.WillMessage != null);
|
||||
|
||||
if (packet.WillMessage != null)
|
||||
{
|
||||
output.Write(packet.PacketIdentifier);
|
||||
|
||||
output.InjectFixedHeader(MqttControlPacketType.PubRel, 0x02);
|
||||
await output.WriteToAsync(destination);
|
||||
connectFlags.Write((int)packet.WillMessage.QualityOfServiceLevel, 2);
|
||||
connectFlags.Write(packet.WillMessage.Retain);
|
||||
}
|
||||
else
|
||||
{
|
||||
connectFlags.Write(0, 2);
|
||||
connectFlags.Write(false);
|
||||
}
|
||||
|
||||
connectFlags.Write(packet.Password != null);
|
||||
connectFlags.Write(packet.Username != null);
|
||||
|
||||
writer.Write(connectFlags);
|
||||
writer.Write(packet.KeepAlivePeriod);
|
||||
writer.WriteWithLengthPrefix(packet.ClientId);
|
||||
|
||||
if (packet.WillMessage != null)
|
||||
{
|
||||
writer.WriteWithLengthPrefix(packet.WillMessage.Topic);
|
||||
writer.WriteWithLengthPrefix(packet.WillMessage.Payload);
|
||||
}
|
||||
|
||||
if (packet.Username != null)
|
||||
{
|
||||
writer.WriteWithLengthPrefix(packet.Username);
|
||||
}
|
||||
|
||||
if (packet.Password != null)
|
||||
{
|
||||
writer.WriteWithLengthPrefix(packet.Password);
|
||||
}
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Connect);
|
||||
}
|
||||
|
||||
private static Task SerializeAsync(MqttDisconnectPacket packet, IMqttCommunicationChannel destination)
|
||||
private byte Serialize(MqttConnAckPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
return SerializeEmptyPacketAsync(MqttControlPacketType.Disconnect, destination);
|
||||
var connectAcknowledgeFlags = new ByteWriter();
|
||||
|
||||
if (ProtocolVersion == MqttProtocolVersion.V311)
|
||||
{
|
||||
connectAcknowledgeFlags.Write(packet.IsSessionPresent);
|
||||
}
|
||||
|
||||
writer.Write(connectAcknowledgeFlags);
|
||||
writer.Write((byte)packet.ConnectReturnCode);
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck);
|
||||
}
|
||||
|
||||
private static Task SerializeAsync(MqttPingReqPacket packet, IMqttCommunicationChannel destination)
|
||||
private static byte Serialize(MqttPubRelPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
return SerializeEmptyPacketAsync(MqttControlPacketType.PingReq, destination);
|
||||
writer.Write(packet.PacketIdentifier);
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02);
|
||||
}
|
||||
|
||||
private static Task SerializeAsync(MqttPingRespPacket packet, IMqttCommunicationChannel destination)
|
||||
private static byte Serialize(MqttDisconnectPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
return SerializeEmptyPacketAsync(MqttControlPacketType.PingResp, destination);
|
||||
return SerializeEmptyPacketAsync(MqttControlPacketType.Disconnect, writer);
|
||||
}
|
||||
|
||||
private static Task SerializeAsync(MqttPublishPacket packet, IMqttCommunicationChannel destination)
|
||||
private static byte Serialize(MqttPingReqPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
return SerializeEmptyPacketAsync(MqttControlPacketType.PingReq, writer);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttPingRespPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
return SerializeEmptyPacketAsync(MqttControlPacketType.PingResp, writer);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttPublishPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
ValidatePublishPacket(packet);
|
||||
|
||||
using (var output = new MqttPacketWriter())
|
||||
{
|
||||
output.WriteWithLengthPrefix(packet.Topic);
|
||||
writer.WriteWithLengthPrefix(packet.Topic);
|
||||
|
||||
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
writer.Write(packet.PacketIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (packet.PacketIdentifier > 0)
|
||||
{
|
||||
output.Write(packet.PacketIdentifier);
|
||||
throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5].");
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
if (packet.Payload?.Length > 0)
|
||||
{
|
||||
writer.Write(packet.Payload);
|
||||
}
|
||||
|
||||
byte fixedHeader = 0;
|
||||
|
||||
if (packet.Retain)
|
||||
{
|
||||
fixedHeader |= 0x01;
|
||||
}
|
||||
|
||||
fixedHeader |= (byte)((byte)packet.QualityOfServiceLevel << 1);
|
||||
|
||||
if (packet.Dup)
|
||||
{
|
||||
fixedHeader |= 0x08;
|
||||
}
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Publish, fixedHeader);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttPubAckPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
writer.Write(packet.PacketIdentifier);
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubAck);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttPubRecPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
writer.Write(packet.PacketIdentifier);
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRec);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttPubCompPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
writer.Write(packet.PacketIdentifier);
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubComp);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttSubscribePacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3].");
|
||||
|
||||
writer.Write(packet.PacketIdentifier);
|
||||
|
||||
if (packet.TopicFilters?.Count > 0)
|
||||
{
|
||||
foreach (var topicFilter in packet.TopicFilters)
|
||||
{
|
||||
if (packet.PacketIdentifier > 0)
|
||||
{
|
||||
throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5].");
|
||||
}
|
||||
writer.WriteWithLengthPrefix(topicFilter.Topic);
|
||||
writer.Write((byte)topicFilter.QualityOfServiceLevel);
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.Payload?.Length > 0)
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Subscribe, 0x02);
|
||||
}
|
||||
|
||||
private static byte Serialize(MqttSubAckPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
writer.Write(packet.PacketIdentifier);
|
||||
|
||||
if (packet.SubscribeReturnCodes?.Any() == true)
|
||||
{
|
||||
foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes)
|
||||
{
|
||||
output.Write(packet.Payload);
|
||||
writer.Write((byte)packetSubscribeReturnCode);
|
||||
}
|
||||
|
||||
var fixedHeader = new ByteWriter();
|
||||
fixedHeader.Write(packet.Retain);
|
||||
fixedHeader.Write((byte)packet.QualityOfServiceLevel, 2);
|
||||
fixedHeader.Write(packet.Dup);
|
||||
|
||||
output.InjectFixedHeader(MqttControlPacketType.Publish, fixedHeader.Value);
|
||||
return output.WriteToAsync(destination);
|
||||
}
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.SubAck);
|
||||
}
|
||||
|
||||
private static Task SerializeAsync(MqttPubAckPacket packet, IMqttCommunicationChannel destination)
|
||||
private static byte Serialize(MqttUnsubscribePacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
using (var output = new MqttPacketWriter())
|
||||
if (!packet.TopicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2].");
|
||||
|
||||
writer.Write(packet.PacketIdentifier);
|
||||
|
||||
if (packet.TopicFilters?.Any() == true)
|
||||
{
|
||||
output.Write(packet.PacketIdentifier);
|
||||
|
||||
output.InjectFixedHeader(MqttControlPacketType.PubAck);
|
||||
return output.WriteToAsync(destination);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task SerializeAsync(MqttPubRecPacket packet, IMqttCommunicationChannel destination)
|
||||
{
|
||||
using (var output = new MqttPacketWriter())
|
||||
{
|
||||
output.Write(packet.PacketIdentifier);
|
||||
|
||||
output.InjectFixedHeader(MqttControlPacketType.PubRec);
|
||||
return output.WriteToAsync(destination);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task SerializeAsync(MqttPubCompPacket packet, IMqttCommunicationChannel destination)
|
||||
{
|
||||
using (var output = new MqttPacketWriter())
|
||||
{
|
||||
output.Write(packet.PacketIdentifier);
|
||||
|
||||
output.InjectFixedHeader(MqttControlPacketType.PubComp);
|
||||
return output.WriteToAsync(destination);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task SerializeAsync(MqttSubscribePacket packet, IMqttCommunicationChannel destination)
|
||||
{
|
||||
using (var output = new MqttPacketWriter())
|
||||
{
|
||||
output.Write(packet.PacketIdentifier);
|
||||
|
||||
if (packet.TopicFilters?.Count > 0)
|
||||
foreach (var topicFilter in packet.TopicFilters)
|
||||
{
|
||||
foreach (var topicFilter in packet.TopicFilters)
|
||||
{
|
||||
output.WriteWithLengthPrefix(topicFilter.Topic);
|
||||
output.Write((byte)topicFilter.QualityOfServiceLevel);
|
||||
}
|
||||
writer.WriteWithLengthPrefix(topicFilter);
|
||||
}
|
||||
|
||||
output.InjectFixedHeader(MqttControlPacketType.Subscribe, 0x02);
|
||||
return output.WriteToAsync(destination);
|
||||
}
|
||||
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
|
||||
}
|
||||
|
||||
private static Task SerializeAsync(MqttSubAckPacket packet, IMqttCommunicationChannel destination)
|
||||
private static byte Serialize(MqttUnsubAckPacket packet, MqttPacketWriter writer)
|
||||
{
|
||||
using (var output = new MqttPacketWriter())
|
||||
{
|
||||
output.Write(packet.PacketIdentifier);
|
||||
writer.Write(packet.PacketIdentifier);
|
||||
|
||||
if (packet.SubscribeReturnCodes?.Any() == true)
|
||||
{
|
||||
foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes)
|
||||
{
|
||||
output.Write((byte)packetSubscribeReturnCode);
|
||||
}
|
||||
}
|
||||
|
||||
output.InjectFixedHeader(MqttControlPacketType.SubAck);
|
||||
return output.WriteToAsync(destination);
|
||||
}
|
||||
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
|
||||
}
|
||||
|
||||
private static Task SerializeAsync(MqttUnsubscribePacket packet, IMqttCommunicationChannel destination)
|
||||
private static byte SerializeEmptyPacketAsync(MqttControlPacketType type, MqttPacketWriter writer)
|
||||
{
|
||||
using (var output = new MqttPacketWriter())
|
||||
{
|
||||
output.Write(packet.PacketIdentifier);
|
||||
|
||||
if (packet.TopicFilters?.Any() == true)
|
||||
{
|
||||
foreach (var topicFilter in packet.TopicFilters)
|
||||
{
|
||||
output.WriteWithLengthPrefix(topicFilter);
|
||||
}
|
||||
}
|
||||
|
||||
output.InjectFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
|
||||
return output.WriteToAsync(destination);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task SerializeAsync(MqttUnsubAckPacket packet, IMqttCommunicationChannel destination)
|
||||
{
|
||||
using (var output = new MqttPacketWriter())
|
||||
{
|
||||
output.Write(packet.PacketIdentifier);
|
||||
|
||||
output.InjectFixedHeader(MqttControlPacketType.UnsubAck);
|
||||
return output.WriteToAsync(destination);
|
||||
}
|
||||
}
|
||||
|
||||
private static Task SerializeEmptyPacketAsync(MqttControlPacketType type, IMqttCommunicationChannel destination)
|
||||
{
|
||||
using (var output = new MqttPacketWriter())
|
||||
{
|
||||
output.InjectFixedHeader(type);
|
||||
return output.WriteToAsync(destination);
|
||||
}
|
||||
return MqttPacketWriter.BuildFixedHeader(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,46 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Protocol;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MQTTnet.Core.Serializer
|
||||
{
|
||||
public sealed class MqttPacketWriter : IDisposable
|
||||
public sealed class MqttPacketWriter : BinaryWriter
|
||||
{
|
||||
private readonly MemoryStream _buffer = new MemoryStream(1024);
|
||||
public MqttPacketWriter(Stream stream)
|
||||
: base(stream)
|
||||
{
|
||||
}
|
||||
|
||||
public void InjectFixedHeader(MqttControlPacketType packetType, byte flags = 0)
|
||||
public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0)
|
||||
{
|
||||
var fixedHeader = (int)packetType << 4;
|
||||
fixedHeader |= flags;
|
||||
InjectFixedHeader((byte)fixedHeader);
|
||||
return (byte)fixedHeader;
|
||||
}
|
||||
|
||||
public void Write(byte value)
|
||||
{
|
||||
_buffer.WriteByte(value);
|
||||
}
|
||||
|
||||
public void Write(ushort value)
|
||||
public override void Write(ushort value)
|
||||
{
|
||||
var buffer = BitConverter.GetBytes(value);
|
||||
_buffer.WriteByte(buffer[1]);
|
||||
_buffer.WriteByte(buffer[0]);
|
||||
Write(buffer[1], buffer[0]);
|
||||
}
|
||||
|
||||
public new void Write(params byte[] values)
|
||||
{
|
||||
base.Write(values);
|
||||
}
|
||||
|
||||
public new void Write(byte value)
|
||||
{
|
||||
base.Write(value);
|
||||
}
|
||||
|
||||
public void Write(ByteWriter value)
|
||||
{
|
||||
if (value == null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
_buffer.WriteByte(value.Value);
|
||||
}
|
||||
|
||||
public void Write(params byte[] value)
|
||||
{
|
||||
if (value == null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
_buffer.Write(value, 0, value.Length);
|
||||
Write(value.Value);
|
||||
}
|
||||
|
||||
public void WriteWithLengthPrefix(string value)
|
||||
@@ -57,36 +56,16 @@ namespace MQTTnet.Core.Serializer
|
||||
Write(value);
|
||||
}
|
||||
|
||||
public Task WriteToAsync(IMqttCommunicationChannel destination)
|
||||
public static void BuildLengthHeader(int length, List<byte> header)
|
||||
{
|
||||
if (destination == null) throw new ArgumentNullException(nameof(destination));
|
||||
|
||||
return destination.WriteAsync(_buffer.ToArray());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_buffer?.Dispose();
|
||||
}
|
||||
|
||||
private void InjectFixedHeader(byte fixedHeader)
|
||||
{
|
||||
if (_buffer.Length == 0)
|
||||
if (length == 0)
|
||||
{
|
||||
Write(fixedHeader);
|
||||
Write(0);
|
||||
header.Add(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;
|
||||
var x = length;
|
||||
do
|
||||
{
|
||||
var encodedByte = x % 128;
|
||||
@@ -96,10 +75,8 @@ namespace MQTTnet.Core.Serializer
|
||||
encodedByte = encodedByte | 128;
|
||||
}
|
||||
|
||||
_buffer.WriteByte((byte)encodedByte);
|
||||
header.Add((byte)encodedByte);
|
||||
} while (x > 0);
|
||||
|
||||
_buffer.Write(backupBuffer, 0, backupBuffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Adapter;
|
||||
|
||||
namespace MQTTnet.Core.Server
|
||||
@@ -8,11 +9,12 @@ namespace MQTTnet.Core.Server
|
||||
{
|
||||
event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
|
||||
event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
|
||||
event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
|
||||
|
||||
IList<ConnectedMqttClient> GetConnectedClients();
|
||||
void InjectClient(string identifier, IMqttCommunicationAdapter adapter);
|
||||
IReadOnlyList<ConnectedMqttClient> GetConnectedClients();
|
||||
void Publish(MqttApplicationMessage applicationMessage);
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
Task StartAsync();
|
||||
Task StopAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MQTTnet.Core.Server
|
||||
{
|
||||
public interface IMqttServerFactory
|
||||
{
|
||||
IMqttServer CreateMqttServer(MqttServerOptions options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Core.Server
|
||||
{
|
||||
public interface IMqttServerStorage
|
||||
{
|
||||
Task SaveRetainedMessagesAsync(IList<MqttApplicationMessage> messages);
|
||||
|
||||
Task<IList<MqttApplicationMessage>> LoadRetainedMessagesAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Diagnostics;
|
||||
using MQTTnet.Core.Exceptions;
|
||||
using MQTTnet.Core.Internal;
|
||||
using MQTTnet.Core.Packets;
|
||||
|
||||
namespace MQTTnet.Core.Server
|
||||
{
|
||||
public sealed class MqttClientMessageQueue
|
||||
{
|
||||
private readonly List<MqttClientPublishPacketContext> _pendingPublishPackets = new List<MqttClientPublishPacketContext>();
|
||||
private readonly AsyncGate _gate = new AsyncGate();
|
||||
|
||||
private readonly MqttServerOptions _options;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private IMqttCommunicationAdapter _adapter;
|
||||
|
||||
public MqttClientMessageQueue(MqttServerOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public void Start(IMqttCommunicationAdapter adapter)
|
||||
{
|
||||
if (_cancellationTokenSource != null)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(MqttClientMessageQueue)} already started.");
|
||||
}
|
||||
|
||||
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
Task.Run(() => SendPendingPublishPacketsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_adapter = null;
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
|
||||
public void Enqueue(MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
|
||||
|
||||
lock (_pendingPublishPackets)
|
||||
{
|
||||
_pendingPublishPackets.Add(new MqttClientPublishPacketContext(publishPacket));
|
||||
_gate.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendPendingPublishPacketsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _gate.WaitOneAsync();
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_adapter == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<MqttClientPublishPacketContext> pendingPublishPackets;
|
||||
lock (_pendingPublishPackets)
|
||||
{
|
||||
pendingPublishPackets = _pendingPublishPackets.ToList();
|
||||
}
|
||||
|
||||
foreach (var publishPacket in pendingPublishPackets)
|
||||
{
|
||||
await TrySendPendingPublishPacketAsync(publishPacket);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttClientMessageQueue), e, "Error while sending pending publish packets.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TrySendPendingPublishPacketAsync(MqttClientPublishPacketContext publishPacketContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_adapter == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
publishPacketContext.PublishPacket.Dup = publishPacketContext.SendTries > 0;
|
||||
await _adapter.SendPacketAsync(publishPacketContext.PublishPacket, _options.DefaultCommunicationTimeout);
|
||||
|
||||
publishPacketContext.IsSent = true;
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
MqttTrace.Warning(nameof(MqttClientMessageQueue), exception, "Sending publish packet failed.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttClientMessageQueue), exception, "Sending publish packet failed.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
publishPacketContext.SendTries++;
|
||||
}
|
||||
}
|
||||
|
||||
private void Cleanup()
|
||||
{
|
||||
lock (_pendingPublishPackets)
|
||||
{
|
||||
_pendingPublishPackets.RemoveAll(p => p.IsSent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Diagnostics;
|
||||
using MQTTnet.Core.Exceptions;
|
||||
using MQTTnet.Core.Packets;
|
||||
using MQTTnet.Core.Protocol;
|
||||
|
||||
namespace MQTTnet.Core.Server
|
||||
{
|
||||
public sealed class MqttClientPendingMessagesQueue
|
||||
{
|
||||
private readonly BlockingCollection<MqttPublishPacket> _pendingPublishPackets = new BlockingCollection<MqttPublishPacket>();
|
||||
private readonly MqttClientSession _session;
|
||||
private readonly MqttServerOptions _options;
|
||||
|
||||
public MqttClientPendingMessagesQueue(MqttServerOptions options, MqttClientSession session)
|
||||
{
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public void Start(IMqttCommunicationAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
|
||||
|
||||
Task.Run(() => SendPendingPublishPacketsAsync(adapter, cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
public void Enqueue(MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
|
||||
|
||||
_pendingPublishPackets.Add(publishPacket);
|
||||
}
|
||||
|
||||
private async Task SendPendingPublishPacketsAsync(IMqttCommunicationAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await SendPendingPublishPacketAsync(adapter, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttNetTrace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Unhandled exception while sending pending publish packets.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendPendingPublishPacketAsync(IMqttCommunicationAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
var packet = _pendingPublishPackets.Take(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packet).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is MqttCommunicationTimedOutException)
|
||||
{
|
||||
MqttNetTrace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to timeout.");
|
||||
}
|
||||
else if (exception is MqttCommunicationException)
|
||||
{
|
||||
MqttNetTrace.Warning(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed due to communication exception.");
|
||||
}
|
||||
if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
MqttNetTrace.Error(nameof(MqttClientPendingMessagesQueue), exception, "Sending publish packet failed.");
|
||||
}
|
||||
|
||||
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
packet.Dup = true;
|
||||
_pendingPublishPackets.Add(packet, cancellationToken);
|
||||
}
|
||||
|
||||
_session.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using MQTTnet.Core.Packets;
|
||||
|
||||
namespace MQTTnet.Core.Server
|
||||
{
|
||||
public sealed class MqttClientPublishPacketContext
|
||||
{
|
||||
public MqttClientPublishPacketContext(MqttPublishPacket publishPacket)
|
||||
{
|
||||
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
|
||||
}
|
||||
|
||||
public MqttPublishPacket PublishPacket { get; }
|
||||
|
||||
public int SendTries { get; set; }
|
||||
|
||||
public bool IsSent { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Diagnostics;
|
||||
using MQTTnet.Core.Internal;
|
||||
using MQTTnet.Core.Packets;
|
||||
|
||||
namespace MQTTnet.Core.Server
|
||||
{
|
||||
public sealed class MqttClientRetainedMessagesManager
|
||||
{
|
||||
private readonly Dictionary<string, MqttPublishPacket> _retainedMessages = new Dictionary<string, MqttPublishPacket>();
|
||||
private readonly MqttServerOptions _options;
|
||||
|
||||
public MqttClientRetainedMessagesManager(MqttServerOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public async Task LoadMessagesAsync()
|
||||
{
|
||||
if (_options.Storage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync();
|
||||
lock (_retainedMessages)
|
||||
{
|
||||
_retainedMessages.Clear();
|
||||
foreach (var retainedMessage in retainedMessages)
|
||||
{
|
||||
_retainedMessages[retainedMessage.Topic] = retainedMessage.ToPublishPacket();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttNetTrace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while loading retained messages.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleMessageAsync(string clientId, MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
|
||||
|
||||
List<MqttPublishPacket> allRetainedMessages;
|
||||
lock (_retainedMessages)
|
||||
{
|
||||
if (publishPacket.Payload?.Any() == false)
|
||||
{
|
||||
_retainedMessages.Remove(publishPacket.Topic);
|
||||
MqttNetTrace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' cleared retained message for topic '{1}'.", clientId, publishPacket.Topic);
|
||||
}
|
||||
else
|
||||
{
|
||||
_retainedMessages[publishPacket.Topic] = publishPacket;
|
||||
MqttNetTrace.Information(nameof(MqttClientRetainedMessagesManager), "Client '{0}' updated retained message for topic '{1}'.", clientId, publishPacket.Topic);
|
||||
}
|
||||
|
||||
allRetainedMessages = new List<MqttPublishPacket>(_retainedMessages.Values);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// ReSharper disable once UseNullPropagation
|
||||
if (_options.Storage != null)
|
||||
{
|
||||
await _options.Storage.SaveRetainedMessagesAsync(allRetainedMessages.Select(p => p.ToApplicationMessage()).ToList());
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttNetTrace.Error(nameof(MqttClientRetainedMessagesManager), exception, "Unhandled exception while saving retained messages.");
|
||||
}
|
||||
}
|
||||
|
||||
public List<MqttPublishPacket> GetMessages(MqttSubscribePacket subscribePacket)
|
||||
{
|
||||
var retainedMessages = new List<MqttPublishPacket>();
|
||||
lock (_retainedMessages)
|
||||
{
|
||||
foreach (var retainedMessage in _retainedMessages.Values)
|
||||
{
|
||||
foreach (var topicFilter in subscribePacket.TopicFilters)
|
||||
{
|
||||
if (retainedMessage.QualityOfServiceLevel < topicFilter.QualityOfServiceLevel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
retainedMessages.Add(retainedMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retainedMessages;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,76 +8,78 @@ using MQTTnet.Core.Exceptions;
|
||||
using MQTTnet.Core.Internal;
|
||||
using MQTTnet.Core.Packets;
|
||||
using MQTTnet.Core.Protocol;
|
||||
using MQTTnet.Core.Serializer;
|
||||
|
||||
namespace MQTTnet.Core.Server
|
||||
{
|
||||
public sealed class MqttClientSession : IDisposable
|
||||
{
|
||||
private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>();
|
||||
|
||||
|
||||
private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager();
|
||||
private readonly MqttClientMessageQueue _messageQueue;
|
||||
private readonly Action<MqttClientSession, MqttPublishPacket> _publishPacketReceivedCallback;
|
||||
private readonly MqttClientSessionsManager _mqttClientSessionsManager;
|
||||
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue;
|
||||
private readonly MqttServerOptions _options;
|
||||
|
||||
private IMqttCommunicationAdapter _adapter;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private string _identifier;
|
||||
private MqttApplicationMessage _willApplicationMessage;
|
||||
private MqttApplicationMessage _willMessage;
|
||||
|
||||
public MqttClientSession(string clientId, MqttServerOptions options, Action<MqttClientSession, MqttPublishPacket> publishPacketReceivedCallback)
|
||||
public MqttClientSession(string clientId, MqttServerOptions options, MqttClientSessionsManager mqttClientSessionsManager)
|
||||
{
|
||||
ClientId = clientId;
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_publishPacketReceivedCallback = publishPacketReceivedCallback ?? throw new ArgumentNullException(nameof(publishPacketReceivedCallback));
|
||||
|
||||
_messageQueue = new MqttClientMessageQueue(options);
|
||||
_mqttClientSessionsManager = mqttClientSessionsManager ?? throw new ArgumentNullException(nameof(mqttClientSessionsManager));
|
||||
_pendingMessagesQueue = new MqttClientPendingMessagesQueue(options, this);
|
||||
}
|
||||
|
||||
public string ClientId { get; }
|
||||
|
||||
public bool IsConnected => Adapter != null;
|
||||
public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion;
|
||||
|
||||
public IMqttCommunicationAdapter Adapter { get; private set; }
|
||||
public bool IsConnected => _adapter != null;
|
||||
|
||||
public async Task RunAsync(string identifier, MqttApplicationMessage willApplicationMessage, IMqttCommunicationAdapter adapter)
|
||||
public async Task RunAsync(MqttApplicationMessage willMessage, IMqttCommunicationAdapter adapter)
|
||||
{
|
||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
|
||||
|
||||
_willApplicationMessage = willApplicationMessage;
|
||||
_willMessage = willMessage;
|
||||
|
||||
try
|
||||
{
|
||||
_identifier = identifier;
|
||||
Adapter = adapter;
|
||||
_adapter = adapter;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
_messageQueue.Start(adapter);
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero);
|
||||
await HandleIncomingPacketAsync(packet);
|
||||
}
|
||||
_pendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token);
|
||||
await ReceivePacketsAsync(adapter, _cancellationTokenSource.Token);
|
||||
}
|
||||
catch (MqttCommunicationException)
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
MqttNetTrace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttClientSession), exception, $"Client '{_identifier}': Unhandled exception while processing client packets.");
|
||||
MqttNetTrace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
}
|
||||
finally
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_willMessage != null)
|
||||
{
|
||||
if (willApplicationMessage != null)
|
||||
{
|
||||
_publishPacketReceivedCallback(this, _willApplicationMessage.ToPublishPacket());
|
||||
}
|
||||
|
||||
_messageQueue.Stop();
|
||||
_cancellationTokenSource.Cancel();
|
||||
Adapter = null;
|
||||
|
||||
MqttTrace.Information(nameof(MqttClientSession), $"Client '{_identifier}': Disconnected.");
|
||||
_mqttClientSessionsManager.DispatchPublishPacket(this, _willMessage.ToPublishPacket());
|
||||
}
|
||||
|
||||
_cancellationTokenSource?.Cancel(false);
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
_adapter = null;
|
||||
|
||||
MqttNetTrace.Information(nameof(MqttClientSession), "Client '{0}': Disconnected.", ClientId);
|
||||
}
|
||||
|
||||
public void EnqueuePublishPacket(MqttPublishPacket publishPacket)
|
||||
@@ -89,8 +91,8 @@ namespace MQTTnet.Core.Server
|
||||
return;
|
||||
}
|
||||
|
||||
_messageQueue.Enqueue(publishPacket);
|
||||
MqttTrace.Verbose(nameof(MqttClientSession), $"Client '{_identifier}: Enqueued pending publish packet.");
|
||||
_pendingMessagesQueue.Enqueue(publishPacket);
|
||||
MqttNetTrace.Verbose(nameof(MqttClientSession), "Client '{0}': Enqueued pending publish packet.", ClientId);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -99,68 +101,100 @@ namespace MQTTnet.Core.Server
|
||||
_cancellationTokenSource?.Dispose();
|
||||
}
|
||||
|
||||
private Task HandleIncomingPacketAsync(MqttBasePacket packet)
|
||||
private async Task ReceivePacketsAsync(IMqttCommunicationAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
|
||||
await ProcessReceivedPacketAsync(adapter, packet).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
MqttNetTrace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
Stop();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttNetTrace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessReceivedPacketAsync(IMqttCommunicationAdapter adapter, MqttBasePacket packet)
|
||||
{
|
||||
if (packet is MqttSubscribePacket subscribePacket)
|
||||
{
|
||||
return Adapter.SendPacketAsync(_subscriptionsManager.Subscribe(subscribePacket), _options.DefaultCommunicationTimeout);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, _subscriptionsManager.Subscribe(subscribePacket));
|
||||
EnqueueRetainedMessages(subscribePacket);
|
||||
}
|
||||
|
||||
if (packet is MqttUnsubscribePacket unsubscribePacket)
|
||||
else if (packet is MqttUnsubscribePacket unsubscribePacket)
|
||||
{
|
||||
return Adapter.SendPacketAsync(_subscriptionsManager.Unsubscribe(unsubscribePacket), _options.DefaultCommunicationTimeout);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, _subscriptionsManager.Unsubscribe(unsubscribePacket));
|
||||
}
|
||||
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
else if (packet is MqttPublishPacket publishPacket)
|
||||
{
|
||||
return HandleIncomingPublishPacketAsync(publishPacket);
|
||||
await HandleIncomingPublishPacketAsync(adapter, publishPacket);
|
||||
}
|
||||
|
||||
if (packet is MqttPubRelPacket pubRelPacket)
|
||||
else if (packet is MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
return HandleIncomingPubRelPacketAsync(pubRelPacket);
|
||||
await HandleIncomingPubRelPacketAsync(adapter, pubRelPacket);
|
||||
}
|
||||
|
||||
if (packet is MqttPubRecPacket pubRecPacket)
|
||||
else if (packet is MqttPubRecPacket pubRecPacket)
|
||||
{
|
||||
return Adapter.SendPacketAsync(pubRecPacket.CreateResponse<MqttPubRelPacket>(), _options.DefaultCommunicationTimeout);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, pubRecPacket.CreateResponse<MqttPubRelPacket>());
|
||||
}
|
||||
|
||||
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
|
||||
else if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
|
||||
{
|
||||
// Discard message.
|
||||
return Task.FromResult((object)null);
|
||||
}
|
||||
|
||||
if (packet is MqttPingReqPacket)
|
||||
else if (packet is MqttPingReqPacket)
|
||||
{
|
||||
return Adapter.SendPacketAsync(new MqttPingRespPacket(), _options.DefaultCommunicationTimeout);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPingRespPacket());
|
||||
}
|
||||
|
||||
if (packet is MqttDisconnectPacket || packet is MqttConnectPacket)
|
||||
else if (packet is MqttDisconnectPacket || packet is MqttConnectPacket)
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
return Task.FromResult((object)null);
|
||||
Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
MqttNetTrace.Warning(nameof(MqttClientSession), "Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
|
||||
Stop();
|
||||
}
|
||||
|
||||
MqttTrace.Warning(nameof(MqttClientSession), $"Client '{_identifier}': Received not supported packet ({packet}). Closing connection.");
|
||||
_cancellationTokenSource.Cancel();
|
||||
|
||||
return Task.FromResult((object)null);
|
||||
}
|
||||
|
||||
private Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket)
|
||||
private void EnqueueRetainedMessages(MqttSubscribePacket subscribePacket)
|
||||
{
|
||||
var retainedMessages = _mqttClientSessionsManager.RetainedMessagesManager.GetMessages(subscribePacket);
|
||||
foreach (var publishPacket in retainedMessages)
|
||||
{
|
||||
EnqueuePublishPacket(publishPacket);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleIncomingPublishPacketAsync(IMqttCommunicationAdapter adapter, MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (publishPacket.Retain)
|
||||
{
|
||||
await _mqttClientSessionsManager.RetainedMessagesManager.HandleMessageAsync(ClientId, publishPacket);
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
_publishPacketReceivedCallback(this, publishPacket);
|
||||
return Task.FromResult(0);
|
||||
_mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
{
|
||||
_publishPacketReceivedCallback(this, publishPacket);
|
||||
return Adapter.SendPacketAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
|
||||
_mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
|
||||
return;
|
||||
}
|
||||
|
||||
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
|
||||
@@ -171,22 +205,23 @@ namespace MQTTnet.Core.Server
|
||||
_unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier);
|
||||
}
|
||||
|
||||
_publishPacketReceivedCallback(this, publishPacket);
|
||||
_mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket);
|
||||
|
||||
return Adapter.SendPacketAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
|
||||
return;
|
||||
}
|
||||
|
||||
throw new MqttCommunicationException("Received a not supported QoS level.");
|
||||
}
|
||||
|
||||
private Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket)
|
||||
private Task HandleIncomingPubRelPacketAsync(IMqttCommunicationAdapter adapter, MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
lock (_unacknowledgedPublishPackets)
|
||||
{
|
||||
_unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier);
|
||||
}
|
||||
|
||||
return Adapter.SendPacketAsync(new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Diagnostics;
|
||||
@@ -8,86 +9,133 @@ using MQTTnet.Core.Exceptions;
|
||||
using MQTTnet.Core.Internal;
|
||||
using MQTTnet.Core.Packets;
|
||||
using MQTTnet.Core.Protocol;
|
||||
using MQTTnet.Core.Serializer;
|
||||
|
||||
namespace MQTTnet.Core.Server
|
||||
{
|
||||
public sealed class MqttClientSessionsManager
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly Dictionary<string, MqttClientSession> _clientSessions = new Dictionary<string, MqttClientSession>();
|
||||
private readonly MqttServerOptions _options;
|
||||
|
||||
public MqttClientSessionsManager(MqttServerOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
RetainedMessagesManager = new MqttClientRetainedMessagesManager(options);
|
||||
}
|
||||
|
||||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
|
||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
|
||||
public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
|
||||
|
||||
public async Task RunClientSessionAsync(MqttClientConnectedEventArgs eventArgs)
|
||||
public MqttClientRetainedMessagesManager RetainedMessagesManager { get; }
|
||||
|
||||
public async Task RunClientSessionAsync(IMqttCommunicationAdapter clientAdapter)
|
||||
{
|
||||
var clientId = string.Empty;
|
||||
try
|
||||
{
|
||||
var connectPacket = await eventArgs.ClientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout) as MqttConnectPacket;
|
||||
if (connectPacket == null)
|
||||
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false) is MqttConnectPacket connectPacket))
|
||||
{
|
||||
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;
|
||||
clientId = connectPacket.ClientId;
|
||||
|
||||
// Switch to the required protocol version before sending any response.
|
||||
clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
|
||||
|
||||
var connectReturnCode = ValidateConnection(connectPacket);
|
||||
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
||||
{
|
||||
await eventArgs.ClientAdapter.SendPacketAsync(new MqttConnAckPacket
|
||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
|
||||
{
|
||||
ConnectReturnCode = connectReturnCode
|
||||
}, _options.DefaultCommunicationTimeout);
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var clientSession = GetOrCreateClientSession(connectPacket);
|
||||
|
||||
await eventArgs.ClientAdapter.SendPacketAsync(new MqttConnAckPacket
|
||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
|
||||
{
|
||||
ConnectReturnCode = connectReturnCode,
|
||||
IsSessionPresent = clientSession.IsExistingSession
|
||||
}, _options.DefaultCommunicationTimeout);
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await clientSession.Session.RunAsync(eventArgs.Identifier, connectPacket.WillMessage, eventArgs.ClientAdapter);
|
||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(new ConnectedMqttClient
|
||||
{
|
||||
ClientId = clientId,
|
||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
|
||||
}));
|
||||
|
||||
await clientSession.Session.RunAsync(connectPacket.WillMessage, clientAdapter).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttTrace.Error(nameof(MqttServer), exception, exception.Message);
|
||||
MqttNetTrace.Error(nameof(MqttServer), exception, exception.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await eventArgs.ClientAdapter.DisconnectAsync();
|
||||
try
|
||||
{
|
||||
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//ignored
|
||||
}
|
||||
|
||||
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(new ConnectedMqttClient
|
||||
{
|
||||
ClientId = clientId,
|
||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
lock (_clientSessions)
|
||||
{
|
||||
_clientSessions.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public IList<ConnectedMqttClient> GetConnectedClients()
|
||||
public IReadOnlyList<ConnectedMqttClient> GetConnectedClients()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
lock (_clientSessions)
|
||||
{
|
||||
return _clientSessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
|
||||
{
|
||||
ClientId = s.Value.ClientId,
|
||||
ProtocolVersion = s.Value.Adapter.PacketSerializer.ProtocolVersion
|
||||
ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public void DispatchPublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
|
||||
{
|
||||
try
|
||||
{
|
||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, publishPacket.ToApplicationMessage());
|
||||
ApplicationMessageReceived?.Invoke(this, eventArgs);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MqttNetTrace.Error(nameof(MqttClientSessionsManager), exception, "Error while processing application message");
|
||||
}
|
||||
|
||||
lock (_clientSessions)
|
||||
{
|
||||
foreach (var clientSession in _clientSessions.Values.ToList())
|
||||
{
|
||||
clientSession.EnqueuePublishPacket(publishPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
|
||||
{
|
||||
if (_options.ConnectionValidator != null)
|
||||
@@ -100,10 +148,9 @@ namespace MQTTnet.Core.Server
|
||||
|
||||
private GetOrCreateClientSessionResult GetOrCreateClientSession(MqttConnectPacket connectPacket)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
lock (_clientSessions)
|
||||
{
|
||||
MqttClientSession clientSession;
|
||||
var isSessionPresent = _clientSessions.TryGetValue(connectPacket.ClientId, out clientSession);
|
||||
var isSessionPresent = _clientSessions.TryGetValue(connectPacket.ClientId, out var clientSession);
|
||||
if (isSessionPresent)
|
||||
{
|
||||
if (connectPacket.CleanSession)
|
||||
@@ -111,11 +158,11 @@ namespace MQTTnet.Core.Server
|
||||
_clientSessions.Remove(connectPacket.ClientId);
|
||||
clientSession.Dispose();
|
||||
clientSession = null;
|
||||
MqttTrace.Verbose(nameof(MqttClientSessionsManager), $"Disposed existing session of client '{connectPacket.ClientId}'.");
|
||||
MqttNetTrace.Verbose(nameof(MqttClientSessionsManager), "Disposed existing session of client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
MqttTrace.Verbose(nameof(MqttClientSessionsManager), $"Reusing existing session of client '{connectPacket.ClientId}'.");
|
||||
MqttNetTrace.Verbose(nameof(MqttClientSessionsManager), "Reusing existing session of client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,35 +171,14 @@ namespace MQTTnet.Core.Server
|
||||
{
|
||||
isExistingSession = false;
|
||||
|
||||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, DispatchPublishPacket);
|
||||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, this);
|
||||
_clientSessions[connectPacket.ClientId] = clientSession;
|
||||
|
||||
MqttTrace.Verbose(nameof(MqttClientSessionsManager), $"Created a new session for client '{connectPacket.ClientId}'.");
|
||||
MqttNetTrace.Verbose(nameof(MqttClientSessionsManager), "Created a new session for client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
|
||||
return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
|
||||
}
|
||||
}
|
||||
|
||||
public void DispatchPublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
foreach (var clientSession in _clientSessions.Values.ToList())
|
||||
{
|
||||
clientSession.EnqueuePublishPacket(publishPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,18 +20,20 @@ namespace MQTTnet.Core.Server
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_adapters = adapters ?? throw new ArgumentNullException(nameof(adapters));
|
||||
|
||||
|
||||
_clientSessionsManager = new MqttClientSessionsManager(options);
|
||||
_clientSessionsManager.ApplicationMessageReceived += (s, e) => ApplicationMessageReceived?.Invoke(s, e);
|
||||
_clientSessionsManager.ClientConnected += OnClientConnected;
|
||||
_clientSessionsManager.ClientDisconnected += OnClientDisconnected;
|
||||
}
|
||||
|
||||
public IList<ConnectedMqttClient> GetConnectedClients()
|
||||
public IReadOnlyList<ConnectedMqttClient> GetConnectedClients()
|
||||
{
|
||||
return _clientSessionsManager.GetConnectedClients();
|
||||
}
|
||||
|
||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
|
||||
|
||||
public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
|
||||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
|
||||
|
||||
public void Publish(MqttApplicationMessage applicationMessage)
|
||||
@@ -41,31 +43,24 @@ namespace MQTTnet.Core.Server
|
||||
_clientSessionsManager.DispatchPublishPacket(null, applicationMessage.ToPublishPacket());
|
||||
}
|
||||
|
||||
public void InjectClient(string identifier, IMqttCommunicationAdapter adapter)
|
||||
{
|
||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
|
||||
|
||||
if (_cancellationTokenSource == null) throw new InvalidOperationException("The MQTT server is not started.");
|
||||
|
||||
OnClientConnected(this, new MqttClientConnectedEventArgs(identifier, adapter));
|
||||
}
|
||||
|
||||
public void Start()
|
||||
public async Task StartAsync()
|
||||
{
|
||||
if (_cancellationTokenSource != null) throw new InvalidOperationException("The MQTT server is already started.");
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
await _clientSessionsManager.RetainedMessagesManager.LoadMessagesAsync();
|
||||
|
||||
foreach (var adapter in _adapters)
|
||||
{
|
||||
adapter.ClientConnected += OnClientConnected;
|
||||
adapter.Start(_options);
|
||||
adapter.ClientAccepted += OnClientAccepted;
|
||||
await adapter.StartAsync(_options);
|
||||
}
|
||||
|
||||
MqttTrace.Information(nameof(MqttServer), "Started.");
|
||||
|
||||
MqttNetTrace.Information(nameof(MqttServer), "Started.");
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
public async Task StopAsync()
|
||||
{
|
||||
_cancellationTokenSource?.Cancel(false);
|
||||
_cancellationTokenSource?.Dispose();
|
||||
@@ -73,21 +68,30 @@ namespace MQTTnet.Core.Server
|
||||
|
||||
foreach (var adapter in _adapters)
|
||||
{
|
||||
adapter.ClientConnected -= OnClientConnected;
|
||||
adapter.Stop();
|
||||
adapter.ClientAccepted -= OnClientAccepted;
|
||||
await adapter.StopAsync();
|
||||
}
|
||||
|
||||
_clientSessionsManager.Clear();
|
||||
|
||||
MqttTrace.Information(nameof(MqttServer), "Stopped.");
|
||||
MqttNetTrace.Information(nameof(MqttServer), "Stopped.");
|
||||
}
|
||||
|
||||
private void OnClientAccepted(IMqttCommunicationAdapter adapter)
|
||||
{
|
||||
Task.Run(() =>_clientSessionsManager.RunClientSessionAsync(adapter), _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
private void OnClientConnected(object sender, MqttClientConnectedEventArgs eventArgs)
|
||||
{
|
||||
MqttTrace.Information(nameof(MqttServer), $"Client '{eventArgs.Identifier}': Connected.");
|
||||
MqttNetTrace.Information(nameof(MqttServer), "Client '{0}': Connected.", eventArgs.Client.ClientId);
|
||||
ClientConnected?.Invoke(this, eventArgs);
|
||||
}
|
||||
|
||||
Task.Run(() => _clientSessionsManager.RunClientSessionAsync(eventArgs), _cancellationTokenSource.Token);
|
||||
private void OnClientDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs)
|
||||
{
|
||||
MqttNetTrace.Information(nameof(MqttServer), "Client '{0}': Disconnected.", eventArgs.Client.ClientId);
|
||||
ClientDisconnected?.Invoke(this, eventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ namespace MQTTnet.Core.Server
|
||||
public MqttServerDefaultEndpointOptions DefaultEndpointOptions { get; } = new MqttServerDefaultEndpointOptions();
|
||||
|
||||
public MqttServerTlsEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsEndpointOptions();
|
||||
|
||||
|
||||
public int ConnectionBacklog { get; set; } = 10;
|
||||
|
||||
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15);
|
||||
|
||||
public Func<MqttConnectPacket, MqttConnectReturnCode> ConnectionValidator { get; set; }
|
||||
|
||||
public IMqttServerStorage Storage { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
+29
-10
@@ -1,9 +1,9 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.8
|
||||
VisualStudioVersion = 15.0.26730.16
|
||||
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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.NetFramework", "Frameworks\MQTTnet.NetFramework\MQTTnet.NetFramework.csproj", "{A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}"
|
||||
EndProject
|
||||
@@ -32,6 +32,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.NetCore", "Tests\MQTTnet.TestApp.NetCore\MQTTnet.TestApp.NetCore.csproj", "{3D283AAD-AAA8-4339-8394-52F80B6304DB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -48,18 +50,18 @@ Global
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x64.Build.0 = Debug|x64
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x86.Build.0 = Debug|x86
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.ActiveCfg = Release|x64
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.Build.0 = Release|x64
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.ActiveCfg = Release|x86
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.Build.0 = Release|x86
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
@@ -162,6 +164,22 @@ Global
|
||||
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|x64.Build.0 = Release|Any CPU
|
||||
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|x86.Build.0 = Release|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x64.Build.0 = Release|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -173,6 +191,7 @@ Global
|
||||
{D9D74F33-6943-49B2-B765-7BD589082098} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
|
||||
{FF1F72D6-9524-4422-9497-3CC0002216ED} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
|
||||
{3587E506-55A2-4EB3-99C7-DC01E42D25D2} = {32A630A7-2598-41D7-B625-204CD906F5FB}
|
||||
{3D283AAD-AAA8-4339-8394-52F80B6304DB} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_CALL_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_METHOD_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -3,30 +3,36 @@
|
||||
</p>
|
||||
|
||||
[](https://www.nuget.org/packages/MQTTnet)
|
||||
[](https://bettercodehub.com/)
|
||||
|
||||
# MQTTnet
|
||||
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
|
||||
|
||||
## General
|
||||
### General
|
||||
* Async support
|
||||
* 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))
|
||||
* Interfaces included for mocking and testing
|
||||
* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS)
|
||||
* Lightweight (only the low level implementation of MQTT, no overhead)
|
||||
* Performance optimized (processing ~27.000 messages / second)*
|
||||
* Interfaces included for mocking and testing
|
||||
* Access to internal trace messages
|
||||
* Unit tested (50+ tests)
|
||||
* Unit tested (62+ tests)
|
||||
|
||||
## Client
|
||||
\* Tested on local machine with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetFramework_.
|
||||
|
||||
### Client
|
||||
* Rx support (via another project)
|
||||
* Communication via TCP (+TLS) or WS (WebSocket)
|
||||
|
||||
## Server (broker)
|
||||
### 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
|
||||
* Retained messages are supported including persisting via interface methods (own implementation required)
|
||||
|
||||
# Supported frameworks
|
||||
* .NET Standard 1.3+
|
||||
@@ -34,6 +40,7 @@ MQTTnet is a .NET library for MQTT based communication. It provides a MQTT clien
|
||||
* .NET Core App 1.1+
|
||||
* .NET Framework 4.5.2+ (x86, x64, AnyCPU)
|
||||
* Universal Windows (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU)
|
||||
* Mono 5.2+
|
||||
|
||||
# Supported MQTT versions
|
||||
* 3.1.1
|
||||
@@ -42,6 +49,9 @@ MQTTnet is a .NET library for MQTT based communication. It provides a MQTT clien
|
||||
# Nuget
|
||||
This library is available as a nuget package: https://www.nuget.org/packages/MQTTnet/
|
||||
|
||||
# Examples
|
||||
Please find examples and the documentation at the Wiki of this repository (https://github.com/chkr1011/MQTTnet/wiki).
|
||||
|
||||
# Contributions
|
||||
If you want to contribute to this project just create a pull request.
|
||||
|
||||
@@ -51,105 +61,4 @@ This library is used in the following projects:
|
||||
* MQTT Client Rx (Wrapper for Reactive Extensions, https://github.com/1iveowl/MQTTClient.rx)
|
||||
* HA4IoT (Open Source Home Automation system for .NET, https://github.com/chkr1011/HA4IoT)
|
||||
|
||||
If you use this library and want to see your project here please let me know.
|
||||
|
||||
# Examples
|
||||
## MqttClient
|
||||
|
||||
```csharp
|
||||
var options = new MqttClientOptions
|
||||
{
|
||||
Server = "localhost"
|
||||
};
|
||||
|
||||
var client = new MqttClientFactory().CreateMqttClient(options);
|
||||
client.ApplicationMessageReceived += (s, e) =>
|
||||
{
|
||||
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
|
||||
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
|
||||
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}");
|
||||
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
|
||||
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
|
||||
Console.WriteLine();
|
||||
};
|
||||
|
||||
client.Connected += async (s, e) =>
|
||||
{
|
||||
Console.WriteLine("### CONNECTED WITH SERVER ###");
|
||||
|
||||
await client.SubscribeAsync(new List<TopicFilter>
|
||||
{
|
||||
new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)
|
||||
});
|
||||
|
||||
Console.WriteLine("### SUBSCRIBED ###");
|
||||
};
|
||||
|
||||
client.Disconnected += async (s, e) =>
|
||||
{
|
||||
Console.WriteLine("### DISCONNECTED FROM SERVER ###");
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("### RECONNECTING FAILED ###");
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("### CONNECTING FAILED ###");
|
||||
}
|
||||
|
||||
Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###");
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.ReadLine();
|
||||
|
||||
var applicationMessage = new MqttApplicationMessage(
|
||||
"A/B/C",
|
||||
Encoding.UTF8.GetBytes("Hello World"),
|
||||
MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
false
|
||||
);
|
||||
|
||||
await client.PublishAsync(applicationMessage);
|
||||
}
|
||||
```
|
||||
|
||||
## MqttServer
|
||||
|
||||
```csharp
|
||||
var options = new MqttServerOptions
|
||||
{
|
||||
ConnectionValidator = p =>
|
||||
{
|
||||
if (p.ClientId == "SpecialClient")
|
||||
{
|
||||
if (p.Username != "USER" || p.Password != "PASS")
|
||||
{
|
||||
return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
|
||||
}
|
||||
}
|
||||
|
||||
return MqttConnectReturnCode.ConnectionAccepted;
|
||||
}
|
||||
};
|
||||
|
||||
var mqttServer = new MqttServerFactory().CreateMqttServer(options);
|
||||
mqttServer.Start();
|
||||
|
||||
Console.WriteLine("Press any key to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
mqttServer.Stop();
|
||||
```
|
||||
If you use this library and want to see your project here please let me know.
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Core.Exceptions;
|
||||
using MQTTnet.Core.Internal;
|
||||
|
||||
namespace MQTTnet.Core.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class ExtensionTests
|
||||
{
|
||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||
[TestMethod]
|
||||
public async Task TimeoutAfter()
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(500)).TimeoutAfter(TimeSpan.FromMilliseconds(100));
|
||||
}
|
||||
|
||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||
[TestMethod]
|
||||
public async Task TimeoutAfterWithResult()
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(500)).ContinueWith(t => 5).TimeoutAfter(TimeSpan.FromMilliseconds(100));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TimeoutAfterCompleteInTime()
|
||||
{
|
||||
var result = await Task.Delay(TimeSpan.FromMilliseconds(100)).ContinueWith(t => 5).TimeoutAfter(TimeSpan.FromMilliseconds(500));
|
||||
Assert.AreEqual(5, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TimeoutAfterWithInnerException()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
var iis = new int[0];
|
||||
iis[1] = 0;
|
||||
}).TimeoutAfter(TimeSpan.FromSeconds(1));
|
||||
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (MqttCommunicationException e)
|
||||
{
|
||||
Assert.IsTrue(e.InnerException is IndexOutOfRangeException);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TimeoutAfterWithInnerExceptionWithResult()
|
||||
{
|
||||
try
|
||||
{
|
||||
var r = await Task.Run(() =>
|
||||
{
|
||||
var iis = new int[0];
|
||||
return iis[1];
|
||||
}).TimeoutAfter(TimeSpan.FromSeconds(1));
|
||||
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (MqttCommunicationException e)
|
||||
{
|
||||
Assert.IsTrue(e.InnerException is IndexOutOfRangeException);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TimeoutAfterMemoryUsage()
|
||||
{
|
||||
var tasks = Enumerable.Range(0, 100000)
|
||||
.Select(i => Task.Delay(TimeSpan.FromMilliseconds(1)).TimeoutAfter(TimeSpan.FromMinutes(1)));
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
AssertIsLess(3_000_000, GC.GetTotalMemory(true));
|
||||
}
|
||||
|
||||
private static void AssertIsLess(long bound, long actual)
|
||||
{
|
||||
if (bound < actual)
|
||||
{
|
||||
Assert.Fail($"value must be less than {bound:N0} but is {actual:N0}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>MQTTnet.Core.Tests</RootNamespace>
|
||||
<AssemblyName>MQTTnet.Core.Tests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Any CPU|AnyCPU'">
|
||||
<OutputPath>bin\Any CPU\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Any CPU|x64'">
|
||||
<OutputPath>bin\x64\Any CPU\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Any CPU|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Any CPU\</OutputPath>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.1.18" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.1.18" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="ByteReaderTests.cs" />
|
||||
<Compile Include="ByteWriterTests.cs" />
|
||||
<Compile Include="MqttPacketSerializerTests.cs" />
|
||||
<Compile Include="MqttServerTests.cs" />
|
||||
<Compile Include="MqttSubscriptionsManagerTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TestMqttCommunicationAdapter.cs" />
|
||||
<Compile Include="TestMqttServerAdapter.cs" />
|
||||
<Compile Include="TopicFilterComparerTests.cs" />
|
||||
<ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj" />
|
||||
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj">
|
||||
<Project>{2ecb99e4-72d0-4c23-99ba-93d511d3967d}</Project>
|
||||
<Name>MQTTnet.Core</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,20 @@
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Client;
|
||||
|
||||
namespace MQTTnet.Core.Tests
|
||||
{
|
||||
public class MqttCommunicationAdapterFactory : IMqttCommunicationAdapterFactory
|
||||
{
|
||||
private readonly IMqttCommunicationAdapter _adapter;
|
||||
|
||||
public MqttCommunicationAdapterFactory(IMqttCommunicationAdapter adapter)
|
||||
{
|
||||
_adapter = adapter;
|
||||
}
|
||||
|
||||
public IMqttCommunicationAdapter CreateMqttCommunicationAdapter(MqttClientOptions options)
|
||||
{
|
||||
return _adapter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Channel;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Packets;
|
||||
using MQTTnet.Core.Protocol;
|
||||
using MQTTnet.Core.Serializer;
|
||||
@@ -391,6 +392,12 @@ namespace MQTTnet.Core.Tests
|
||||
{
|
||||
private readonly MemoryStream _stream = new MemoryStream();
|
||||
|
||||
public Stream ReceiveStream => _stream;
|
||||
|
||||
public Stream RawReceiveStream => _stream;
|
||||
|
||||
public Stream SendStream => _stream;
|
||||
|
||||
public bool IsConnected { get; } = true;
|
||||
|
||||
public TestChannel()
|
||||
@@ -403,24 +410,14 @@ namespace MQTTnet.Core.Tests
|
||||
_stream.Position = 0;
|
||||
}
|
||||
|
||||
public async Task ConnectAsync(MqttClientOptions options)
|
||||
public Task ConnectAsync()
|
||||
{
|
||||
await Task.FromResult(0);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
await Task.FromResult(0);
|
||||
}
|
||||
|
||||
public async Task WriteAsync(byte[] buffer)
|
||||
{
|
||||
await _stream.WriteAsync(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
public async Task ReadAsync(byte[] buffer)
|
||||
{
|
||||
await _stream.ReadAsync(buffer, 0, buffer.Length);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public byte[] ToArray()
|
||||
@@ -429,32 +426,32 @@ namespace MQTTnet.Core.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
|
||||
private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
|
||||
{
|
||||
var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion };
|
||||
var channel = new TestChannel();
|
||||
serializer.SerializeAsync(packet, channel).Wait();
|
||||
var buffer = channel.ToArray();
|
||||
var buffer = serializer.Serialize(packet);
|
||||
|
||||
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(buffer));
|
||||
}
|
||||
|
||||
private void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value)
|
||||
private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value)
|
||||
{
|
||||
var serializer = new MqttPacketSerializer();
|
||||
|
||||
var channel1 = new TestChannel();
|
||||
serializer.SerializeAsync(packet, channel1).Wait();
|
||||
var buffer1 = channel1.ToArray();
|
||||
var buffer1 = serializer.Serialize(packet);
|
||||
|
||||
var channel2 = new TestChannel(buffer1);
|
||||
var deserializedPacket = serializer.DeserializeAsync(channel2).Result;
|
||||
var buffer2 = channel2.ToArray();
|
||||
using (var headerStream = new MemoryStream(buffer1))
|
||||
{
|
||||
var header = MqttPacketReader.ReadHeaderFromSource(headerStream, CancellationToken.None);
|
||||
|
||||
var channel3 = new TestChannel(buffer2);
|
||||
serializer.SerializeAsync(deserializedPacket, channel3).Wait();
|
||||
using (var bodyStream = new MemoryStream(buffer1, (int)headerStream.Position, header.BodyLength))
|
||||
{
|
||||
var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header, bodyStream));
|
||||
var buffer2 = serializer.Serialize(deserializedPacket);
|
||||
|
||||
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(channel3.ToArray()));
|
||||
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(buffer2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,47 +13,48 @@ namespace MQTTnet.Core.Tests
|
||||
public class MqttServerTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task MqttServer_PublishSimple_AtMostOnce()
|
||||
public void MqttServer_PublishSimple_AtMostOnce()
|
||||
{
|
||||
await TestPublishAsync(
|
||||
TestPublishAsync(
|
||||
"A/B/C",
|
||||
MqttQualityOfServiceLevel.AtMostOnce,
|
||||
"A/B/C",
|
||||
MqttQualityOfServiceLevel.AtMostOnce,
|
||||
1);
|
||||
1).Wait();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MqttServer_PublishSimple_AtLeastOnce()
|
||||
public void MqttServer_PublishSimple_AtLeastOnce()
|
||||
{
|
||||
await TestPublishAsync(
|
||||
TestPublishAsync(
|
||||
"A/B/C",
|
||||
MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
"A/B/C",
|
||||
MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
1);
|
||||
1).Wait();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MqttServer_PublishSimple_ExactlyOnce()
|
||||
public void MqttServer_PublishSimple_ExactlyOnce()
|
||||
{
|
||||
await TestPublishAsync(
|
||||
TestPublishAsync(
|
||||
"A/B/C",
|
||||
MqttQualityOfServiceLevel.ExactlyOnce,
|
||||
"A/B/C",
|
||||
MqttQualityOfServiceLevel.ExactlyOnce,
|
||||
1);
|
||||
1).Wait();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MqttServer_WillMessage()
|
||||
{
|
||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() });
|
||||
s.Start();
|
||||
var serverAdapter = new TestMqttServerAdapter();
|
||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { serverAdapter });
|
||||
await s.StartAsync();
|
||||
|
||||
var willMessage = new MqttApplicationMessage("My/last/will", new byte[0], MqttQualityOfServiceLevel.AtMostOnce, false);
|
||||
var c1 = ConnectTestClient("c1", null, s);
|
||||
var c2 = ConnectTestClient("c2", willMessage, s);
|
||||
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
|
||||
var c2 = await serverAdapter.ConnectTestClient(s, "c2", willMessage);
|
||||
|
||||
var receivedMessagesCount = 0;
|
||||
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
|
||||
@@ -63,7 +64,7 @@ namespace MQTTnet.Core.Tests
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
s.Stop();
|
||||
await s.StopAsync();
|
||||
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
}
|
||||
@@ -71,15 +72,16 @@ namespace MQTTnet.Core.Tests
|
||||
[TestMethod]
|
||||
public async Task MqttServer_Unsubscribe()
|
||||
{
|
||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() });
|
||||
s.Start();
|
||||
var serverAdapter = new TestMqttServerAdapter();
|
||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { serverAdapter });
|
||||
await s.StartAsync();
|
||||
|
||||
var c1 = ConnectTestClient("c1", null, s);
|
||||
var c2 = ConnectTestClient("c2", null, s);
|
||||
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
|
||||
var c2 = await serverAdapter.ConnectTestClient(s, "c2");
|
||||
|
||||
var receivedMessagesCount = 0;
|
||||
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
|
||||
|
||||
|
||||
var message = new MqttApplicationMessage("a", new byte[0], MqttQualityOfServiceLevel.AtLeastOnce, false);
|
||||
|
||||
await c2.PublishAsync(message);
|
||||
@@ -91,13 +93,13 @@ namespace MQTTnet.Core.Tests
|
||||
await Task.Delay(500);
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
|
||||
await c1.Unsubscribe("a");
|
||||
await c1.UnsubscribeAsync("a");
|
||||
await c2.PublishAsync(message);
|
||||
|
||||
await Task.Delay(500);
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
|
||||
s.Stop();
|
||||
await s.StopAsync();
|
||||
await Task.Delay(500);
|
||||
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
@@ -106,10 +108,11 @@ namespace MQTTnet.Core.Tests
|
||||
[TestMethod]
|
||||
public async Task MqttServer_Publish()
|
||||
{
|
||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() });
|
||||
s.Start();
|
||||
var serverAdapter = new TestMqttServerAdapter();
|
||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { serverAdapter });
|
||||
await s.StartAsync();
|
||||
|
||||
var c1 = ConnectTestClient("c1", null, s);
|
||||
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
|
||||
|
||||
var receivedMessagesCount = 0;
|
||||
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
|
||||
@@ -120,36 +123,140 @@ namespace MQTTnet.Core.Tests
|
||||
s.Publish(message);
|
||||
await Task.Delay(500);
|
||||
|
||||
s.Stop();
|
||||
await s.StopAsync();
|
||||
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
}
|
||||
|
||||
private static MqttClient ConnectTestClient(string clientId, MqttApplicationMessage willMessage, MqttServer server)
|
||||
[TestMethod]
|
||||
public async Task MqttServer_NoRetainedMessage()
|
||||
{
|
||||
var adapterA = new TestMqttCommunicationAdapter();
|
||||
var adapterB = new TestMqttCommunicationAdapter();
|
||||
adapterA.Partner = adapterB;
|
||||
adapterB.Partner = adapterA;
|
||||
var serverAdapter = new TestMqttServerAdapter();
|
||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { serverAdapter });
|
||||
await s.StartAsync();
|
||||
|
||||
var client = new MqttClient(new MqttClientOptions(), adapterA);
|
||||
server.InjectClient(clientId, adapterB);
|
||||
client.ConnectAsync(willMessage).Wait();
|
||||
return client;
|
||||
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
|
||||
await c1.PublishAsync(new MqttApplicationMessage("retained", new byte[3], MqttQualityOfServiceLevel.AtLeastOnce, false));
|
||||
await c1.DisconnectAsync();
|
||||
|
||||
var c2 = await serverAdapter.ConnectTestClient(s, "c2");
|
||||
var receivedMessagesCount = 0;
|
||||
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
|
||||
await c2.SubscribeAsync(new TopicFilter("retained", MqttQualityOfServiceLevel.AtLeastOnce));
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
await s.StopAsync();
|
||||
|
||||
Assert.AreEqual(0, receivedMessagesCount);
|
||||
}
|
||||
|
||||
private async Task TestPublishAsync(
|
||||
[TestMethod]
|
||||
public async Task MqttServer_RetainedMessage()
|
||||
{
|
||||
var serverAdapter = new TestMqttServerAdapter();
|
||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { serverAdapter });
|
||||
await s.StartAsync();
|
||||
|
||||
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
|
||||
await c1.PublishAsync(new MqttApplicationMessage("retained", new byte[3], MqttQualityOfServiceLevel.AtLeastOnce, true));
|
||||
await c1.DisconnectAsync();
|
||||
|
||||
var c2 = await serverAdapter.ConnectTestClient(s, "c2");
|
||||
var receivedMessagesCount = 0;
|
||||
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
|
||||
await c2.SubscribeAsync(new TopicFilter("retained", MqttQualityOfServiceLevel.AtLeastOnce));
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
await s.StopAsync();
|
||||
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MqttServer_ClearRetainedMessage()
|
||||
{
|
||||
var serverAdapter = new TestMqttServerAdapter();
|
||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { serverAdapter });
|
||||
await s.StartAsync();
|
||||
|
||||
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
|
||||
await c1.PublishAsync(new MqttApplicationMessage("retained", new byte[3], MqttQualityOfServiceLevel.AtLeastOnce, true));
|
||||
await c1.PublishAsync(new MqttApplicationMessage("retained", new byte[0], MqttQualityOfServiceLevel.AtLeastOnce, true));
|
||||
await c1.DisconnectAsync();
|
||||
|
||||
var c2 = await serverAdapter.ConnectTestClient(s, "c2");
|
||||
var receivedMessagesCount = 0;
|
||||
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
|
||||
await c2.SubscribeAsync(new TopicFilter("retained", MqttQualityOfServiceLevel.AtLeastOnce));
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
await s.StopAsync();
|
||||
|
||||
Assert.AreEqual(0, receivedMessagesCount);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task MqttServer_PersistRetainedMessage()
|
||||
{
|
||||
var storage = new TestStorage();
|
||||
|
||||
var serverAdapter = new TestMqttServerAdapter();
|
||||
var s = new MqttServer(new MqttServerOptions { Storage = storage }, new List<IMqttServerAdapter> { serverAdapter });
|
||||
await s.StartAsync();
|
||||
|
||||
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
|
||||
await c1.PublishAsync(new MqttApplicationMessage("retained", new byte[3], MqttQualityOfServiceLevel.AtLeastOnce, true));
|
||||
await c1.DisconnectAsync();
|
||||
|
||||
await s.StopAsync();
|
||||
|
||||
s = new MqttServer(new MqttServerOptions { Storage = storage }, new List<IMqttServerAdapter> { serverAdapter });
|
||||
await s.StartAsync();
|
||||
|
||||
var c2 = await serverAdapter.ConnectTestClient(s, "c2");
|
||||
var receivedMessagesCount = 0;
|
||||
c2.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
|
||||
await c2.SubscribeAsync(new TopicFilter("retained", MqttQualityOfServiceLevel.AtLeastOnce));
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
await s.StopAsync();
|
||||
|
||||
Assert.AreEqual(1, receivedMessagesCount);
|
||||
}
|
||||
|
||||
private class TestStorage : IMqttServerStorage
|
||||
{
|
||||
private IList<MqttApplicationMessage> _messages = new List<MqttApplicationMessage>();
|
||||
|
||||
public Task SaveRetainedMessagesAsync(IList<MqttApplicationMessage> messages)
|
||||
{
|
||||
_messages = messages;
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<IList<MqttApplicationMessage>> LoadRetainedMessagesAsync()
|
||||
{
|
||||
return Task.FromResult(_messages);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task TestPublishAsync(
|
||||
string topic,
|
||||
MqttQualityOfServiceLevel qualityOfServiceLevel,
|
||||
string topicFilter,
|
||||
MqttQualityOfServiceLevel filterQualityOfServiceLevel,
|
||||
int expectedReceivedMessagesCount)
|
||||
{
|
||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() });
|
||||
s.Start();
|
||||
var serverAdapter = new TestMqttServerAdapter();
|
||||
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { serverAdapter });
|
||||
await s.StartAsync();
|
||||
|
||||
var c1 = ConnectTestClient("c1", null, s);
|
||||
var c2 = ConnectTestClient("c2", null, s);
|
||||
var c1 = await serverAdapter.ConnectTestClient(s, "c1");
|
||||
var c2 = await serverAdapter.ConnectTestClient(s, "c2");
|
||||
|
||||
var receivedMessagesCount = 0;
|
||||
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
|
||||
@@ -158,11 +265,11 @@ namespace MQTTnet.Core.Tests
|
||||
await c2.PublishAsync(new MqttApplicationMessage(topic, new byte[0], qualityOfServiceLevel, false));
|
||||
|
||||
await Task.Delay(500);
|
||||
await c1.Unsubscribe(topicFilter);
|
||||
await c1.UnsubscribeAsync(topicFilter);
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
s.Stop();
|
||||
await s.StopAsync();
|
||||
|
||||
Assert.AreEqual(expectedReceivedMessagesCount, receivedMessagesCount);
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("MQTTnet.Core.Tests")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Christian Kratky")]
|
||||
[assembly: AssemblyProduct("MQTTnet")]
|
||||
[assembly: AssemblyCopyright("Copyright © Christian Kratky 2016-2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: Guid("a7ff0c91-25de-4ba6-b39e-f54e8dadf1cc")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Packets;
|
||||
using MQTTnet.Core.Serializer;
|
||||
|
||||
@@ -16,29 +17,33 @@ namespace MQTTnet.Core.Tests
|
||||
|
||||
public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer();
|
||||
|
||||
public async Task ConnectAsync(MqttClientOptions options, TimeSpan timeout)
|
||||
public Task ConnectAsync(TimeSpan timeout)
|
||||
{
|
||||
await Task.FromResult(0);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
public Task DisconnectAsync(TimeSpan timeout)
|
||||
{
|
||||
await Task.FromResult(0);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout)
|
||||
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
|
||||
{
|
||||
ThrowIfPartnerIsNull();
|
||||
|
||||
Partner.SendPacketInternal(packet);
|
||||
await Task.FromResult(0);
|
||||
foreach (var packet in packets)
|
||||
{
|
||||
Partner.SendPacketInternal(packet);
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout)
|
||||
public Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
ThrowIfPartnerIsNull();
|
||||
|
||||
return await Task.Run(() => _incomingPackets.Take());
|
||||
return Task.Run(() => _incomingPackets.Take(), cancellationToken);
|
||||
}
|
||||
|
||||
private void SendPacketInternal(MqttBasePacket packet)
|
||||
|
||||
@@ -1,24 +1,63 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Core.Adapter;
|
||||
using MQTTnet.Core.Server;
|
||||
using MQTTnet.Core.Client;
|
||||
|
||||
namespace MQTTnet.Core.Tests
|
||||
{
|
||||
public class TestMqttServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
|
||||
public event Action<IMqttCommunicationAdapter> ClientAccepted;
|
||||
|
||||
public void FireClientConnectedEvent(MqttClientConnectedEventArgs eventArgs)
|
||||
public async Task<MqttClient> ConnectTestClient(IMqttServer server, string clientId, MqttApplicationMessage willMessage = null)
|
||||
{
|
||||
ClientConnected?.Invoke(this, eventArgs);
|
||||
var adapterA = new TestMqttCommunicationAdapter();
|
||||
var adapterB = new TestMqttCommunicationAdapter();
|
||||
adapterA.Partner = adapterB;
|
||||
adapterB.Partner = adapterA;
|
||||
|
||||
var client = new MqttClient(new MqttCommunicationAdapterFactory(adapterA));
|
||||
var connected = WaitForClientToConnect(server, clientId);
|
||||
|
||||
FireClientAcceptedEvent(adapterB);
|
||||
await client.ConnectAsync(new MqttClientTcpOptions { ClientId = clientId, WillMessage = willMessage });
|
||||
await connected;
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private static Task WaitForClientToConnect(IMqttServer s, string clientId)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
|
||||
void Handler(object sender, MqttClientConnectedEventArgs args)
|
||||
{
|
||||
if (args.Client.ClientId == clientId)
|
||||
{
|
||||
s.ClientConnected -= Handler;
|
||||
tcs.SetResult(null);
|
||||
}
|
||||
}
|
||||
|
||||
s.ClientConnected += Handler;
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public void Start(MqttServerOptions options)
|
||||
private void FireClientAcceptedEvent(IMqttCommunicationAdapter adapter)
|
||||
{
|
||||
ClientAccepted?.Invoke(adapter);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
public Task StartAsync(MqttServerOptions options)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj" />
|
||||
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,170 @@
|
||||
using MQTTnet.Core;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Diagnostics;
|
||||
using MQTTnet.Core.Packets;
|
||||
using MQTTnet.Core.Protocol;
|
||||
using MQTTnet.Core.Server;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.TestApp.NetCore
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("MQTTnet - TestApp.NetFramework");
|
||||
Console.WriteLine("1 = Start client");
|
||||
Console.WriteLine("2 = Start server");
|
||||
var pressedKey = Console.ReadKey(true);
|
||||
if (pressedKey.Key == ConsoleKey.D1)
|
||||
{
|
||||
Task.Run(() => RunClientAsync(args));
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
}
|
||||
else if (pressedKey.Key == ConsoleKey.D2)
|
||||
{
|
||||
Task.Run(() => RunServerAsync(args));
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task RunClientAsync(string[] arguments)
|
||||
{
|
||||
|
||||
MqttNetTrace.TraceMessagePublished += (s, e) =>
|
||||
{
|
||||
Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}");
|
||||
if (e.Exception != null)
|
||||
{
|
||||
Console.WriteLine(e.Exception);
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var options = new MqttClientWebSocketOptions
|
||||
{
|
||||
Uri = "localhost",
|
||||
ClientId = "XYZ",
|
||||
CleanSession = true
|
||||
};
|
||||
|
||||
var client = new MqttClientFactory().CreateMqttClient();
|
||||
client.ApplicationMessageReceived += (s, e) =>
|
||||
{
|
||||
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
|
||||
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
|
||||
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}");
|
||||
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
|
||||
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
|
||||
Console.WriteLine();
|
||||
};
|
||||
|
||||
client.Connected += async (s, e) =>
|
||||
{
|
||||
Console.WriteLine("### CONNECTED WITH SERVER ###");
|
||||
|
||||
await client.SubscribeAsync(new List<TopicFilter>
|
||||
{
|
||||
new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)
|
||||
});
|
||||
|
||||
Console.WriteLine("### SUBSCRIBED ###");
|
||||
};
|
||||
|
||||
client.Disconnected += async (s, e) =>
|
||||
{
|
||||
Console.WriteLine("### DISCONNECTED FROM SERVER ###");
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync(options);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("### RECONNECTING FAILED ###");
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync(options);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine("### CONNECTING FAILED ###" + Environment.NewLine + exception);
|
||||
}
|
||||
|
||||
Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###");
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.ReadLine();
|
||||
|
||||
var applicationMessage = new MqttApplicationMessage(
|
||||
"A/B/C",
|
||||
Encoding.UTF8.GetBytes("Hello World"),
|
||||
MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
false
|
||||
);
|
||||
|
||||
await client.PublishAsync(applicationMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunServerAsync(string[] arguments)
|
||||
{
|
||||
MqttNetTrace.TraceMessagePublished += (s, e) =>
|
||||
{
|
||||
Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}");
|
||||
if (e.Exception != null)
|
||||
{
|
||||
Console.WriteLine(e.Exception);
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var options = new MqttServerOptions
|
||||
{
|
||||
ConnectionValidator = p =>
|
||||
{
|
||||
if (p.ClientId == "SpecialClient")
|
||||
{
|
||||
if (p.Username != "USER" || p.Password != "PASS")
|
||||
{
|
||||
return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
|
||||
}
|
||||
}
|
||||
|
||||
return MqttConnectReturnCode.ConnectionAccepted;
|
||||
}
|
||||
};
|
||||
|
||||
var mqttServer = new MqttServerFactory().CreateMqttServer(options);
|
||||
mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press any key to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
mqttServer.StopAsync();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,7 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="PerformanceTest.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
@@ -90,7 +91,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Frameworks\MQTTnet.NetFramework\MQTTnet.NetFramework.csproj">
|
||||
<Project>{A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}</Project>
|
||||
<Project>{a480ef90-0eaa-4d9a-b271-47a9c47f6f7d}</Project>
|
||||
<Name>MQTTnet.NetFramework</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj">
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
using MQTTnet.Core;
|
||||
using MQTTnet.Core.Client;
|
||||
using MQTTnet.Core.Packets;
|
||||
using MQTTnet.Core.Protocol;
|
||||
using MQTTnet.Core.Server;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.TestApp.NetFramework
|
||||
{
|
||||
public static class PerformanceTest
|
||||
{
|
||||
public static async Task RunAsync()
|
||||
{
|
||||
var server = Task.Factory.StartNew(RunServerAsync, TaskCreationOptions.LongRunning);
|
||||
var client = Task.Factory.StartNew(() => RunClientAsync(2000, TimeSpan.FromMilliseconds(10)), TaskCreationOptions.LongRunning);
|
||||
|
||||
await Task.WhenAll(server, client).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static Task RunClientsAsync(int msgChunkSize, TimeSpan interval)
|
||||
{
|
||||
return Task.WhenAll(Enumerable.Range(0, 3).Select(i => Task.Run(() => RunClientAsync(msgChunkSize, interval))));
|
||||
}
|
||||
|
||||
private static async Task RunClientAsync(int msgChunkSize, TimeSpan interval)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new MqttClientTcpOptions
|
||||
{
|
||||
Server = "localhost",
|
||||
ClientId = "XYZ",
|
||||
CleanSession = true,
|
||||
DefaultCommunicationTimeout = TimeSpan.FromMinutes(10)
|
||||
};
|
||||
|
||||
var client = new MqttClientFactory().CreateMqttClient();
|
||||
client.ApplicationMessageReceived += (s, e) =>
|
||||
{
|
||||
};
|
||||
|
||||
client.Connected += async (s, e) =>
|
||||
{
|
||||
Console.WriteLine("### CONNECTED WITH SERVER ###");
|
||||
|
||||
await client.SubscribeAsync(new List<TopicFilter>
|
||||
{
|
||||
new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)
|
||||
});
|
||||
|
||||
Console.WriteLine("### SUBSCRIBED ###");
|
||||
};
|
||||
|
||||
client.Disconnected += async (s, e) =>
|
||||
{
|
||||
Console.WriteLine("### DISCONNECTED FROM SERVER ###");
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync(options);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("### RECONNECTING FAILED ###");
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync(options);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine("### CONNECTING FAILED ###" + Environment.NewLine + exception);
|
||||
}
|
||||
|
||||
Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###");
|
||||
|
||||
var testMessageCount = 10000;
|
||||
var message = CreateMessage();
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
for (var i = 0; i < testMessageCount; i++)
|
||||
{
|
||||
await client.PublishAsync(message);
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
Console.WriteLine($"Sent 10.000 messages within {stopwatch.ElapsedMilliseconds} ms ({stopwatch.ElapsedMilliseconds / (float)testMessageCount} ms / message).");
|
||||
|
||||
stopwatch.Restart();
|
||||
var sentMessagesCount = 0;
|
||||
while (stopwatch.ElapsedMilliseconds < 1000)
|
||||
{
|
||||
await client.PublishAsync(message);
|
||||
sentMessagesCount++;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Sending {sentMessagesCount} messages per second.");
|
||||
|
||||
var last = DateTime.Now;
|
||||
var msgCount = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var msgs = Enumerable.Range(0, msgChunkSize)
|
||||
.Select(i => CreateMessage())
|
||||
.ToList();
|
||||
|
||||
if (true)
|
||||
{
|
||||
//send concurrent (test for raceconditions)
|
||||
var sendTasks = msgs
|
||||
.Select(msg => PublishSingleMessage(client, msg, ref msgCount))
|
||||
.ToList();
|
||||
|
||||
await Task.WhenAll(sendTasks);
|
||||
}
|
||||
else
|
||||
{
|
||||
await client.PublishAsync(msgs);
|
||||
msgCount += msgs.Count;
|
||||
//send multiple
|
||||
}
|
||||
|
||||
|
||||
|
||||
var now = DateTime.Now;
|
||||
if (last < now - TimeSpan.FromSeconds(1))
|
||||
{
|
||||
Console.WriteLine($"sending {msgCount} intended {msgChunkSize / interval.TotalSeconds}");
|
||||
msgCount = 0;
|
||||
last = now;
|
||||
}
|
||||
|
||||
await Task.Delay(interval).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static MqttApplicationMessage CreateMessage()
|
||||
{
|
||||
return new MqttApplicationMessage(
|
||||
"A/B/C",
|
||||
Encoding.UTF8.GetBytes("Hello World"),
|
||||
MqttQualityOfServiceLevel.AtMostOnce,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private static Task PublishSingleMessage(IMqttClient client, MqttApplicationMessage applicationMessage, ref int count)
|
||||
{
|
||||
Interlocked.Increment(ref count);
|
||||
return Task.Run(() =>
|
||||
{
|
||||
return client.PublishAsync(applicationMessage);
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task RunServerAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new MqttServerOptions
|
||||
{
|
||||
ConnectionValidator = p =>
|
||||
{
|
||||
if (p.ClientId == "SpecialClient")
|
||||
{
|
||||
if (p.Username != "USER" || p.Password != "PASS")
|
||||
{
|
||||
return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
|
||||
}
|
||||
}
|
||||
|
||||
return MqttConnectReturnCode.ConnectionAccepted;
|
||||
},
|
||||
DefaultCommunicationTimeout = TimeSpan.FromMinutes(10)
|
||||
};
|
||||
|
||||
var mqttServer = new MqttServerFactory().CreateMqttServer(options);
|
||||
var msgs = 0;
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
mqttServer.ApplicationMessageReceived += (sender, args) =>
|
||||
{
|
||||
msgs++;
|
||||
if (stopwatch.ElapsedMilliseconds > 1000)
|
||||
{
|
||||
Console.WriteLine($"received {msgs}");
|
||||
msgs = 0;
|
||||
stopwatch.Restart();
|
||||
}
|
||||
};
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press any key to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using MQTTnet.Core.Diagnostics;
|
||||
using MQTTnet.Core.Packets;
|
||||
using MQTTnet.Core.Protocol;
|
||||
using MQTTnet.Core.Server;
|
||||
using MQTTnet.Implementations;
|
||||
|
||||
namespace MQTTnet.TestApp.NetFramework
|
||||
{
|
||||
@@ -19,6 +20,7 @@ namespace MQTTnet.TestApp.NetFramework
|
||||
Console.WriteLine("MQTTnet - TestApp.NetFramework");
|
||||
Console.WriteLine("1 = Start client");
|
||||
Console.WriteLine("2 = Start server");
|
||||
Console.WriteLine("3 = Start performance test");
|
||||
var pressedKey = Console.ReadKey(true);
|
||||
if (pressedKey.Key == ConsoleKey.D1)
|
||||
{
|
||||
@@ -30,11 +32,17 @@ namespace MQTTnet.TestApp.NetFramework
|
||||
Task.Run(() => RunServerAsync(args));
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
}
|
||||
else if (pressedKey.Key == ConsoleKey.D3)
|
||||
{
|
||||
Task.Run(PerformanceTest.RunAsync);
|
||||
Thread.Sleep(Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task RunClientAsync(string[] arguments)
|
||||
{
|
||||
MqttTrace.TraceMessagePublished += (s, e) =>
|
||||
|
||||
MqttNetTrace.TraceMessagePublished += (s, e) =>
|
||||
{
|
||||
Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}");
|
||||
if (e.Exception != null)
|
||||
@@ -45,15 +53,20 @@ namespace MQTTnet.TestApp.NetFramework
|
||||
|
||||
try
|
||||
{
|
||||
var options = new MqttClientOptions
|
||||
var options = new MqttClientWebSocketOptions
|
||||
{
|
||||
Server = "localhost",
|
||||
ClientId = "XYZ",
|
||||
CleanSession = true
|
||||
Uri = "broker.hivemq.com:8000/mqtt"
|
||||
};
|
||||
|
||||
var client = new MqttClientFactory().CreateMqttClient(options);
|
||||
client.ApplicationMessageReceived += (s, e) =>
|
||||
////var options = new MqttClientOptions
|
||||
////{
|
||||
//// Server = "localhost",
|
||||
//// ClientId = "XYZ",
|
||||
//// CleanSession = true
|
||||
////};
|
||||
|
||||
var mqttClient = new MqttClientFactory().CreateMqttClient();
|
||||
mqttClient.ApplicationMessageReceived += (s, e) =>
|
||||
{
|
||||
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
|
||||
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
|
||||
@@ -63,26 +76,24 @@ namespace MQTTnet.TestApp.NetFramework
|
||||
Console.WriteLine();
|
||||
};
|
||||
|
||||
client.Connected += async (s, e) =>
|
||||
mqttClient.Connected += async (s, e) =>
|
||||
{
|
||||
Console.WriteLine("### CONNECTED WITH SERVER ###");
|
||||
|
||||
await client.SubscribeAsync(new List<TopicFilter>
|
||||
await mqttClient.SubscribeAsync(new List<TopicFilter>
|
||||
{
|
||||
new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)
|
||||
});
|
||||
|
||||
Console.WriteLine("### SUBSCRIBED ###");
|
||||
};
|
||||
|
||||
client.Disconnected += async (s, e) =>
|
||||
mqttClient.Disconnected += async (s, e) =>
|
||||
{
|
||||
Console.WriteLine("### DISCONNECTED FROM SERVER ###");
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync();
|
||||
await mqttClient.ConnectAsync(options);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -92,7 +103,7 @@ namespace MQTTnet.TestApp.NetFramework
|
||||
|
||||
try
|
||||
{
|
||||
await client.ConnectAsync();
|
||||
await mqttClient.ConnectAsync(options);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -101,18 +112,13 @@ namespace MQTTnet.TestApp.NetFramework
|
||||
|
||||
Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###");
|
||||
|
||||
var messageFactory = new MqttApplicationMessageFactory();
|
||||
while (true)
|
||||
{
|
||||
Console.ReadLine();
|
||||
|
||||
var applicationMessage = new MqttApplicationMessage(
|
||||
"A/B/C",
|
||||
Encoding.UTF8.GetBytes("Hello World"),
|
||||
MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
false
|
||||
);
|
||||
|
||||
await client.PublishAsync(applicationMessage);
|
||||
var applicationMessage = messageFactory.CreateApplicationMessage("myTopic", "Hello World", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
await mqttClient.PublishAsync(applicationMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
@@ -121,9 +127,9 @@ namespace MQTTnet.TestApp.NetFramework
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunServerAsync(string[] arguments)
|
||||
private static async Task RunServerAsync(string[] arguments)
|
||||
{
|
||||
MqttTrace.TraceMessagePublished += (s, e) =>
|
||||
MqttNetTrace.TraceMessagePublished += (s, e) =>
|
||||
{
|
||||
Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}");
|
||||
if (e.Exception != null)
|
||||
@@ -151,12 +157,12 @@ namespace MQTTnet.TestApp.NetFramework
|
||||
};
|
||||
|
||||
var mqttServer = new MqttServerFactory().CreateMqttServer(options);
|
||||
mqttServer.Start();
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press any key to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
mqttServer.Stop();
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -165,5 +171,19 @@ namespace MQTTnet.TestApp.NetFramework
|
||||
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
private static async Task WikiCode()
|
||||
{
|
||||
// For .NET Framwork & netstandard apps:
|
||||
MqttTcpChannel.CustomCertificateValidationCallback = (x509Certificate, x509Chain, sslPolicyErrors, mqttClientTcpOptions) =>
|
||||
{
|
||||
if (mqttClientTcpOptions.Server == "server_with_revoked_cert")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,5 @@
|
||||
x:Class="MQTTnet.TestApp.UniversalWindows.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MQTTnet.TestApp.UniversalWindows"
|
||||
RequestedTheme="Light">
|
||||
|
||||
</Application>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user