Compare commits

..
128 Commits
Author SHA1 Message Date
Christian Kratky a7d0ad5f43 Fix several bugs 2017-10-09 19:42:31 +02:00
Christian Kratky 59f07c868d Add custom certificate validation handler 2017-10-09 19:03:11 +02:00
Christian Kratky 4517d80704 Add unit tests 2017-10-07 19:51:51 +02:00
Christian Kratky 9bca4aa53f Extended certificate validation options 2017-10-07 19:37:51 +02:00
Christian Kratky 4c4539509c Add support for ignored certificate chain errors (Self signed) 2017-10-07 19:22:26 +02:00
Christian Kratky af3cebb266 Renamed trace class 2017-10-07 13:06:13 +02:00
Christian Kratky d22528ca46 Fix bugs 2017-10-04 14:44:36 +02:00
Christian Kratky 67267c05fd Refactor options 2017-10-04 13:58:51 +02:00
Christian d23ab88add Merge pull request #51 from JanEggers/Fix50_Eventing
Fix50 eventing
2017-10-04 12:32:56 +02:00
JanEggers 1d78d675f7 improved api surface of mqttserver and fixed unittests 2017-10-02 22:55:56 +02:00
JanEggers 3079ad37ce improved client livetime events 2017-10-02 22:55:46 +02:00
Christian Kratky c7b7d56229 Add disconnected event to server interface 2017-10-02 20:44:56 +02:00
Christian Kratky c97b97c677 Update readme 2017-10-01 14:23:17 +02:00
Christian Kratky 96a62551b2 Update readme 2017-10-01 14:22:14 +02:00
Christian Kratky 300c9b9ccb Update version info 2017-10-01 14:16:15 +02:00
Christian Kratky 342085db40 Update readme 2017-10-01 14:11:05 +02:00
Christian Kratky 862f76fea2 Add support for retained application messages 2017-09-30 18:42:41 +02:00
Christian Kratky 3cce33415d Update documents 2017-09-28 18:09:01 +02:00
Christian Kratky d09455f78e Add semaphore for accessing send stream 2017-09-28 09:29:47 +02:00
Christian Kratky e947881aca Remove lock to prevent dead locks 2017-09-27 21:20:42 +02:00
Christian Kratky 077c9ea00d Merge 2017-09-27 21:11:11 +02:00
Christian f8af4597f2 Merge pull request #44 from 1iveowl/master
Bug fixes v3
2017-09-27 21:09:31 +02:00
Christian Kratky 32dc96ae93 Wait until receiving of packets is started before sending packets 2017-09-27 21:08:36 +02:00
Christian Kratky 153e83ee3e Remove not needed async/await 2017-09-26 20:02:12 +02:00
Christian Kratky 6402dfd9ea Update version info to 2.3.1 2017-09-26 20:01:57 +02:00
Christian Kratky 98bafb1f26 Fix null ref exception in UWP TCP channel 2017-09-26 20:00:35 +02:00
1iveowl 6d92937892 Fixed typo 2017-09-24 17:06:41 +02:00
1iveowl 28d31d0cf5 Increased PR quality 2017-09-24 17:03:29 +02:00
1iveowl 07fd509e74 Bug fixes 2017-09-24 16:19:59 +02:00
Christian 37a5dc7abe Update README.md 2017-09-21 11:48:05 +02:00
Christian Kratky 8604097ada Merge branch 'master' of https://github.com/chkr1011/MQTTnet 2017-09-20 23:31:14 +02:00
Christian Kratky ea587d97c8 Organize solution 2017-09-20 23:31:09 +02:00
Christian fb272b07aa Update .bettercodehub.yml 2017-09-20 23:25:35 +02:00
Christian 7067a8e411 Create .bettercodehub.yml 2017-09-20 23:22:12 +02:00
Christian Kratky 50800b1d44 Fix issues with exception handling after disconnect 2017-09-20 21:36:32 +02:00
Christian Kratky dcf6f94d77 Refactoring 2017-09-20 21:28:18 +02:00
Christian Kratky 8131995006 Update core project to netstandard 1.3 2017-09-20 21:05:36 +02:00
Christian Kratky 7de1634a36 Fix issues with disconnected event 2017-09-20 21:05:13 +02:00
Christian b252781f8d Merge pull request #41 from JanEggers/develop
Develop
2017-09-20 21:00:39 +02:00
Eggers Jan 023cb12079 added resharper settings 2017-09-20 09:34:01 +02:00
Eggers Jan b5b87f5272 improved timeout after memory usage 2017-09-20 09:33:41 +02:00
Christian Kratky f287bd4d1d Fix TimeoutAfter again 2017-09-20 08:32:36 +02:00
Christian Kratky 222a154290 Update documentation 2017-09-19 23:15:41 +02:00
Christian Kratky 9b8af2455f Fix issues with "TimeoutAfter" 2017-09-19 23:08:18 +02:00
Christian Kratky 493c934463 Update documents 2017-09-19 23:07:42 +02:00
Christian Kratky 83f1d74a76 Make deserialization cancleable 2017-09-19 23:07:28 +02:00
Christian Kratky e7c8d1c1c1 Add cancellation token to adapter 2017-09-19 23:06:45 +02:00
Christian Kratky 305578e7e6 Refactor code 2017-09-19 23:05:41 +02:00
Christian Kratky 651f0aa15c Add interfaces for factories 2017-09-19 23:04:42 +02:00
Christian Kratky 51e7cd725e Extend UWP test app 2017-09-19 23:03:30 +02:00
Christian Kratky 32473af7c2 Refactor web socket usage 2017-09-18 18:20:36 +02:00
Christian Kratky b5fc6e3d12 Refactorings 2017-09-16 17:06:47 +02:00
Christian dc65bdb913 Merge pull request #40 from JanEggers/develop
Develop
2017-09-16 16:08:40 +02:00
JanEggers d4ef1a7302 fixed code issue 2017-09-16 08:29:34 +02:00
JanEggers f8c01b2168 fixed invalid operation exception 2017-09-16 08:13:46 +02:00
JanEggers d7e2b18247 packages that are events form the server may not cause invalid operation exceptions 2017-09-16 08:10:49 +02:00
JanEggers 12a275918e fixed high load on server 2017-09-16 08:02:48 +02:00
Christian Kratky eda8bff10b Optimized exception handling and awaits. 2017-09-15 15:58:48 +02:00
Christian Kratky 76105de4c7 Refactor latest changes 2017-09-15 15:26:26 +02:00
Christian Kratky eed56cdf55 Update documentation 2017-09-15 15:26:08 +02:00
Christian da7adc1068 Merge pull request #39 from JanEggers/develop
fixed unittest server and client both generated id 2 for differnt pac…
2017-09-15 08:22:58 +02:00
JanEggers f1ff947697 higher numbers 2017-09-14 22:26:28 +02:00
JanEggers 9250853854 Merge branch 'EvenMorePerf' into develop 2017-09-14 22:22:43 +02:00
JanEggers d1b634511d reduced footprint of client msg queue and numbers can go even higher 2017-09-14 22:18:54 +02:00
JanEggers cf7b7191f8 if we wait before sending the packets we dont need to store the received packets at all because the awaiter will always be there or is already canceled 2017-09-14 21:48:38 +02:00
Christian 0dcd8227da Add new badge 2017-09-14 18:33:29 +02:00
Eggers Jan 089eb9b604 fixed unittest server and client both generated id 2 for differnt package types 2017-09-14 13:37:45 +02:00
Christian f04a9c4f3e Fix readme layout 2017-09-14 08:41:23 +02:00
Christian Kratky e2d15c8764 Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop
# Conflicts:
#	MQTTnet.Core/Serializer/MqttPacketReader.cs
#	MQTTnet.Core/Serializer/MqttPacketSerializer.cs
#	Tests/MQTTnet.TestApp.NetFramework/PerformanceTest.cs
2017-09-13 21:57:39 +02:00
Christian 9a25fb63ad Merge pull request #38 from JanEggers/Buffering
Buffering
2017-09-13 21:01:59 +02:00
Christian Kratky 0e48076d25 Extended performance test app 2017-09-13 21:01:36 +02:00
Christian Kratky a956de3df7 Refactorings 2017-09-13 21:01:25 +02:00
Eggers Jan 455a047242 fixed async await return bug 2017-09-13 09:35:56 +02:00
Eggers Jan 27bb22de09 use buffered receive stream 2017-09-13 09:35:46 +02:00
Eggers Jan 285c70f24f removed unused code 2017-09-13 09:19:41 +02:00
Eggers Jan c7c1691d75 use raw buffer for frist connect msg then use buffered stream to read more efficient 2017-09-13 09:14:08 +02:00
Eggers Jan 6c7e70f78a use blockingcollection for pipelining 2017-09-13 09:14:08 +02:00
Eggers Jan 97338b016d optimized communication adapter 2017-09-13 09:14:08 +02:00
Eggers Jan 473c8e0a15 send multiple packages at once msg/sec from 9000 to 32000 2017-09-13 09:14:08 +02:00
Eggers Jan 090e59e99a unfifed timeout handling and fixed memory usage due to Task.Delay tasks for each send package are present for the duration of the timeout resulting in memory usage. new approach uses cancellationtoken that will be cleaned up directly if operation completes before timeout 2017-09-13 09:12:34 +02:00
Eggers Jan 5712bc11d2 increased concurrency to force raceconditions 2017-09-13 09:10:41 +02:00
Eggers Jan 323339b0b2 dont use byte reader writer in critical paths 2017-09-13 09:10:41 +02:00
Eggers Jan c001897590 remove async and framing from serializer 2017-09-13 09:10:41 +02:00
Eggers Jan 8fd3e5070d add missing await 2017-09-13 09:10:41 +02:00
Eggers Jan 01e739d524 generate full debug symbols 2017-09-13 09:10:41 +02:00
Eggers Jan 16a184a92f use streams 2017-09-13 09:10:41 +02:00
Christian Kratky 1a1c0beabc Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop 2017-09-12 22:07:41 +02:00
Christian Kratky ff0b022357 Partially fix UWP implementations 2017-09-12 22:07:27 +02:00
Christian c0e6e54496 Merge pull request #37 from JanEggers/PerfMsgDispatcher
Perf msg dispatcher
2017-09-12 21:50:30 +02:00
Christian Kratky 16bc9cb203 Fix awaits in try/catch 2017-09-12 21:50:07 +02:00
Eggers Jan 196a325f7f fixed incorrect merge 2017-09-12 08:23:50 +02:00
JanEggers da70dc0565 improved performance of packet dispatcher 2017-09-11 22:05:41 +02:00
Christian f7a8b764d0 Merge pull request #35 from JanEggers/BugFixMsgDispatcher
Bug fixes in msg dispatcher
2017-09-11 21:22:33 +02:00
Christian 16949ffb21 Merge pull request #36 from JanEggers/Buffering
added perf test app
2017-09-11 08:46:48 +02:00
JanEggers de0dfb9265 restore other app modes 2017-09-10 22:40:30 +02:00
JanEggers 9cde49f5f8 propper buffering in place 2017-09-10 18:20:25 +02:00
JanEggers 644bcfba27 - make MqttPacketReader and Writer a specialized version of their Binary counterparts
- improved message framing. as basis to improve buffering
- share more common code for the writer part
2017-09-10 15:41:39 +02:00
JanEggers a4d393ae28 fix code quality issues 2017-09-10 14:02:21 +02:00
JanEggers a7a5eed0f2 added perf test app 2017-09-10 14:02:21 +02:00
JanEggers 80a6e6809c use same lockobject everywhere to prevent raceconditions 2017-09-10 13:56:35 +02:00
JanEggers 861efa5e75 dont add the same package twice to _receivedPackets 2017-09-10 13:56:35 +02:00
JanEggers 6c8f85afe9 fixed invalid operation exception 2017-09-10 13:56:35 +02:00
Christian Kratky 6b1a6c054e Even more performance optimizations 2017-09-10 00:07:41 +02:00
Christian Kratky f2502ac431 Refactoring 2017-09-09 23:57:34 +02:00
Christian Kratky 76151deba0 Additional performance improvements 2017-09-09 23:53:42 +02:00
Christian 4a6790f0aa Merge pull request #34 from JanEggers/Fix_Bug33
fixed bug in read async and unfied stream handling
2017-09-09 23:18:04 +02:00
Christian 95298202a3 Merge branch 'develop' into Fix_Bug33 2017-09-09 23:17:41 +02:00
Christian Kratky 1c30d14910 Refactoring of WebSocket support 2017-09-09 23:13:53 +02:00
Christian Kratky 8aec1583df Performance improvements in area of logging and async/await 2017-09-09 22:40:37 +02:00
Christian Kratky 04036cf63a Prepare version information for the next release 2017-09-09 22:29:14 +02:00
Christian Kratky 68aae70b44 Merge branch 'develop' of https://github.com/chkr1011/MQTTnet into develop 2017-09-09 22:01:35 +02:00
Christian Kratky 0907eadb55 Make string format optional if no trace listener is attached 2017-09-09 21:50:55 +02:00
JanEggers 77818d669f Merge branch 'develop' into Fix_Bug33 2017-09-09 20:10:07 +02:00
Christian 70aee177c3 Merge pull request #32 from 1iveowl/master
ConfigureAwait(false)
2017-09-09 19:06:09 +02:00
JanEggers 5e417affa5 use configure await 2017-09-09 02:06:17 +02:00
JanEggers 783ee4328a fixed nullpointer exception in server 2017-09-09 02:01:45 +02:00
JanEggers caa857f318 fixed bug in read async and unfied stream handling 2017-09-09 01:47:32 +02:00
1iveowl 20a1b77b58 ConfigureAwait(false) 2017-09-09 00:48:50 +02:00
Christian 960900dafa Merge pull request #29 from Inventum24/WebSockets
Support for websockets
2017-09-01 18:46:07 +02:00
Christian Kratky 9bf287a1ac Update travis 2017-09-01 18:42:38 +02:00
Christian Kratky f886b0eed1 Fix travis file... 2017-09-01 18:19:59 +02:00
Christian Kratky 1f1f2d2e90 Yet another travis file 2017-09-01 18:13:16 +02:00
Christian Kratky 8ad619ebc1 Add travis.yml 2017-09-01 18:10:00 +02:00
Christian Kratky 85f1a31be3 Add travis file 2017-09-01 18:06:23 +02:00
IMPAQ\pnow 4a57d050f6 Clean Up 2017-09-01 11:28:31 +02:00
IMPAQ\pnow 3a1e32f5aa Added support for websockets 2017-09-01 09:39:59 +02:00
Christian Kratky b407221ce6 Merge branch 'master' into develop 2017-08-21 21:24:35 +02:00
Christian Kratky 487a97c31e Merge branch 'master' into develop 2017-08-17 22:53:15 +02:00
104 changed files with 3679 additions and 1909 deletions
+8
View File
@@ -0,0 +1,8 @@
exclude:
- /Frameworks/.*
- /MQTTnet.TestApp.NetCore/Program.cs
- /Tests/MQTTnet.TestApp.NetFramework/.*
- /Tests/MQTTnet.TestApp.UniversalWindows/.*
component_depth: 1
languages:
- csharp
+2
View File
@@ -286,4 +286,6 @@ __pycache__/
*.btm.cs
*.odx.cs
*.xsd.cs
Build/nuget.exe
+10
View File
@@ -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
View File
@@ -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)
{
@@ -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)' &lt; '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);
}
}
+4 -4
View File
@@ -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);
}
}
+6 -6
View File
@@ -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
View File
@@ -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 -5
View File
@@ -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,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));
+5 -1
View File
@@ -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; }
}
}
-16
View File
@@ -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; }
}
}
+52 -67
View File
@@ -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 _);
}
}
}
+63
View File
@@ -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,6 +1,6 @@
namespace MQTTnet.Core.Diagnostics
{
public enum MqttTraceLevel
public enum MqttNetTraceLevel
{
Verbose,
Information,
@@ -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; }
-44
View File
@@ -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);
}
}
}
}
}
+74
View File
@@ -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();
}
}
}
}
}
+3 -2
View File
@@ -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);
}
}
}
+1 -1
View File
@@ -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 -2
View File
@@ -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 + "]";
}
}
}
+13
View File
@@ -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; }
}
}
+8 -3
View File
@@ -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 + "]";
}
}
}
+1 -2
View File
@@ -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 + "]";
}
}
}
+3 -4
View File
@@ -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 + "]";
}
}
}
+1 -1
View File
@@ -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);
}
}
+37 -86
View File
@@ -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;
}
}
}
+335 -352
View File
@@ -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);
}
}
}
+26 -49
View File
@@ -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);
}
}
}
+6 -4
View File
@@ -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);
}
}
+12
View File
@@ -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;
}
}
}
+109 -74
View File
@@ -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);
}
}
}
}
}
+27 -23
View File
@@ -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);
}
}
}
+3 -1
View File
@@ -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
View File
@@ -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}
+7
View File
@@ -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>
+17 -108
View File
@@ -3,30 +3,36 @@
</p>
[![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet)
[![BCH compliance](https://bettercodehub.com/edge/badge/chkr1011/MQTTnet?branch=master)](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));
}
}
}
}
}
+148 -41
View File
@@ -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>
+170
View File
@@ -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();
}
}
}
+47 -27
View File
@@ -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