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 *.btm.cs
*.odx.cs *.odx.cs
*.xsd.cs *.xsd.cs
Build/nuget.exe 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 > <package >
<metadata> <metadata>
<id>MQTTnet</id> <id>MQTTnet</id>
<version>2.2.1</version> <version>2.4.0</version>
<authors>Christian Kratky</authors> <authors>Christian Kratky</authors>
<owners>Christian Kratky</owners> <owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl> <licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,19 +10,29 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl> <iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description> <description>MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
<releaseNotes>* [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> </releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</copyright> <copyright>Copyright Christian Kratky 2016-2017</copyright>
<tags>MQTT MQTTClient MQTTServer MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Queue Hardware Arduino</tags> <tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M</tags>
<dependencies> <dependencies>
<group targetFramework="netstandard1.3"> <group targetFramework="netstandard1.3">
<dependency id="NETStandard.Library" version="1.6.1" /> <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>
<group targetFramework="uap10.0"> <group targetFramework="uap10.0">
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.3.3" /> <dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.0" />
</group> </group>
</dependencies> </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; 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)); if (options == null) throw new ArgumentNullException(nameof(options));
@@ -57,9 +57,11 @@ namespace MQTTnet.Implementations
Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
} }
return Task.FromResult(0);
} }
public void Stop() public Task StopAsync()
{ {
_isRunning = false; _isRunning = false;
@@ -72,11 +74,13 @@ namespace MQTTnet.Implementations
_tlsEndpointSocket?.Dispose(); _tlsEndpointSocket?.Dispose();
_tlsEndpointSocket = null; _tlsEndpointSocket = null;
return Task.FromResult(0);
} }
public void Dispose() public void Dispose()
{ {
Stop(); StopAsync();
} }
private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken) private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken)
@@ -85,16 +89,18 @@ namespace MQTTnet.Implementations
{ {
try try
{ {
var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null); var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false);
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer());
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter)); var tcpChannel = new MqttTcpChannel(clientSocket, null);
var clientAdapter = new MqttChannelCommunicationAdapter(tcpChannel, new MqttPacketSerializer());
ClientAccepted?.Invoke(clientAdapter);
} }
catch (Exception exception) when (!(exception is ObjectDisposedException)) 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 //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 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)); 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()); var tcpChannel = new MqttTcpChannel(clientSocket, sslStream);
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter)); var clientAdapter = new MqttChannelCommunicationAdapter(tcpChannel, new MqttPacketSerializer());
ClientAccepted?.Invoke(clientAdapter);
} }
catch (Exception exception) 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 //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 System.Threading.Tasks;
using MQTTnet.Core.Channel; using MQTTnet.Core.Channel;
using MQTTnet.Core.Client; using MQTTnet.Core.Client;
using MQTTnet.Core.Exceptions; using System.IO;
using System.Linq;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable 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 Socket _socket;
private SslStream _sslStream; 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) public MqttTcpChannel(Socket socket, SslStream sslStream)
{ {
_socket = socket ?? throw new ArgumentNullException(nameof(socket)); _socket = socket ?? throw new ArgumentNullException(nameof(socket));
_sslStream = sslStream; _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)); if (_socket == null)
try
{ {
if (_socket == null) _socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
{
_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);
}
} }
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() public Task DisconnectAsync()
{ {
try Dispose();
{ return Task.FromResult(0);
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);
}
} }
public void Dispose() public void Dispose()
{ {
_socket?.Dispose(); RawReceiveStream?.Dispose();
_sslStream?.Dispose(); RawReceiveStream = null;
ReceiveStream?.Dispose();
ReceiveStream = null;
SendStream?.Dispose();
SendStream = null;
_socket?.Dispose();
_socket = null; _socket = null;
_sslStream?.Dispose();
_sslStream = null; _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) private static X509CertificateCollection LoadCertificates(MqttClientOptions options)
{ {
var certificates = new X509CertificateCollection(); 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" /> <Reference Include="System.Core" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Implementations\MqttCommunicationAdapterFactory.cs" />
<Compile Include="Implementations\MqttWebSocketChannel.cs" />
<Compile Include="Implementations\WebSocketStream.cs" />
<Compile Include="MqttClientFactory.cs" /> <Compile Include="MqttClientFactory.cs" />
<Compile Include="MqttServerFactory.cs" /> <Compile Include="MqttServerFactory.cs" />
<Compile Include="Implementations\MqttServerAdapter.cs" /> <Compile Include="Implementations\MqttServerAdapter.cs" />
@@ -1,18 +1,13 @@
using System; using MQTTnet.Core.Client;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Client;
using MQTTnet.Core.Serializer;
using MQTTnet.Implementations; using MQTTnet.Implementations;
namespace MQTTnet 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(new MqttCommunicationAdapterFactory());
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttPacketSerializer()));
} }
} }
} }
@@ -6,7 +6,7 @@ using MQTTnet.Implementations;
namespace MQTTnet namespace MQTTnet
{ {
public class MqttServerFactory public class MqttServerFactory : IMqttServerFactory
{ {
public IMqttServer CreateMqttServer(MqttServerOptions options) public IMqttServer CreateMqttServer(MqttServerOptions options)
{ {
@@ -11,5 +11,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
[assembly: Guid("a480ef90-0eaa-4d9a-b271-47a9c47f6f7d")] [assembly: Guid("a480ef90-0eaa-4d9a-b271-47a9c47f6f7d")]
[assembly: AssemblyVersion("2.2.1.0")] [assembly: AssemblyVersion("2.4.0.0")]
[assembly: AssemblyFileVersion("2.2.1.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; 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."); if (_isRunning) throw new InvalidOperationException("Server is already started.");
_isRunning = true; _isRunning = true;
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
@@ -55,9 +56,11 @@ namespace MQTTnet.Implementations
Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
} }
return Task.FromResult(0);
} }
public void Stop() public Task StopAsync()
{ {
_isRunning = false; _isRunning = false;
@@ -70,11 +73,13 @@ namespace MQTTnet.Implementations
_tlsEndpointSocket?.Dispose(); _tlsEndpointSocket?.Dispose();
_tlsEndpointSocket = null; _tlsEndpointSocket = null;
return Task.FromResult(0);
} }
public void Dispose() public void Dispose()
{ {
Stop(); StopAsync();
} }
private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken) private async Task AcceptDefaultEndpointConnectionsAsync(CancellationToken cancellationToken)
@@ -83,16 +88,16 @@ namespace MQTTnet.Implementations
{ {
try try
{ {
var clientSocket = await _defaultEndpointSocket.AcceptAsync(); var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false);
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer()); 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) 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 //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 try
{ {
var clientSocket = await _tlsEndpointSocket.AcceptAsync(); var clientSocket = await _tlsEndpointSocket.AcceptAsync().ConfigureAwait(false);
var sslStream = new SslStream(new NetworkStream(clientSocket)); var sslStream = new SslStream(new NetworkStream(clientSocket));
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false); await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer()); 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) 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 //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 System.Threading.Tasks;
using MQTTnet.Core.Channel; using MQTTnet.Core.Channel;
using MQTTnet.Core.Client; using MQTTnet.Core.Client;
using MQTTnet.Core.Exceptions; using System.IO;
using System.Linq;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
{ {
private readonly MqttClientTcpOptions _options;
private Socket _socket; private Socket _socket;
private SslStream _sslStream; 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) public MqttTcpChannel(Socket socket, SslStream sslStream)
{ {
_socket = socket ?? throw new ArgumentNullException(nameof(socket)); _socket = socket ?? throw new ArgumentNullException(nameof(socket));
_sslStream = sslStream; _sslStream = sslStream;
ReceiveStream = (Stream)sslStream ?? new NetworkStream(socket);
} }
public async Task ConnectAsync(MqttClientOptions options) public Stream SendStream => ReceiveStream;
{ public Stream ReceiveStream { get; private set; }
if (options == null) throw new ArgumentNullException(nameof(options)); public Stream RawReceiveStream => ReceiveStream;
try
{
if (_socket == null)
{
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
}
await _socket.ConnectAsync(options.Server, options.GetPort()); public static Func<X509Certificate, X509Chain, SslPolicyErrors, MqttClientTcpOptions, bool> CustomCertificateValidationCallback { get; set; }
if (options.TlsOptions.UseTls) public async Task ConnectAsync()
{ {
_sslStream = new SslStream(new NetworkStream(_socket, true)); if (_socket == null)
await _sslStream.AuthenticateAsClientAsync(options.Server, LoadCertificates(options), SslProtocols.Tls12, options.TlsOptions.CheckCertificateRevocation);
}
}
catch (SocketException exception)
{ {
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() public Task DisconnectAsync()
{ {
try Dispose();
{ return Task.FromResult(0);
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);
}
} }
public void Dispose() public void Dispose()
{ {
_socket?.Dispose(); _socket?.Dispose();
_sslStream?.Dispose();
_socket = null; _socket = null;
_sslStream?.Dispose();
_sslStream = null; _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) private static X509CertificateCollection LoadCertificates(MqttClientOptions options)
{ {
var certificates = new X509CertificateCollection(); 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> <TargetFramework>netstandard1.3</TargetFramework>
<AssemblyName>MQTTnet</AssemblyName> <AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace> <RootNamespace>MQTTnet</RootNamespace>
<AssemblyVersion>2.2.1.0</AssemblyVersion> <AssemblyVersion>2.4.0.0</AssemblyVersion>
<FileVersion>2.2.1.0</FileVersion> <FileVersion>2.4.0.0</FileVersion>
<Version>0.0.0.0</Version> <Version>0.0.0.0</Version>
<Company /> <Company />
<Product /> <Product />
@@ -23,7 +23,9 @@
</ItemGroup> </ItemGroup>
<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" /> <PackageReference Include="System.Threading.Thread" Version="4.3.0" />
</ItemGroup> </ItemGroup>
@@ -1,18 +1,13 @@
using System; using MQTTnet.Core.Client;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Client;
using MQTTnet.Core.Serializer;
using MQTTnet.Implementations; using MQTTnet.Implementations;
namespace MQTTnet 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(new MqttCommunicationAdapterFactory());
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttPacketSerializer()));
} }
} }
} }
@@ -6,7 +6,7 @@ using MQTTnet.Implementations;
namespace MQTTnet namespace MQTTnet
{ {
public class MqttServerFactory public class MqttServerFactory : IMqttServerFactory
{ {
public IMqttServer CreateMqttServer(MqttServerOptions options) 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;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter; using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Serializer; using MQTTnet.Core.Serializer;
@@ -10,23 +11,24 @@ namespace MQTTnet.Implementations
public class MqttServerAdapter : IMqttServerAdapter, IDisposable public class MqttServerAdapter : IMqttServerAdapter, IDisposable
{ {
private StreamSocketListener _defaultEndpointSocket; private StreamSocketListener _defaultEndpointSocket;
private bool _isRunning; 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 (options == null) throw new ArgumentNullException(nameof(options));
if (_isRunning) throw new InvalidOperationException("Server is already started."); if (_isRunning) throw new InvalidOperationException("Server is already started.");
_isRunning = true; _isRunning = true;
if (options.DefaultEndpointOptions.IsEnabled) if (options.DefaultEndpointOptions.IsEnabled)
{ {
_defaultEndpointSocket = new StreamSocketListener(); _defaultEndpointSocket = new StreamSocketListener();
_defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket).GetAwaiter().GetResult(); await _defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket);
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync; _defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync;
} }
if (options.TlsEndpointOptions.IsEnabled) if (options.TlsEndpointOptions.IsEnabled)
@@ -35,17 +37,19 @@ namespace MQTTnet.Implementations
} }
} }
public void Stop() public Task StopAsync()
{ {
_isRunning = false; _isRunning = false;
_defaultEndpointSocket?.Dispose(); _defaultEndpointSocket?.Dispose();
_defaultEndpointSocket = null; _defaultEndpointSocket = null;
return Task.FromResult(0);
} }
public void Dispose() public void Dispose()
{ {
Stop(); StopAsync();
} }
private void AcceptDefaultEndpointConnectionsAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args) private void AcceptDefaultEndpointConnectionsAsync(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
@@ -53,11 +57,11 @@ namespace MQTTnet.Implementations
try try
{ {
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer()); 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) 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;
using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Sockets;
using System.Runtime.InteropServices.WindowsRuntime; using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Networking; using Windows.Networking;
using Windows.Networking.Sockets; using Windows.Networking.Sockets;
using Windows.Security.Cryptography.Certificates; using Windows.Security.Cryptography.Certificates;
using Windows.Storage.Streams;
using MQTTnet.Core.Channel; using MQTTnet.Core.Channel;
using MQTTnet.Core.Client; using MQTTnet.Core.Client;
using MQTTnet.Core.Exceptions;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
{ {
private readonly MqttClientTcpOptions _options;
private StreamSocket _socket; private StreamSocket _socket;
public MqttTcpChannel() public MqttTcpChannel(MqttClientTcpOptions options)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options));
} }
public MqttTcpChannel(StreamSocket socket) public MqttTcpChannel(StreamSocket socket)
{ {
_socket = socket ?? throw new ArgumentNullException(nameof(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)); if (_socket == null)
try
{ {
if (_socket == null) _socket = new StreamSocket();
{
_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);
}
} }
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() public Task DisconnectAsync()
{ {
try Dispose();
{ return Task.FromResult(0);
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);
}
} }
public void Dispose() public void Dispose()
{ {
_socket?.Dispose(); RawReceiveStream?.Dispose();
RawReceiveStream = null;
SendStream?.Dispose();
SendStream = null;
ReceiveStream?.Dispose();
ReceiveStream = null;
_socket?.Dispose();
_socket = null; _socket = null;
} }
private void CreateStreams()
{
SendStream = _socket.OutputStream.AsStreamForWrite();
ReceiveStream = _socket.InputStream.AsStreamForRead();
RawReceiveStream = ReceiveStream;
}
private static Certificate LoadCertificate(MqttClientOptions options) private static Certificate LoadCertificate(MqttClientOptions options)
{ {
if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any()) if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any())
@@ -122,5 +103,34 @@ namespace MQTTnet.Implementations
return new Certificate(options.TlsOptions.Certificates.First().AsBuffer()); 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> <DocumentationFile>bin\x64\Release\MQTTnet.XML</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Implementations\MqttCommunicationAdapterFactory.cs" />
<Compile Include="Implementations\MqttWebSocketChannel.cs" />
<Compile Include="MqttClientFactory.cs" /> <Compile Include="MqttClientFactory.cs" />
<Compile Include="Implementations\MqttServerAdapter.cs" /> <Compile Include="Implementations\MqttServerAdapter.cs" />
<Compile Include="MqttServerFactory.cs" />
<Compile Include="Implementations\MqttTcpChannel.cs" /> <Compile Include="Implementations\MqttTcpChannel.cs" />
<Compile Include="MqttServerFactory.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Implementations\WebSocketStream.cs" />
<EmbeddedResource Include="Properties\MQTTnet.Universal.rd.xml" /> <EmbeddedResource Include="Properties\MQTTnet.Universal.rd.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -125,7 +128,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform"> <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>5.3.3</Version> <Version>5.4.0</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' "> <PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
@@ -1,18 +1,13 @@
using System; using MQTTnet.Core.Client;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Client;
using MQTTnet.Core.Serializer;
using MQTTnet.Implementations; using MQTTnet.Implementations;
namespace MQTTnet 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(new MqttCommunicationAdapterFactory());
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttPacketSerializer()));
} }
} }
} }
@@ -6,7 +6,7 @@ using MQTTnet.Implementations;
namespace MQTTnet namespace MQTTnet
{ {
public class MqttServerFactory public class MqttServerFactory : IMqttServerFactory
{ {
public IMqttServer CreateMqttServer(MqttServerOptions options) public IMqttServer CreateMqttServer(MqttServerOptions options)
{ {
@@ -10,5 +10,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
[assembly: AssemblyVersion("2.2.1.0")] [assembly: AssemblyVersion("2.4.0.0")]
[assembly: AssemblyFileVersion("2.2.1.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;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Core.Client;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
using MQTTnet.Core.Serializer; using MQTTnet.Core.Serializer;
@@ -8,14 +9,14 @@ namespace MQTTnet.Core.Adapter
{ {
public interface IMqttCommunicationAdapter 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; } 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;
using System.Threading.Tasks;
using MQTTnet.Core.Server; using MQTTnet.Core.Server;
namespace MQTTnet.Core.Adapter namespace MQTTnet.Core.Adapter
{ {
public interface IMqttServerAdapter public interface IMqttServerAdapter
{ {
event EventHandler<MqttClientConnectedEventArgs> ClientConnected; event Action<IMqttCommunicationAdapter> ClientAccepted;
void Start(MqttServerOptions options); Task StartAsync(MqttServerOptions options);
Task StopAsync();
void Stop();
} }
} }
@@ -1,9 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Core.Channel; using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions; using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
using MQTTnet.Core.Serializer; using MQTTnet.Core.Serializer;
@@ -12,6 +15,7 @@ namespace MQTTnet.Core.Adapter
public class MqttChannelCommunicationAdapter : IMqttCommunicationAdapter public class MqttChannelCommunicationAdapter : IMqttCommunicationAdapter
{ {
private readonly IMqttCommunicationChannel _channel; private readonly IMqttCommunicationChannel _channel;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer) public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer)
{ {
@@ -21,72 +25,185 @@ namespace MQTTnet.Core.Adapter
public IMqttPacketSerializer PacketSerializer { get; } 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; try
if (timeout > TimeSpan.Zero)
{ {
packet = await ExecuteWithTimeoutAsync(PacketSerializer.DeserializeAsync(_channel), timeout); ReceivedMqttPacket receivedMqttPacket;
} if (timeout > TimeSpan.Zero)
else {
{ receivedMqttPacket = await ReceiveAsync(_channel.RawReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
packet = await PacketSerializer.DeserializeAsync(_channel); }
} else
{
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false);
}
if (packet == null) if (cancellationToken.IsCancellationRequested)
{ {
throw new MqttProtocolViolationException("Received malformed packet."); throw new TaskCanceledException();
} }
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"RX <<< {packet}"); var packet = PacketSerializer.Deserialize(receivedMqttPacket);
return packet; 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); var header = MqttPacketReader.ReadHeaderFromSource(stream, cancellationToken);
if (await Task.WhenAny(timeoutTask, task) == timeoutTask)
if (header.BodyLength == 0)
{ {
throw new MqttCommunicationTimedOutException(); return new ReceivedMqttPacket(header, new MemoryStream(0));
} }
if (task.IsFaulted) var body = new byte[header.BodyLength];
{
throw new MqttCommunicationException(task.Exception);
}
return task.Result; var offset = 0;
} do
private static async Task ExecuteWithTimeoutAsync(Task task, TimeSpan timeout)
{
var timeoutTask = Task.Delay(timeout);
if (await Task.WhenAny(timeoutTask, task) == timeoutTask)
{ {
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) return new ReceivedMqttPacket(header, new MemoryStream(body, 0, body.Length));
{
throw new MqttCommunicationException(task.Exception);
}
} }
} }
} }
@@ -1,17 +1,15 @@
using System; using MQTTnet.Core.Server;
using System;
namespace MQTTnet.Core.Adapter namespace MQTTnet.Core.Adapter
{ {
public class MqttClientConnectedEventArgs : EventArgs public class MqttClientConnectedEventArgs : EventArgs
{ {
public MqttClientConnectedEventArgs(string identifier, IMqttCommunicationAdapter clientAdapter) public MqttClientConnectedEventArgs(ConnectedMqttClient client)
{ {
Identifier = identifier ?? throw new ArgumentNullException(nameof(identifier)); Client = client ?? throw new ArgumentNullException(nameof(client));
ClientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
} }
public string Identifier { get; } public ConnectedMqttClient Client { get; }
public IMqttCommunicationAdapter ClientAdapter { 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 class MqttConnectingFailedException : MqttCommunicationException
{ {
public MqttConnectingFailedException(MqttConnectReturnCode returnCode) public MqttConnectingFailedException(MqttConnectReturnCode returnCode)
: base($"Connecting with MQTT server failed ({returnCode}).") : base($"Connecting with MQTT server failed ({returnCode}).")
{ {
ReturnCode = 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 System.Threading.Tasks;
using MQTTnet.Core.Client; using System.IO;
namespace MQTTnet.Core.Channel namespace MQTTnet.Core.Channel
{ {
public interface IMqttCommunicationChannel public interface IMqttCommunicationChannel
{ {
Task ConnectAsync(MqttClientOptions options); Stream SendStream { get; }
Stream ReceiveStream { get; }
Stream RawReceiveStream { get; }
Task ConnectAsync();
Task DisconnectAsync(); 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 Connected;
event EventHandler Disconnected; event EventHandler Disconnected;
Task ConnectAsync(MqttApplicationMessage willApplicationMessage = null); Task ConnectAsync(MqttClientOptions options);
Task DisconnectAsync(); Task DisconnectAsync();
Task PublishAsync(MqttApplicationMessage applicationMessage);
Task<IList<MqttSubscribeResult>> SubscribeAsync(IList<TopicFilter> topicFilters); Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters);
Task<IList<MqttSubscribeResult>> SubscribeAsync(params TopicFilter[] topicFilters); Task UnsubscribeAsync(IEnumerable<string> topicFilters);
Task Unsubscribe(IList<string> topicFilters);
Task Unsubscribe(params 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 public class MqttClient : IMqttClient
{ {
private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>(); private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>();
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
private readonly MqttClientOptions _options; private readonly IMqttCommunicationAdapterFactory _communicationChannelFactory;
private readonly IMqttCommunicationAdapter _adapter;
private bool _disconnectedEventSuspended; private MqttClientOptions _options;
private bool _isReceivingPackets;
private int _latestPacketIdentifier; private int _latestPacketIdentifier;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private IMqttCommunicationAdapter _adapter;
public MqttClient(MqttClientOptions options, IMqttCommunicationAdapter adapter) public MqttClient(IMqttCommunicationAdapterFactory communicationChannelFactory)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _communicationChannelFactory = communicationChannelFactory ?? throw new ArgumentNullException(nameof(communicationChannelFactory));
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
_adapter.PacketSerializer.ProtocolVersion = options.ProtocolVersion;
} }
public event EventHandler Connected; public event EventHandler Connected;
public event EventHandler Disconnected; public event EventHandler Disconnected;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; 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) ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
{
throw new MqttProtocolViolationException("It is not allowed to connect with a server after the connection is established.");
}
try try
{ {
_disconnectedEventSuspended = false; _options = options;
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
};
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
_latestPacketIdentifier = 0; _latestPacketIdentifier = 0;
_packetDispatcher.Reset(); _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); await SetupIncomingPacketProcessingAsync();
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted) await AuthenticateAsync(options.WillMessage);
{
await DisconnectInternalAsync(); MqttNetTrace.Verbose(nameof(MqttClient), "MQTT connection with server established.");
throw new MqttConnectingFailedException(response.ConnectReturnCode);
}
if (_options.KeepAlivePeriod != TimeSpan.Zero) 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); Connected?.Invoke(this, EventArgs.Empty);
} }
catch (Exception) catch (Exception)
{ {
await DisconnectInternalAsync(); await DisconnectInternalAsync().ConfigureAwait(false);
throw; throw;
} }
} }
public async Task DisconnectAsync() public async Task DisconnectAsync()
{ {
if (!IsConnected)
{
return;
}
try try
{ {
await SendAsync(new MqttDisconnectPacket()); await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
} }
finally 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)); 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(); ThrowIfNotConnected();
var subscribePacket = new MqttSubscribePacket var subscribePacket = new MqttSubscribePacket
{ {
PacketIdentifier = GetNewPacketIdentifier(), 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]."); 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)); 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(); ThrowIfNotConnected();
var unsubscribePacket = new MqttUnsubscribePacket var unsubscribePacket = new MqttUnsubscribePacket
{ {
PacketIdentifier = GetNewPacketIdentifier(), PacketIdentifier = GetNewPacketIdentifier(),
TopicFilters = topicFilters TopicFilters = topicFilters.ToList()
}; };
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket); 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(); 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] var qosPackets = qosGroup.ToArray();
await SendAsync(publishPacket); 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(); ClientId = _options.ClientId,
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket); 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(); await Task.Delay(TimeSpan.FromMilliseconds(100));
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket);
await SendAndReceiveAsync<MqttPubCompPacket>(pubRecPacket.CreateResponse<MqttPubRelPacket>());
} }
} }
@@ -191,92 +215,102 @@ namespace MQTTnet.Core.Client
if (!IsConnected) throw new MqttCommunicationException("The client is not connected."); 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() private async Task DisconnectInternalAsync()
{ {
var cts = _cancellationTokenSource;
if (cts == null || cts.IsCancellationRequested)
{
return;
}
cts.Cancel(false);
cts.Dispose();
_cancellationTokenSource = null;
try try
{ {
await _adapter.DisconnectAsync(); await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
MqttNetTrace.Information(nameof(MqttClient), "Disconnected from adapter.");
} }
catch (Exception exception) catch (Exception exception)
{ {
MqttTrace.Warning(nameof(MqttClient), exception, "Error while disconnecting."); MqttNetTrace.Warning(nameof(MqttClient), exception, "Error while disconnecting from adapter.");
} }
finally finally
{ {
_cancellationTokenSource?.Cancel(false); Disconnected?.Invoke(this, EventArgs.Empty);
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
IsConnected = false;
if (!_disconnectedEventSuspended)
{
_disconnectedEventSuspended = true;
Disconnected?.Invoke(this, EventArgs.Empty);
}
} }
} }
private Task ProcessReceivedPacketAsync(MqttBasePacket mqttPacket) private async Task ProcessReceivedPacketAsync(MqttBasePacket packet)
{ {
try 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) 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) private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
{ {
var applicationMessage = publishPacket.ToApplicationMessage();
try try
{ {
var applicationMessage = publishPacket.ToApplicationMessage();
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(applicationMessage)); ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(applicationMessage));
} }
catch (Exception exception) 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) if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{ {
FireApplicationMessageReceivedEvent(publishPacket); FireApplicationMessageReceivedEvent(publishPacket);
return Task.FromResult(0); return;
} }
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{ {
FireApplicationMessageReceivedEvent(publishPacket); FireApplicationMessageReceivedEvent(publishPacket);
return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }); await SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
return;
} }
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
@@ -288,50 +322,33 @@ namespace MQTTnet.Core.Client
} }
FireApplicationMessageReceivedEvent(publishPacket); 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."); throw new MqttCommunicationException("Received a not supported QoS level.");
} }
private async Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket) private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
{ {
lock (_unacknowledgedPublishPackets) lock (_unacknowledgedPublishPackets)
{ {
_unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier); _unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier);
} }
await SendAsync(pubRelPacket.CreateResponse<MqttPubCompPacket>()); return SendAsync(pubRelPacket.CreateResponse<MqttPubCompPacket>());
} }
private Task SendAsync(MqttBasePacket packet) 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 private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
{ {
bool ResponsePacketSelector(MqttBasePacket p) var packetAwaiter = _packetDispatcher.WaitForPacketAsync(requestPacket, typeof(TResponsePacket), _options.DefaultCommunicationTimeout);
{ await _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false);
var p1 = p as TResponsePacket; return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
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);
} }
private ushort GetNewPacketIdentifier() private ushort GetNewPacketIdentifier()
@@ -341,79 +358,99 @@ namespace MQTTnet.Core.Client
private async Task SendKeepAliveMessagesAsync(CancellationToken cancellationToken) 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 try
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
await Task.Delay(_options.KeepAlivePeriod, cancellationToken); await Task.Delay(_options.KeepAlivePeriod, cancellationToken).ConfigureAwait(false);
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()); if (cancellationToken.IsCancellationRequested)
{
return;
}
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
} }
} }
catch (OperationCanceledException)
{
}
catch (MqttCommunicationException exception) catch (MqttCommunicationException exception)
{ {
MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication error while receiving packets."); if (cancellationToken.IsCancellationRequested)
await DisconnectInternalAsync(); {
return;
}
MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
MqttTrace.Warning(nameof(MqttClient), exception, "Error while sending/receiving keep alive packets."); MqttNetTrace.Warning(nameof(MqttClient), exception, "Unhandled exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync(); await DisconnectInternalAsync().ConfigureAwait(false);
} }
finally 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) private async Task ReceivePackets(CancellationToken cancellationToken)
{ {
MqttTrace.Information(nameof(MqttClient), "Start receiving packets."); MqttNetTrace.Information(nameof(MqttClient), "Start receiving packets.");
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero); _isReceivingPackets = true;
MqttTrace.Information(nameof(MqttClient), $"Received <<< {packet}");
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return;
}
StartProcessReceivedPacket(packet, cancellationToken); StartProcessReceivedPacket(packet, cancellationToken);
} }
} }
catch (OperationCanceledException)
{
}
catch (MqttCommunicationException exception) catch (MqttCommunicationException exception)
{ {
MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets."); if (cancellationToken.IsCancellationRequested)
await DisconnectInternalAsync(); {
return;
}
MqttNetTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
MqttTrace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets."); MqttNetTrace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets.");
await DisconnectInternalAsync(); await DisconnectInternalAsync().ConfigureAwait(false);
} }
finally finally
{ {
MqttTrace.Information(nameof(MqttClient), "Stopped receiving packets."); MqttNetTrace.Information(nameof(MqttClient), "Stopped receiving packets.");
} }
} }
private void StartProcessReceivedPacket(MqttBasePacket packet, CancellationToken cancellationToken) 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 #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 #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 #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); 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
}
private void StartSendKeepAliveMessages()
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => SendKeepAliveMessagesAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #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 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 MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
public MqttApplicationMessage WillMessage { get; set; }
public string UserName { get; set; } public string UserName { get; set; }
public string Password { 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 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)); 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 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; } 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;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions; using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
using System.Collections.Concurrent;
namespace MQTTnet.Core.Client namespace MQTTnet.Core.Client
{ {
public class MqttPacketDispatcher public class MqttPacketDispatcher
{ {
private readonly object _syncRoot = new object(); private readonly ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>> _packetByResponseType = new ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>>();
private readonly List<MqttBasePacket> _receivedPackets = new List<MqttBasePacket>(); private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _packetByResponseTypeAndIdentifier = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>();
private readonly List<MqttPacketAwaiter> _packetAwaiters = new List<MqttPacketAwaiter>();
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); var packetAwaiter = AddPacketAwaiter(request, responseType);
DispatchPendingPackets(); try
var hasTimeout = await Task.WhenAny(Task.Delay(timeout), packetAwaiter.Task) != packetAwaiter.Task;
RemovePacketAwaiter(packetAwaiter);
if (hasTimeout)
{ {
MqttTrace.Warning(nameof(MqttPacketDispatcher), "Timeout while waiting for packet."); return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false);
throw new MqttCommunicationTimedOutException(); }
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) public void Dispatch(MqttBasePacket packet)
{ {
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null) throw new ArgumentNullException(nameof(packet));
var packetDispatched = false; var type = packet.GetType();
foreach (var packetAwaiter in GetPacketAwaiters()) if (packet is IMqttPacketWithIdentifier withIdentifier)
{ {
if (packetAwaiter.PacketSelector(packet)) if (_packetByResponseTypeAndIdentifier.TryGetValue(type, out var byid))
{ {
packetAwaiter.SetResult(packet); if (byid.TryRemove(withIdentifier.PacketIdentifier, out var tcs))
packetDispatched = true; {
break; tcs.TrySetResult(packet);
return;
}
} }
} }
else if (_packetByResponseType.TryRemove(type, out var tcs))
{
tcs.TrySetResult(packet);
return;
}
lock (_syncRoot) throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched.");
{
if (!packetDispatched)
{
_receivedPackets.Add(packet);
}
else
{
_receivedPackets.Remove(packet);
}
}
} }
public void Reset() public void Reset()
{ {
lock (_syncRoot) _packetByResponseTypeAndIdentifier.Clear();
{ _packetByResponseType.Clear();
_packetAwaiters.Clear();
_receivedPackets.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); var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
_packetAwaiters.Add(packetAwaiter); byId.TryRemove(requestWithIdentifier.PacketIdentifier, out var _);
return packetAwaiter;
} }
} else
private void RemovePacketAwaiter(MqttPacketAwaiter packetAwaiter)
{
lock (_syncRoot)
{ {
_packetAwaiters.Remove(packetAwaiter); _packetByResponseType.TryRemove(responseType, out var _);
}
}
private void DispatchPendingPackets()
{
List<MqttBasePacket> receivedPackets;
lock (_syncRoot)
{
receivedPackets = new List<MqttBasePacket>(_receivedPackets);
}
foreach (var pendingPacket in receivedPackets)
{
Dispatch(pendingPacket);
} }
} }
} }
+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 namespace MQTTnet.Core.Diagnostics
{ {
public enum MqttTraceLevel public enum MqttNetTraceLevel
{ {
Verbose, Verbose,
Information, Information,
@@ -2,9 +2,9 @@
namespace MQTTnet.Core.Diagnostics 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; ThreadId = threadId;
Source = source; Source = source;
@@ -17,7 +17,7 @@ namespace MQTTnet.Core.Diagnostics
public string Source { get; } public string Source { get; }
public MqttTraceLevel Level { get; } public MqttNetTraceLevel Level { get; }
public string Message { 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 sealed class MqttProtocolViolationException : Exception
{ {
public MqttProtocolViolationException(string message) public MqttProtocolViolationException(string message)
: base(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> <AssemblyName>MQTTnet.Core</AssemblyName>
<RootNamespace>MQTTnet.Core</RootNamespace> <RootNamespace>MQTTnet.Core</RootNamespace>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild> <GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<DebugType>Full</DebugType>
<Description></Description> <Description></Description>
<Product></Product> <Product></Product>
<Company></Company> <Company></Company>
@@ -16,8 +17,8 @@
<PackageIconUrl></PackageIconUrl> <PackageIconUrl></PackageIconUrl>
<RepositoryUrl></RepositoryUrl> <RepositoryUrl></RepositoryUrl>
<PackageTags></PackageTags> <PackageTags></PackageTags>
<FileVersion>2.2.1.0</FileVersion> <FileVersion>2.4.0.0</FileVersion>
<AssemblyVersion>2.2.1.0</AssemblyVersion> <AssemblyVersion>2.4.0.0</AssemblyVersion>
<PackageLicenseUrl></PackageLicenseUrl> <PackageLicenseUrl></PackageLicenseUrl>
</PropertyGroup> </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() 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 namespace MQTTnet.Core.Packets
{ {
public sealed class MqttConnectPacket: MqttBasePacket public sealed class MqttConnectPacket : MqttBasePacket
{ {
public MqttProtocolVersion ProtocolVersion { get; set; } public MqttProtocolVersion ProtocolVersion { get; set; }
@@ -20,7 +20,7 @@ namespace MQTTnet.Core.Packets
public override string ToString() 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; using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Packets namespace MQTTnet.Core.Packets
@@ -17,8 +17,13 @@ namespace MQTTnet.Core.Packets
public override string ToString() public override string ToString()
{ {
return return nameof(MqttPublishPacket) +
$"{nameof(MqttPublishPacket)} [Topic={Topic}] [Payload={Encoding.UTF8.GetString(Payload, 0, Payload.Length)}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]"; ": [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() public override string ToString()
{ {
var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString())); var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString()));
return return nameof(MqttSubAckPacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [SubscribeReturnCodes=" + subscribeReturnCodesText + "]";
$"{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 sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort PacketIdentifier { get; set; }
public IList<TopicFilter> TopicFilters { get; set; } = new List<TopicFilter>(); public IList<TopicFilter> TopicFilters { get; set; } = new List<TopicFilter>();
public override string ToString() public override string ToString()
{ {
var topicFiltersText = string.Join(",", TopicFilters.Select(f => $"{f.Topic}@{f.QualityOfServiceLevel}")); var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel));
return return nameof(MqttSubscribePacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
$"{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 sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{ {
public ushort PacketIdentifier { get; set; } public ushort PacketIdentifier { get; set; }
public IList<string> TopicFilters { get; set; } = new List<string>(); public IList<string> TopicFilters { get; set; } = new List<string>();
} }
} }
@@ -1,5 +1,4 @@
using System.Threading.Tasks; using MQTTnet.Core.Adapter;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Serializer namespace MQTTnet.Core.Serializer
@@ -8,8 +7,8 @@ namespace MQTTnet.Core.Serializer
{ {
MqttProtocolVersion ProtocolVersion { get; set; } 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;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Core.Channel; using MQTTnet.Core.Adapter;
using MQTTnet.Core.Exceptions; using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Protocol; using MQTTnet.Core.Protocol;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Serializer namespace MQTTnet.Core.Serializer
{ {
public sealed class MqttPacketReader : IDisposable public sealed class MqttPacketReader : BinaryReader
{ {
private readonly MemoryStream _remainingData = new MemoryStream(1024); private readonly ReceivedMqttPacket _receivedMqttPacket;
private readonly IMqttCommunicationChannel _source;
public MqttPacketReader(ReceivedMqttPacket receivedMqttPacket)
private int _remainingLength; : base(receivedMqttPacket.Body, Encoding.UTF8, true)
public MqttPacketReader(IMqttCommunicationChannel source)
{ {
_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 static MqttPacketHeader ReadHeaderFromSource(Stream stream, CancellationToken cancellationToken)
public bool EndOfRemainingData => _remainingData.Position == _remainingData.Length;
public async Task ReadToEndAsync()
{ {
await ReadFixedHeaderAsync(); var fixedHeader = (byte)stream.ReadByte();
await ReadRemainingLengthAsync(); var controlPacketType = (MqttControlPacketType)(fixedHeader >> 4);
var bodyLength = ReadBodyLengthFromSource(stream, cancellationToken);
if (_remainingLength == 0) return new MqttPacketHeader
{ {
return; FixedHeader = fixedHeader,
} ControlPacketType = controlPacketType,
BodyLength = bodyLength
var buffer = new byte[_remainingLength]; };
await ReadFromSourceAsync(buffer);
_remainingData.Write(buffer, 0, buffer.Length);
_remainingData.Position = 0;
} }
public async Task<byte> ReadRemainingDataByteAsync() public override ushort ReadUInt16()
{ {
return (await ReadRemainingDataAsync(1))[0]; var buffer = ReadBytes(2);
}
public async Task<ushort> ReadRemainingDataUShortAsync()
{
var buffer = await ReadRemainingDataAsync(2);
var temp = buffer[0]; var temp = buffer[0];
buffer[0] = buffer[1]; buffer[0] = buffer[1];
@@ -59,32 +47,24 @@ namespace MQTTnet.Core.Serializer
return BitConverter.ToUInt16(buffer, 0); 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); return Encoding.UTF8.GetString(buffer, 0, buffer.Length);
} }
public async Task<byte[]> ReadRemainingDataWithLengthPrefixAsync() public byte[] ReadWithLengthPrefix()
{ {
var length = await ReadRemainingDataUShortAsync(); var length = ReadUInt16();
return await ReadRemainingDataAsync(length); 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) private static int ReadBodyLengthFromSource(Stream stream, CancellationToken cancellationToken)
{
var buffer = new byte[length];
await _remainingData.ReadAsync(buffer, 0, buffer.Length);
return buffer;
}
private async Task ReadRemainingLengthAsync()
{ {
// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html. // Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
var multiplier = 1; var multiplier = 1;
@@ -92,49 +72,20 @@ namespace MQTTnet.Core.Serializer
byte encodedByte; byte encodedByte;
do do
{ {
encodedByte = await ReadStreamByteAsync(); if (cancellationToken.IsCancellationRequested)
{
throw new TaskCanceledException();
}
encodedByte = (byte)stream.ReadByte();
value += (encodedByte & 127) * multiplier; value += (encodedByte & 127) * multiplier;
multiplier *= 128; multiplier *= 128;
if (multiplier > 128 * 128 * 128) if (multiplier > 128 * 128 * 128)
{ {
throw new MqttProtocolViolationException("Remaining length is ivalid."); throw new MqttProtocolViolationException("Remaining length is invalid.");
} }
} while ((encodedByte & 128) != 0); } while ((encodedByte & 128) != 0);
return value;
_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();
} }
} }
} }
+335 -352
View File
@@ -1,8 +1,9 @@
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using MQTTnet.Core.Adapter;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Exceptions; using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol; using MQTTnet.Core.Protocol;
@@ -16,232 +17,251 @@ namespace MQTTnet.Core.Serializer
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; 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 (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) if (packet is MqttConnectPacket connectPacket)
{ {
return SerializeAsync(connectPacket, destination); return Serialize(connectPacket, writer);
} }
if (packet is MqttConnAckPacket connAckPacket) if (packet is MqttConnAckPacket connAckPacket)
{ {
return SerializeAsync(connAckPacket, destination); return Serialize(connAckPacket, writer);
} }
if (packet is MqttDisconnectPacket disconnectPacket) if (packet is MqttDisconnectPacket disconnectPacket)
{ {
return SerializeAsync(disconnectPacket, destination); return Serialize(disconnectPacket, writer);
} }
if (packet is MqttPingReqPacket pingReqPacket) if (packet is MqttPingReqPacket pingReqPacket)
{ {
return SerializeAsync(pingReqPacket, destination); return Serialize(pingReqPacket, writer);
} }
if (packet is MqttPingRespPacket pingRespPacket) if (packet is MqttPingRespPacket pingRespPacket)
{ {
return SerializeAsync(pingRespPacket, destination); return Serialize(pingRespPacket, writer);
} }
if (packet is MqttPublishPacket publishPacket) if (packet is MqttPublishPacket publishPacket)
{ {
return SerializeAsync(publishPacket, destination); return Serialize(publishPacket, writer);
} }
if (packet is MqttPubAckPacket pubAckPacket) if (packet is MqttPubAckPacket pubAckPacket)
{ {
return SerializeAsync(pubAckPacket, destination); return Serialize(pubAckPacket, writer);
} }
if (packet is MqttPubRecPacket pubRecPacket) if (packet is MqttPubRecPacket pubRecPacket)
{ {
return SerializeAsync(pubRecPacket, destination); return Serialize(pubRecPacket, writer);
} }
if (packet is MqttPubRelPacket pubRelPacket) if (packet is MqttPubRelPacket pubRelPacket)
{ {
return SerializeAsync(pubRelPacket, destination); return Serialize(pubRelPacket, writer);
} }
if (packet is MqttPubCompPacket pubCompPacket) if (packet is MqttPubCompPacket pubCompPacket)
{ {
return SerializeAsync(pubCompPacket, destination); return Serialize(pubCompPacket, writer);
} }
if (packet is MqttSubscribePacket subscribePacket) if (packet is MqttSubscribePacket subscribePacket)
{ {
return SerializeAsync(subscribePacket, destination); return Serialize(subscribePacket, writer);
} }
if (packet is MqttSubAckPacket subAckPacket) if (packet is MqttSubAckPacket subAckPacket)
{ {
return SerializeAsync(subAckPacket, destination); return Serialize(subAckPacket, writer);
} }
if (packet is MqttUnsubscribePacket unsubscribePacket) if (packet is MqttUnsubscribePacket unsubscribePacket)
{ {
return SerializeAsync(unsubscribePacket, destination); return Serialize(unsubscribePacket, writer);
} }
if (packet is MqttUnsubAckPacket unsubAckPacket) if (packet is MqttUnsubAckPacket unsubAckPacket)
{ {
return SerializeAsync(unsubAckPacket, destination); return Serialize(unsubAckPacket, writer);
} }
throw new MqttProtocolViolationException("Packet type invalid."); 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(); return Deserialize(receivedMqttPacket.Header, reader);
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.");
}
}
} }
} }
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 var packet = new MqttUnsubscribePacket
{ {
PacketIdentifier = await reader.ReadRemainingDataUShortAsync(), PacketIdentifier = reader.ReadUInt16(),
}; };
while (!reader.EndOfRemainingData) while (!reader.EndOfRemainingData)
{ {
packet.TopicFilters.Add(await reader.ReadRemainingDataStringWithLengthPrefixAsync()); packet.TopicFilters.Add(reader.ReadStringWithLengthPrefix());
} }
return packet; return packet;
} }
private static async Task<MqttBasePacket> DeserializeSubscribeAsync(MqttPacketReader reader) private static MqttBasePacket DeserializeSubscribe(MqttPacketReader reader)
{ {
var packet = new MqttSubscribePacket var packet = new MqttSubscribePacket
{ {
PacketIdentifier = await reader.ReadRemainingDataUShortAsync(), PacketIdentifier = reader.ReadUInt16()
}; };
while (!reader.EndOfRemainingData) while (!reader.EndOfRemainingData)
{ {
packet.TopicFilters.Add(new TopicFilter( packet.TopicFilters.Add(new TopicFilter(
await reader.ReadRemainingDataStringWithLengthPrefixAsync(), reader.ReadStringWithLengthPrefix(),
(MqttQualityOfServiceLevel)await reader.ReadRemainingDataByteAsync())); (MqttQualityOfServiceLevel)reader.ReadByte()));
} }
return packet; 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 retain = fixedHeader.Read();
var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2); var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2);
var dup = fixedHeader.Read(); var dup = fixedHeader.Read();
var topic = await reader.ReadRemainingDataStringWithLengthPrefixAsync(); var topic = reader.ReadStringWithLengthPrefix();
ushort packetIdentifier = 0; ushort packetIdentifier = 0;
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce) if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{ {
packetIdentifier = await reader.ReadRemainingDataUShortAsync(); packetIdentifier = reader.ReadUInt16();
} }
var packet = new MqttPublishPacket var packet = new MqttPublishPacket
@@ -250,22 +270,22 @@ namespace MQTTnet.Core.Serializer
QualityOfServiceLevel = qualityOfServiceLevel, QualityOfServiceLevel = qualityOfServiceLevel,
Dup = dup, Dup = dup,
Topic = topic, Topic = topic,
Payload = await reader.ReadRemainingDataAsync(), Payload = reader.ReadRemainingData(),
PacketIdentifier = packetIdentifier PacketIdentifier = packetIdentifier
}; };
return packet; 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; MqttProtocolVersion protocolVersion;
var protocolName = await reader.ReadRemainingDataAsync(4); var protocolName = reader.ReadBytes(4);
if (protocolName.SequenceEqual(ProtocolVersionV310Name)) if (protocolName.SequenceEqual(ProtocolVersionV310Name))
{ {
await reader.ReadRemainingDataAsync(2); reader.ReadBytes(2);
protocolVersion = MqttProtocolVersion.V310; protocolVersion = MqttProtocolVersion.V310;
} }
else if (protocolName.SequenceEqual(ProtocolVersionV311Name)) else if (protocolName.SequenceEqual(ProtocolVersionV311Name))
@@ -277,8 +297,8 @@ namespace MQTTnet.Core.Serializer
throw new MqttProtocolViolationException("Protocol name is not supported."); throw new MqttProtocolViolationException("Protocol name is not supported.");
} }
var protocolLevel = await reader.ReadRemainingDataByteAsync(); var protocolLevel = reader.ReadByte();
var connectFlags = await reader.ReadRemainingDataByteAsync(); var connectFlags = reader.ReadByte();
var connectFlagsReader = new ByteReader(connectFlags); var connectFlagsReader = new ByteReader(connectFlags);
connectFlagsReader.Read(); // Reserved. connectFlagsReader.Read(); // Reserved.
@@ -295,51 +315,51 @@ namespace MQTTnet.Core.Serializer
var passwordFlag = connectFlagsReader.Read(); var passwordFlag = connectFlagsReader.Read();
var usernameFlag = connectFlagsReader.Read(); var usernameFlag = connectFlagsReader.Read();
packet.KeepAlivePeriod = await reader.ReadRemainingDataUShortAsync(); packet.KeepAlivePeriod = reader.ReadUInt16();
packet.ClientId = await reader.ReadRemainingDataStringWithLengthPrefixAsync(); packet.ClientId = reader.ReadStringWithLengthPrefix();
if (willFlag) if (willFlag)
{ {
packet.WillMessage = new MqttApplicationMessage( packet.WillMessage = new MqttApplicationMessage(
await reader.ReadRemainingDataStringWithLengthPrefixAsync(), reader.ReadStringWithLengthPrefix(),
await reader.ReadRemainingDataWithLengthPrefixAsync(), reader.ReadWithLengthPrefix(),
(MqttQualityOfServiceLevel)willQoS, (MqttQualityOfServiceLevel)willQoS,
willRetain); willRetain);
} }
if (usernameFlag) if (usernameFlag)
{ {
packet.Username = await reader.ReadRemainingDataStringWithLengthPrefixAsync(); packet.Username = reader.ReadStringWithLengthPrefix();
} }
if (passwordFlag) if (passwordFlag)
{ {
packet.Password = await reader.ReadRemainingDataStringWithLengthPrefixAsync(); packet.Password = reader.ReadStringWithLengthPrefix();
} }
ValidateConnectPacket(packet); ValidateConnectPacket(packet);
return packet; return packet;
} }
private static async Task<MqttBasePacket> DeserializeSubAck(MqttPacketReader reader) private static MqttBasePacket DeserializeSubAck(MqttPacketReader reader)
{ {
var packet = new MqttSubAckPacket var packet = new MqttSubAckPacket
{ {
PacketIdentifier = await reader.ReadRemainingDataUShortAsync() PacketIdentifier = reader.ReadUInt16()
}; };
while (!reader.EndOfRemainingData) while (!reader.EndOfRemainingData)
{ {
packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)await reader.ReadRemainingDataByteAsync()); packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)reader.ReadByte());
} }
return packet; return packet;
} }
private static async Task<MqttBasePacket> DeserializeConnAck(MqttPacketReader reader) private static MqttBasePacket DeserializeConnAck(MqttPacketReader reader)
{ {
var variableHeader1 = await reader.ReadRemainingDataByteAsync(); var variableHeader1 = reader.ReadByte();
var variableHeader2 = await reader.ReadRemainingDataByteAsync(); var variableHeader2 = reader.ReadByte();
var packet = new MqttConnAckPacket 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); 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 writer.Write(ProtocolVersionV311Name);
output.Write(0x00, 0x04); // 3.1.2.1 Protocol Name writer.Write(0x04); // 3.1.2.2 Protocol Level (4)
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);
} }
} else
private Task SerializeAsync(MqttConnAckPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{ {
var connectAcknowledgeFlags = new ByteWriter(); writer.Write(ProtocolVersionV310Name);
writer.Write(0x64, 0x70, 0x03); // Protocol Level (0x03)
if (ProtocolVersion == MqttProtocolVersion.V311)
{
connectAcknowledgeFlags.Write(packet.IsSessionPresent);
}
output.Write(connectAcknowledgeFlags);
output.Write((byte)packet.ConnectReturnCode);
output.InjectFixedHeader(MqttControlPacketType.ConnAck);
return output.WriteToAsync(destination);
} }
}
private static async Task SerializeAsync(MqttPubRelPacket packet, IMqttCommunicationChannel destination) var connectFlags = new ByteWriter(); // 3.1.2.3 Connect Flags
{ connectFlags.Write(false); // Reserved
using (var output = new MqttPacketWriter()) connectFlags.Write(packet.CleanSession);
connectFlags.Write(packet.WillMessage != null);
if (packet.WillMessage != null)
{ {
output.Write(packet.PacketIdentifier); connectFlags.Write((int)packet.WillMessage.QualityOfServiceLevel, 2);
connectFlags.Write(packet.WillMessage.Retain);
output.InjectFixedHeader(MqttControlPacketType.PubRel, 0x02);
await output.WriteToAsync(destination);
} }
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); ValidatePublishPacket(packet);
using (var output = new MqttPacketWriter()) writer.WriteWithLengthPrefix(packet.Topic);
{
output.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) writer.WriteWithLengthPrefix(topicFilter.Topic);
{ writer.Write((byte)topicFilter.QualityOfServiceLevel);
throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5].");
}
} }
}
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); foreach (var topicFilter in packet.TopicFilters)
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) writer.WriteWithLengthPrefix(topicFilter);
{
output.WriteWithLengthPrefix(topicFilter.Topic);
output.Write((byte)topicFilter.QualityOfServiceLevel);
}
} }
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()) writer.Write(packet.PacketIdentifier);
{
output.Write(packet.PacketIdentifier);
if (packet.SubscribeReturnCodes?.Any() == true) return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
{
foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes)
{
output.Write((byte)packetSubscribeReturnCode);
}
}
output.InjectFixedHeader(MqttControlPacketType.SubAck);
return output.WriteToAsync(destination);
}
} }
private static Task SerializeAsync(MqttUnsubscribePacket packet, IMqttCommunicationChannel destination) private static byte SerializeEmptyPacketAsync(MqttControlPacketType type, MqttPacketWriter writer)
{ {
using (var output = new MqttPacketWriter()) return MqttPacketWriter.BuildFixedHeader(type);
{
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);
}
} }
} }
} }
+26 -49
View File
@@ -1,47 +1,46 @@
using System; using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Protocol; using MQTTnet.Core.Protocol;
using System.Collections.Generic;
namespace MQTTnet.Core.Serializer 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; var fixedHeader = (int)packetType << 4;
fixedHeader |= flags; fixedHeader |= flags;
InjectFixedHeader((byte)fixedHeader); return (byte)fixedHeader;
} }
public void Write(byte value) public override void Write(ushort value)
{
_buffer.WriteByte(value);
}
public void Write(ushort value)
{ {
var buffer = BitConverter.GetBytes(value); var buffer = BitConverter.GetBytes(value);
_buffer.WriteByte(buffer[1]); Write(buffer[1], buffer[0]);
_buffer.WriteByte(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) public void Write(ByteWriter value)
{ {
if (value == null) throw new ArgumentNullException(nameof(value)); if (value == null) throw new ArgumentNullException(nameof(value));
_buffer.WriteByte(value.Value); Write(value.Value);
}
public void Write(params byte[] value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
_buffer.Write(value, 0, value.Length);
} }
public void WriteWithLengthPrefix(string value) public void WriteWithLengthPrefix(string value)
@@ -57,36 +56,16 @@ namespace MQTTnet.Core.Serializer
Write(value); Write(value);
} }
public Task WriteToAsync(IMqttCommunicationChannel destination) public static void BuildLengthHeader(int length, List<byte> header)
{ {
if (destination == null) throw new ArgumentNullException(nameof(destination)); if (length == 0)
return destination.WriteAsync(_buffer.ToArray());
}
public void Dispose()
{
_buffer?.Dispose();
}
private void InjectFixedHeader(byte fixedHeader)
{
if (_buffer.Length == 0)
{ {
Write(fixedHeader); header.Add(0);
Write(0);
return; 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. // 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 do
{ {
var encodedByte = x % 128; var encodedByte = x % 128;
@@ -96,10 +75,8 @@ namespace MQTTnet.Core.Serializer
encodedByte = encodedByte | 128; encodedByte = encodedByte | 128;
} }
_buffer.WriteByte((byte)encodedByte); header.Add((byte)encodedByte);
} while (x > 0); } while (x > 0);
_buffer.Write(backupBuffer, 0, backupBuffer.Length);
} }
} }
} }
+6 -4
View File
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter; using MQTTnet.Core.Adapter;
namespace MQTTnet.Core.Server namespace MQTTnet.Core.Server
@@ -8,11 +9,12 @@ namespace MQTTnet.Core.Server
{ {
event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
event EventHandler<MqttClientConnectedEventArgs> ClientConnected; event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
IList<ConnectedMqttClient> GetConnectedClients(); IReadOnlyList<ConnectedMqttClient> GetConnectedClients();
void InjectClient(string identifier, IMqttCommunicationAdapter adapter);
void Publish(MqttApplicationMessage applicationMessage); 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.Internal;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol; using MQTTnet.Core.Protocol;
using MQTTnet.Core.Serializer;
namespace MQTTnet.Core.Server namespace MQTTnet.Core.Server
{ {
public sealed class MqttClientSession : IDisposable public sealed class MqttClientSession : IDisposable
{ {
private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>(); private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>();
private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager(); private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager();
private readonly MqttClientMessageQueue _messageQueue; private readonly MqttClientSessionsManager _mqttClientSessionsManager;
private readonly Action<MqttClientSession, MqttPublishPacket> _publishPacketReceivedCallback; private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue;
private readonly MqttServerOptions _options; private readonly MqttServerOptions _options;
private IMqttCommunicationAdapter _adapter;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private string _identifier; private MqttApplicationMessage _willMessage;
private MqttApplicationMessage _willApplicationMessage;
public MqttClientSession(string clientId, MqttServerOptions options, Action<MqttClientSession, MqttPublishPacket> publishPacketReceivedCallback) public MqttClientSession(string clientId, MqttServerOptions options, MqttClientSessionsManager mqttClientSessionsManager)
{ {
ClientId = clientId; ClientId = clientId;
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_publishPacketReceivedCallback = publishPacketReceivedCallback ?? throw new ArgumentNullException(nameof(publishPacketReceivedCallback)); _mqttClientSessionsManager = mqttClientSessionsManager ?? throw new ArgumentNullException(nameof(mqttClientSessionsManager));
_pendingMessagesQueue = new MqttClientPendingMessagesQueue(options, this);
_messageQueue = new MqttClientMessageQueue(options);
} }
public string ClientId { get; } 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)); if (adapter == null) throw new ArgumentNullException(nameof(adapter));
_willApplicationMessage = willApplicationMessage; _willMessage = willMessage;
try try
{ {
_identifier = identifier; _adapter = adapter;
Adapter = adapter;
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
_messageQueue.Start(adapter); _pendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token);
while (!_cancellationTokenSource.IsCancellationRequested) await ReceivePacketsAsync(adapter, _cancellationTokenSource.Token);
{
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero);
await HandleIncomingPacketAsync(packet);
}
} }
catch (MqttCommunicationException) catch (OperationCanceledException)
{ {
} }
catch (MqttCommunicationException exception)
{
MqttNetTrace.Warning(nameof(MqttClientSession), exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
}
catch (Exception exception) 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) _mqttClientSessionsManager.DispatchPublishPacket(this, _willMessage.ToPublishPacket());
{
_publishPacketReceivedCallback(this, _willApplicationMessage.ToPublishPacket());
}
_messageQueue.Stop();
_cancellationTokenSource.Cancel();
Adapter = null;
MqttTrace.Information(nameof(MqttClientSession), $"Client '{_identifier}': Disconnected.");
} }
_cancellationTokenSource?.Cancel(false);
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
_adapter = null;
MqttNetTrace.Information(nameof(MqttClientSession), "Client '{0}': Disconnected.", ClientId);
} }
public void EnqueuePublishPacket(MqttPublishPacket publishPacket) public void EnqueuePublishPacket(MqttPublishPacket publishPacket)
@@ -89,8 +91,8 @@ namespace MQTTnet.Core.Server
return; return;
} }
_messageQueue.Enqueue(publishPacket); _pendingMessagesQueue.Enqueue(publishPacket);
MqttTrace.Verbose(nameof(MqttClientSession), $"Client '{_identifier}: Enqueued pending publish packet."); MqttNetTrace.Verbose(nameof(MqttClientSession), "Client '{0}': Enqueued pending publish packet.", ClientId);
} }
public void Dispose() public void Dispose()
@@ -99,68 +101,100 @@ namespace MQTTnet.Core.Server
_cancellationTokenSource?.Dispose(); _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) 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);
} }
else if (packet is MqttUnsubscribePacket unsubscribePacket)
if (packet is MqttUnsubscribePacket unsubscribePacket)
{ {
return Adapter.SendPacketAsync(_subscriptionsManager.Unsubscribe(unsubscribePacket), _options.DefaultCommunicationTimeout); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, _subscriptionsManager.Unsubscribe(unsubscribePacket));
} }
else if (packet is MqttPublishPacket publishPacket)
if (packet is MqttPublishPacket publishPacket)
{ {
return HandleIncomingPublishPacketAsync(publishPacket); await HandleIncomingPublishPacketAsync(adapter, publishPacket);
} }
else if (packet is MqttPubRelPacket pubRelPacket)
if (packet is MqttPubRelPacket pubRelPacket)
{ {
return HandleIncomingPubRelPacketAsync(pubRelPacket); await HandleIncomingPubRelPacketAsync(adapter, pubRelPacket);
} }
else if (packet is MqttPubRecPacket pubRecPacket)
if (packet is MqttPubRecPacket pubRecPacket)
{ {
return Adapter.SendPacketAsync(pubRecPacket.CreateResponse<MqttPubRelPacket>(), _options.DefaultCommunicationTimeout); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, pubRecPacket.CreateResponse<MqttPubRelPacket>());
} }
else if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
{ {
// Discard message. // Discard message.
return Task.FromResult((object)null);
} }
else if (packet is MqttPingReqPacket)
if (packet is MqttPingReqPacket)
{ {
return Adapter.SendPacketAsync(new MqttPingRespPacket(), _options.DefaultCommunicationTimeout); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPingRespPacket());
} }
else if (packet is MqttDisconnectPacket || packet is MqttConnectPacket)
if (packet is MqttDisconnectPacket || packet is MqttConnectPacket)
{ {
_cancellationTokenSource.Cancel(); Stop();
return Task.FromResult((object)null); }
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) if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{ {
_publishPacketReceivedCallback(this, publishPacket); _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket);
return Task.FromResult(0); return;
} }
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{ {
_publishPacketReceivedCallback(this, publishPacket); _mqttClientSessionsManager.DispatchPublishPacket(this, publishPacket);
return Adapter.SendPacketAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout); await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
return;
} }
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
@@ -171,22 +205,23 @@ namespace MQTTnet.Core.Server
_unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier); _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."); throw new MqttCommunicationException("Received a not supported QoS level.");
} }
private Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket) private Task HandleIncomingPubRelPacketAsync(IMqttCommunicationAdapter adapter, MqttPubRelPacket pubRelPacket)
{ {
lock (_unacknowledgedPublishPackets) lock (_unacknowledgedPublishPackets)
{ {
_unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier); _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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Core.Adapter; using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Diagnostics;
@@ -8,86 +9,133 @@ using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal; using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol; using MQTTnet.Core.Protocol;
using MQTTnet.Core.Serializer;
namespace MQTTnet.Core.Server namespace MQTTnet.Core.Server
{ {
public sealed class MqttClientSessionsManager public sealed class MqttClientSessionsManager
{ {
private readonly object _syncRoot = new object();
private readonly Dictionary<string, MqttClientSession> _clientSessions = new Dictionary<string, MqttClientSession>(); private readonly Dictionary<string, MqttClientSession> _clientSessions = new Dictionary<string, MqttClientSession>();
private readonly MqttServerOptions _options; private readonly MqttServerOptions _options;
public MqttClientSessionsManager(MqttServerOptions options) public MqttClientSessionsManager(MqttServerOptions options)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
RetainedMessagesManager = new MqttClientRetainedMessagesManager(options);
} }
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; 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 try
{ {
var connectPacket = await eventArgs.ClientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout) as MqttConnectPacket; if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false) is MqttConnectPacket connectPacket))
if (connectPacket == null)
{ {
throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1]."); throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
} }
// Switch to the required protocol version before sending any response. clientId = connectPacket.ClientId;
eventArgs.ClientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
// Switch to the required protocol version before sending any response.
clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
var connectReturnCode = ValidateConnection(connectPacket); var connectReturnCode = ValidateConnection(connectPacket);
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted) if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{ {
await eventArgs.ClientAdapter.SendPacketAsync(new MqttConnAckPacket await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
{ {
ConnectReturnCode = connectReturnCode ConnectReturnCode = connectReturnCode
}, _options.DefaultCommunicationTimeout); }).ConfigureAwait(false);
return; return;
} }
var clientSession = GetOrCreateClientSession(connectPacket); var clientSession = GetOrCreateClientSession(connectPacket);
await eventArgs.ClientAdapter.SendPacketAsync(new MqttConnAckPacket await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
{ {
ConnectReturnCode = connectReturnCode, ConnectReturnCode = connectReturnCode,
IsSessionPresent = clientSession.IsExistingSession 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) catch (Exception exception)
{ {
MqttTrace.Error(nameof(MqttServer), exception, exception.Message); MqttNetTrace.Error(nameof(MqttServer), exception, exception.Message);
} }
finally 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() public void Clear()
{ {
lock (_syncRoot) lock (_clientSessions)
{ {
_clientSessions.Clear(); _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 return _clientSessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
{ {
ClientId = s.Value.ClientId, ClientId = s.Value.ClientId,
ProtocolVersion = s.Value.Adapter.PacketSerializer.ProtocolVersion ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311
}).ToList(); }).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) private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
{ {
if (_options.ConnectionValidator != null) if (_options.ConnectionValidator != null)
@@ -100,10 +148,9 @@ namespace MQTTnet.Core.Server
private GetOrCreateClientSessionResult GetOrCreateClientSession(MqttConnectPacket connectPacket) private GetOrCreateClientSessionResult GetOrCreateClientSession(MqttConnectPacket connectPacket)
{ {
lock (_syncRoot) lock (_clientSessions)
{ {
MqttClientSession clientSession; var isSessionPresent = _clientSessions.TryGetValue(connectPacket.ClientId, out var clientSession);
var isSessionPresent = _clientSessions.TryGetValue(connectPacket.ClientId, out clientSession);
if (isSessionPresent) if (isSessionPresent)
{ {
if (connectPacket.CleanSession) if (connectPacket.CleanSession)
@@ -111,11 +158,11 @@ namespace MQTTnet.Core.Server
_clientSessions.Remove(connectPacket.ClientId); _clientSessions.Remove(connectPacket.ClientId);
clientSession.Dispose(); clientSession.Dispose();
clientSession = null; 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 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; isExistingSession = false;
clientSession = new MqttClientSession(connectPacket.ClientId, _options, DispatchPublishPacket); clientSession = new MqttClientSession(connectPacket.ClientId, _options, this);
_clientSessions[connectPacket.ClientId] = clientSession; _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 }; 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)); _options = options ?? throw new ArgumentNullException(nameof(options));
_adapters = adapters ?? throw new ArgumentNullException(nameof(adapters)); _adapters = adapters ?? throw new ArgumentNullException(nameof(adapters));
_clientSessionsManager = new MqttClientSessionsManager(options); _clientSessionsManager = new MqttClientSessionsManager(options);
_clientSessionsManager.ApplicationMessageReceived += (s, e) => ApplicationMessageReceived?.Invoke(s, e); _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(); return _clientSessionsManager.GetConnectedClients();
} }
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected; public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived; public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public void Publish(MqttApplicationMessage applicationMessage) public void Publish(MqttApplicationMessage applicationMessage)
@@ -41,31 +43,24 @@ namespace MQTTnet.Core.Server
_clientSessionsManager.DispatchPublishPacket(null, applicationMessage.ToPublishPacket()); _clientSessionsManager.DispatchPublishPacket(null, applicationMessage.ToPublishPacket());
} }
public void InjectClient(string identifier, IMqttCommunicationAdapter adapter) public async Task StartAsync()
{
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()
{ {
if (_cancellationTokenSource != null) throw new InvalidOperationException("The MQTT server is already started."); if (_cancellationTokenSource != null) throw new InvalidOperationException("The MQTT server is already started.");
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
await _clientSessionsManager.RetainedMessagesManager.LoadMessagesAsync();
foreach (var adapter in _adapters) foreach (var adapter in _adapters)
{ {
adapter.ClientConnected += OnClientConnected; adapter.ClientAccepted += OnClientAccepted;
adapter.Start(_options); 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?.Cancel(false);
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
@@ -73,21 +68,30 @@ namespace MQTTnet.Core.Server
foreach (var adapter in _adapters) foreach (var adapter in _adapters)
{ {
adapter.ClientConnected -= OnClientConnected; adapter.ClientAccepted -= OnClientAccepted;
adapter.Stop(); await adapter.StopAsync();
} }
_clientSessionsManager.Clear(); _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) 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); 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 MqttServerDefaultEndpointOptions DefaultEndpointOptions { get; } = new MqttServerDefaultEndpointOptions();
public MqttServerTlsEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsEndpointOptions(); public MqttServerTlsEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsEndpointOptions();
public int ConnectionBacklog { get; set; } = 10; public int ConnectionBacklog { get; set; } = 10;
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15); public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15);
public Func<MqttConnectPacket, MqttConnectReturnCode> ConnectionValidator { get; set; } 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 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26730.8 VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.NetFramework", "Frameworks\MQTTnet.NetFramework\MQTTnet.NetFramework.csproj", "{A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.NetFramework", "Frameworks\MQTTnet.NetFramework\MQTTnet.NetFramework.csproj", "{A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}"
EndProject EndProject
@@ -32,6 +32,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md README.md = README.md
EndProjectSection EndProjectSection
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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|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.ActiveCfg = Debug|Any CPU
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|ARM.Build.0 = 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.ActiveCfg = Debug|Any CPU
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x64.Build.0 = Debug|x64 {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x64.Build.0 = Debug|Any CPU
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x86.ActiveCfg = Debug|x86 {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x86.ActiveCfg = Debug|Any CPU
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Debug|x86.Build.0 = Debug|x86 {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.ActiveCfg = Release|Any CPU
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|ARM.Build.0 = 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.ActiveCfg = Release|Any CPU
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.Build.0 = Release|x64 {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x64.Build.0 = Release|Any CPU
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.ActiveCfg = Release|x86 {A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.ActiveCfg = Release|Any CPU
{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}.Release|x86.Build.0 = Release|x86 {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.ActiveCfg = Debug|Any CPU
{A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}.Debug|Any CPU.Build.0 = 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 {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|x64.Build.0 = Release|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|x86.ActiveCfg = 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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -173,6 +191,7 @@ Global
{D9D74F33-6943-49B2-B765-7BD589082098} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4} {D9D74F33-6943-49B2-B765-7BD589082098} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
{FF1F72D6-9524-4422-9497-3CC0002216ED} = {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} {3587E506-55A2-4EB3-99C7-DC01E42D25D2} = {32A630A7-2598-41D7-B625-204CD906F5FB}
{3D283AAD-AAA8-4339-8394-52F80B6304DB} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
+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> </p>
[![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet) [![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet)
[![BCH compliance](https://bettercodehub.com/edge/badge/chkr1011/MQTTnet?branch=master)](https://bettercodehub.com/)
# MQTTnet # 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/. 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 # Features
## General ### General
* Async support * Async support
* TLS 1.2 support for client and server (but not UWP servers) * TLS 1.2 support for client and server (but not UWP servers)
* Extensible communication channels (i.e. In-Memory, TCP, TCP+SSL, WebSockets (not included in this project)) * Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS)
* Interfaces included for mocking and testing
* Lightweight (only the low level implementation of MQTT, no overhead) * Lightweight (only the low level implementation of MQTT, no overhead)
* Performance optimized (processing ~27.000 messages / second)*
* Interfaces included for mocking and testing
* Access to internal trace messages * 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) * Rx support (via another project)
* Communication via TCP (+TLS) or WS (WebSocket)
## Server (broker) ### Server (broker)
* List of connected clients available * List of connected clients available
* Supports connected clients with different protocol versions at the same time * Supports connected clients with different protocol versions at the same time
* Able to publish its own messages (no loopback client required) * Able to publish its own messages (no loopback client required)
* Able to receive every messages (no loopback client required) * Able to receive every messages (no loopback client required)
* Extensible client credential validation * Extensible client credential validation
* Retained messages are supported including persisting via interface methods (own implementation required)
# Supported frameworks # Supported frameworks
* .NET Standard 1.3+ * .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 Core App 1.1+
* .NET Framework 4.5.2+ (x86, x64, AnyCPU) * .NET Framework 4.5.2+ (x86, x64, AnyCPU)
* Universal Windows (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU) * Universal Windows (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU)
* Mono 5.2+
# Supported MQTT versions # Supported MQTT versions
* 3.1.1 * 3.1.1
@@ -42,6 +49,9 @@ MQTTnet is a .NET library for MQTT based communication. It provides a MQTT clien
# Nuget # Nuget
This library is available as a nuget package: https://www.nuget.org/packages/MQTTnet/ This library is available as a nuget package: https://www.nuget.org/packages/MQTTnet/
# Examples
Please find examples and the documentation at the Wiki of this repository (https://github.com/chkr1011/MQTTnet/wiki).
# Contributions # Contributions
If you want to contribute to this project just create a pull request. If you want to contribute to this project just create a pull request.
@@ -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) * 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) * 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. 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();
```
@@ -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 Sdk="Microsoft.NET.Sdk">
<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')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <OutputType>Exe</OutputType>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <DebugType>Full</DebugType>
<ProjectGuid>{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}</ProjectGuid> <TargetFramework>netcoreapp2.0</TargetFramework>
<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>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> <PackageReference Include="MSTest.TestAdapter" Version="1.1.18" />
<Reference Include="System" /> <PackageReference Include="MSTest.TestFramework" Version="1.1.18" />
<Reference Include="System.Core" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ByteReaderTests.cs" /> <ProjectReference Include="..\..\Frameworks\MQTTnet.Netstandard\MQTTnet.NetStandard.csproj" />
<Compile Include="ByteWriterTests.cs" /> <ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj" />
<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" />
</ItemGroup> </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> </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;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Channel; using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol; using MQTTnet.Core.Protocol;
using MQTTnet.Core.Serializer; using MQTTnet.Core.Serializer;
@@ -391,6 +392,12 @@ namespace MQTTnet.Core.Tests
{ {
private readonly MemoryStream _stream = new MemoryStream(); private readonly MemoryStream _stream = new MemoryStream();
public Stream ReceiveStream => _stream;
public Stream RawReceiveStream => _stream;
public Stream SendStream => _stream;
public bool IsConnected { get; } = true; public bool IsConnected { get; } = true;
public TestChannel() public TestChannel()
@@ -403,24 +410,14 @@ namespace MQTTnet.Core.Tests
_stream.Position = 0; _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); return 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);
} }
public byte[] ToArray() 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 serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion };
var channel = new TestChannel(); var buffer = serializer.Serialize(packet);
serializer.SerializeAsync(packet, channel).Wait();
var buffer = channel.ToArray();
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(buffer)); 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 serializer = new MqttPacketSerializer();
var channel1 = new TestChannel(); var buffer1 = serializer.Serialize(packet);
serializer.SerializeAsync(packet, channel1).Wait();
var buffer1 = channel1.ToArray();
var channel2 = new TestChannel(buffer1); using (var headerStream = new MemoryStream(buffer1))
var deserializedPacket = serializer.DeserializeAsync(channel2).Result; {
var buffer2 = channel2.ToArray(); var header = MqttPacketReader.ReadHeaderFromSource(headerStream, CancellationToken.None);
var channel3 = new TestChannel(buffer2); using (var bodyStream = new MemoryStream(buffer1, (int)headerStream.Position, header.BodyLength))
serializer.SerializeAsync(deserializedPacket, channel3).Wait(); {
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 public class MqttServerTests
{ {
[TestMethod] [TestMethod]
public async Task MqttServer_PublishSimple_AtMostOnce() public void MqttServer_PublishSimple_AtMostOnce()
{ {
await TestPublishAsync( TestPublishAsync(
"A/B/C", "A/B/C",
MqttQualityOfServiceLevel.AtMostOnce, MqttQualityOfServiceLevel.AtMostOnce,
"A/B/C", "A/B/C",
MqttQualityOfServiceLevel.AtMostOnce, MqttQualityOfServiceLevel.AtMostOnce,
1); 1).Wait();
} }
[TestMethod] [TestMethod]
public async Task MqttServer_PublishSimple_AtLeastOnce() public void MqttServer_PublishSimple_AtLeastOnce()
{ {
await TestPublishAsync( TestPublishAsync(
"A/B/C", "A/B/C",
MqttQualityOfServiceLevel.AtLeastOnce, MqttQualityOfServiceLevel.AtLeastOnce,
"A/B/C", "A/B/C",
MqttQualityOfServiceLevel.AtLeastOnce, MqttQualityOfServiceLevel.AtLeastOnce,
1); 1).Wait();
} }
[TestMethod] [TestMethod]
public async Task MqttServer_PublishSimple_ExactlyOnce() public void MqttServer_PublishSimple_ExactlyOnce()
{ {
await TestPublishAsync( TestPublishAsync(
"A/B/C", "A/B/C",
MqttQualityOfServiceLevel.ExactlyOnce, MqttQualityOfServiceLevel.ExactlyOnce,
"A/B/C", "A/B/C",
MqttQualityOfServiceLevel.ExactlyOnce, MqttQualityOfServiceLevel.ExactlyOnce,
1); 1).Wait();
} }
[TestMethod] [TestMethod]
public async Task MqttServer_WillMessage() public async Task MqttServer_WillMessage()
{ {
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); var serverAdapter = new TestMqttServerAdapter();
s.Start(); 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 willMessage = new MqttApplicationMessage("My/last/will", new byte[0], MqttQualityOfServiceLevel.AtMostOnce, false);
var c1 = ConnectTestClient("c1", null, s); var c1 = await serverAdapter.ConnectTestClient(s, "c1");
var c2 = ConnectTestClient("c2", willMessage, s); var c2 = await serverAdapter.ConnectTestClient(s, "c2", willMessage);
var receivedMessagesCount = 0; var receivedMessagesCount = 0;
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
@@ -63,7 +64,7 @@ namespace MQTTnet.Core.Tests
await Task.Delay(1000); await Task.Delay(1000);
s.Stop(); await s.StopAsync();
Assert.AreEqual(1, receivedMessagesCount); Assert.AreEqual(1, receivedMessagesCount);
} }
@@ -71,15 +72,16 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public async Task MqttServer_Unsubscribe() public async Task MqttServer_Unsubscribe()
{ {
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); var serverAdapter = new TestMqttServerAdapter();
s.Start(); 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 c2 = ConnectTestClient("c2", null, s); var c2 = await serverAdapter.ConnectTestClient(s, "c2");
var receivedMessagesCount = 0; var receivedMessagesCount = 0;
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
var message = new MqttApplicationMessage("a", new byte[0], MqttQualityOfServiceLevel.AtLeastOnce, false); var message = new MqttApplicationMessage("a", new byte[0], MqttQualityOfServiceLevel.AtLeastOnce, false);
await c2.PublishAsync(message); await c2.PublishAsync(message);
@@ -91,13 +93,13 @@ namespace MQTTnet.Core.Tests
await Task.Delay(500); await Task.Delay(500);
Assert.AreEqual(1, receivedMessagesCount); Assert.AreEqual(1, receivedMessagesCount);
await c1.Unsubscribe("a"); await c1.UnsubscribeAsync("a");
await c2.PublishAsync(message); await c2.PublishAsync(message);
await Task.Delay(500); await Task.Delay(500);
Assert.AreEqual(1, receivedMessagesCount); Assert.AreEqual(1, receivedMessagesCount);
s.Stop(); await s.StopAsync();
await Task.Delay(500); await Task.Delay(500);
Assert.AreEqual(1, receivedMessagesCount); Assert.AreEqual(1, receivedMessagesCount);
@@ -106,10 +108,11 @@ namespace MQTTnet.Core.Tests
[TestMethod] [TestMethod]
public async Task MqttServer_Publish() public async Task MqttServer_Publish()
{ {
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); var serverAdapter = new TestMqttServerAdapter();
s.Start(); 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; var receivedMessagesCount = 0;
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
@@ -120,36 +123,140 @@ namespace MQTTnet.Core.Tests
s.Publish(message); s.Publish(message);
await Task.Delay(500); await Task.Delay(500);
s.Stop(); await s.StopAsync();
Assert.AreEqual(1, receivedMessagesCount); 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 serverAdapter = new TestMqttServerAdapter();
var adapterB = new TestMqttCommunicationAdapter(); var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { serverAdapter });
adapterA.Partner = adapterB; await s.StartAsync();
adapterB.Partner = adapterA;
var client = new MqttClient(new MqttClientOptions(), adapterA); var c1 = await serverAdapter.ConnectTestClient(s, "c1");
server.InjectClient(clientId, adapterB); await c1.PublishAsync(new MqttApplicationMessage("retained", new byte[3], MqttQualityOfServiceLevel.AtLeastOnce, false));
client.ConnectAsync(willMessage).Wait(); await c1.DisconnectAsync();
return client;
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, string topic,
MqttQualityOfServiceLevel qualityOfServiceLevel, MqttQualityOfServiceLevel qualityOfServiceLevel,
string topicFilter, string topicFilter,
MqttQualityOfServiceLevel filterQualityOfServiceLevel, MqttQualityOfServiceLevel filterQualityOfServiceLevel,
int expectedReceivedMessagesCount) int expectedReceivedMessagesCount)
{ {
var s = new MqttServer(new MqttServerOptions(), new List<IMqttServerAdapter> { new TestMqttServerAdapter() }); var serverAdapter = new TestMqttServerAdapter();
s.Start(); 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 c2 = ConnectTestClient("c2", null, s); var c2 = await serverAdapter.ConnectTestClient(s, "c2");
var receivedMessagesCount = 0; var receivedMessagesCount = 0;
c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++; c1.ApplicationMessageReceived += (_, __) => receivedMessagesCount++;
@@ -158,11 +265,11 @@ namespace MQTTnet.Core.Tests
await c2.PublishAsync(new MqttApplicationMessage(topic, new byte[0], qualityOfServiceLevel, false)); await c2.PublishAsync(new MqttApplicationMessage(topic, new byte[0], qualityOfServiceLevel, false));
await Task.Delay(500); await Task.Delay(500);
await c1.Unsubscribe(topicFilter); await c1.UnsubscribeAsync(topicFilter);
await Task.Delay(500); await Task.Delay(500);
s.Stop(); await s.StopAsync();
Assert.AreEqual(expectedReceivedMessagesCount, receivedMessagesCount); 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;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Core.Adapter; using MQTTnet.Core.Adapter;
using MQTTnet.Core.Client;
using MQTTnet.Core.Packets; using MQTTnet.Core.Packets;
using MQTTnet.Core.Serializer; using MQTTnet.Core.Serializer;
@@ -16,29 +17,33 @@ namespace MQTTnet.Core.Tests
public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer(); 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(); ThrowIfPartnerIsNull();
Partner.SendPacketInternal(packet); foreach (var packet in packets)
await Task.FromResult(0); {
Partner.SendPacketInternal(packet);
}
return Task.FromResult(0);
} }
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout) public Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
{ {
ThrowIfPartnerIsNull(); ThrowIfPartnerIsNull();
return await Task.Run(() => _incomingPackets.Take()); return Task.Run(() => _incomingPackets.Take(), cancellationToken);
} }
private void SendPacketInternal(MqttBasePacket packet) private void SendPacketInternal(MqttBasePacket packet)
@@ -1,24 +1,63 @@
using System; using System;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter; using MQTTnet.Core.Adapter;
using MQTTnet.Core.Server; using MQTTnet.Core.Server;
using MQTTnet.Core.Client;
namespace MQTTnet.Core.Tests namespace MQTTnet.Core.Tests
{ {
public class TestMqttServerAdapter : IMqttServerAdapter 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" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="PerformanceTest.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
@@ -90,7 +91,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Frameworks\MQTTnet.NetFramework\MQTTnet.NetFramework.csproj"> <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> <Name>MQTTnet.NetFramework</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj"> <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.Packets;
using MQTTnet.Core.Protocol; using MQTTnet.Core.Protocol;
using MQTTnet.Core.Server; using MQTTnet.Core.Server;
using MQTTnet.Implementations;
namespace MQTTnet.TestApp.NetFramework namespace MQTTnet.TestApp.NetFramework
{ {
@@ -19,6 +20,7 @@ namespace MQTTnet.TestApp.NetFramework
Console.WriteLine("MQTTnet - TestApp.NetFramework"); Console.WriteLine("MQTTnet - TestApp.NetFramework");
Console.WriteLine("1 = Start client"); Console.WriteLine("1 = Start client");
Console.WriteLine("2 = Start server"); Console.WriteLine("2 = Start server");
Console.WriteLine("3 = Start performance test");
var pressedKey = Console.ReadKey(true); var pressedKey = Console.ReadKey(true);
if (pressedKey.Key == ConsoleKey.D1) if (pressedKey.Key == ConsoleKey.D1)
{ {
@@ -30,11 +32,17 @@ namespace MQTTnet.TestApp.NetFramework
Task.Run(() => RunServerAsync(args)); Task.Run(() => RunServerAsync(args));
Thread.Sleep(Timeout.Infinite); 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) 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}"); Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}");
if (e.Exception != null) if (e.Exception != null)
@@ -45,15 +53,20 @@ namespace MQTTnet.TestApp.NetFramework
try try
{ {
var options = new MqttClientOptions var options = new MqttClientWebSocketOptions
{ {
Server = "localhost", Uri = "broker.hivemq.com:8000/mqtt"
ClientId = "XYZ",
CleanSession = true
}; };
var client = new MqttClientFactory().CreateMqttClient(options); ////var options = new MqttClientOptions
client.ApplicationMessageReceived += (s, e) => ////{
//// Server = "localhost",
//// ClientId = "XYZ",
//// CleanSession = true
////};
var mqttClient = new MqttClientFactory().CreateMqttClient();
mqttClient.ApplicationMessageReceived += (s, e) =>
{ {
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
@@ -63,26 +76,24 @@ namespace MQTTnet.TestApp.NetFramework
Console.WriteLine(); Console.WriteLine();
}; };
client.Connected += async (s, e) => mqttClient.Connected += async (s, e) =>
{ {
Console.WriteLine("### CONNECTED WITH SERVER ###"); Console.WriteLine("### CONNECTED WITH SERVER ###");
await client.SubscribeAsync(new List<TopicFilter> await mqttClient.SubscribeAsync(new List<TopicFilter>
{ {
new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce) new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)
}); });
Console.WriteLine("### SUBSCRIBED ###");
}; };
client.Disconnected += async (s, e) => mqttClient.Disconnected += async (s, e) =>
{ {
Console.WriteLine("### DISCONNECTED FROM SERVER ###"); Console.WriteLine("### DISCONNECTED FROM SERVER ###");
await Task.Delay(TimeSpan.FromSeconds(5)); await Task.Delay(TimeSpan.FromSeconds(5));
try try
{ {
await client.ConnectAsync(); await mqttClient.ConnectAsync(options);
} }
catch catch
{ {
@@ -92,7 +103,7 @@ namespace MQTTnet.TestApp.NetFramework
try try
{ {
await client.ConnectAsync(); await mqttClient.ConnectAsync(options);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -101,18 +112,13 @@ namespace MQTTnet.TestApp.NetFramework
Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###"); Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###");
var messageFactory = new MqttApplicationMessageFactory();
while (true) while (true)
{ {
Console.ReadLine(); Console.ReadLine();
var applicationMessage = new MqttApplicationMessage( var applicationMessage = messageFactory.CreateApplicationMessage("myTopic", "Hello World", MqttQualityOfServiceLevel.AtLeastOnce);
"A/B/C", await mqttClient.PublishAsync(applicationMessage);
Encoding.UTF8.GetBytes("Hello World"),
MqttQualityOfServiceLevel.AtLeastOnce,
false
);
await client.PublishAsync(applicationMessage);
} }
} }
catch (Exception exception) 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}"); Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}");
if (e.Exception != null) if (e.Exception != null)
@@ -151,12 +157,12 @@ namespace MQTTnet.TestApp.NetFramework
}; };
var mqttServer = new MqttServerFactory().CreateMqttServer(options); var mqttServer = new MqttServerFactory().CreateMqttServer(options);
mqttServer.Start(); await mqttServer.StartAsync();
Console.WriteLine("Press any key to exit."); Console.WriteLine("Press any key to exit.");
Console.ReadLine(); Console.ReadLine();
mqttServer.Stop(); await mqttServer.StopAsync();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -165,5 +171,19 @@ namespace MQTTnet.TestApp.NetFramework
Console.ReadLine(); 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" x:Class="MQTTnet.TestApp.UniversalWindows.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MQTTnet.TestApp.UniversalWindows"
RequestedTheme="Light"> RequestedTheme="Light">
</Application> </Application>

Some files were not shown because too many files have changed in this diff Show More