Compare commits

..
118 Commits
Author SHA1 Message Date
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 96b92aabae Update version information 2017-08-21 20:27:39 +02:00
Christian Kratky d87dab2e25 Fixed typos 2017-08-21 20:24:16 +02:00
Christian b2f9d937e1 Merge pull request #28 from rydergillen-compacSort/master
Tls EndPoint uses DefaultSocket
2017-08-21 20:22:01 +02:00
Ryder Gillen c9ac954bfd Tls endpoint using wrong socket 2017-08-21 09:00:51 -07:00
rydergillen-compacSort dea2da52f9 Merge pull request #1 from chkr1011/master
v2.2.0 Merge
2017-08-21 08:04:02 -07:00
Christian Kratky 487a97c31e Merge branch 'master' into develop 2017-08-17 22:53:15 +02:00
Christian Kratky c90bdbfb93 Fix issue with connection management. 2017-08-17 22:49:16 +02:00
Christian Kratky 598ed66a3b Add support for V3.1.0; Performance improvements 2017-08-13 19:46:32 +02:00
89 changed files with 3098 additions and 1625 deletions
+8
View File
@@ -0,0 +1,8 @@
exclude:
- /Frameworks/.*
- /MQTTnet.TestApp.NetCore/Program.cs
- /Tests/MQTTnet.TestApp.NetFramework/.*
- /Tests/MQTTnet.TestApp.UniversalWindows/.*
component_depth: 1
languages:
- csharp
+2
View File
@@ -286,4 +286,6 @@ __pycache__/
*.btm.cs
*.odx.cs
*.xsd.cs
Build/nuget.exe
+10
View File
@@ -0,0 +1,10 @@
language: csharp
solution: MQTTnet.sln
matrix:
include:
- dotnet: 2.0.0
mono: none
dist: trusty
env: DOTNETCORE=1 # optional, can be used to take different code paths in your script
- mono: latest
+8 -5
View File
@@ -2,7 +2,7 @@
<package >
<metadata>
<id>MQTTnet</id>
<version>2.1.5.1</version>
<version>2.3.1</version>
<authors>Christian Kratky</authors>
<owners>Christian Kratky</owners>
<licenseUrl>https://github.com/chkr1011/MQTTnet/blob/master/LICENSE</licenseUrl>
@@ -10,19 +10,22 @@
<iconUrl>https://raw.githubusercontent.com/chkr1011/MQTTnet/master/Images/Logo_128x128.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker).</description>
<releaseNotes>* [Server] Fixed wrong handling of QoS levels
<releaseNotes>* [Server] Fixed an issue when accepting a new connection (UWP only) (Thanks to haeberle)
[Core] Fixed a dead lock while sending messages (Thanks to 1liveowl, JanEggers)
[Client] The client is no longer sending packets before receiving has started
[Core] Minor changes and improvements
</releaseNotes>
<copyright>Copyright Christian Kratky 2016-2017</copyright>
<tags>MQTT MQTTClient MQTTServer MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Queue Hardware</tags>
<tags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M</tags>
<dependencies>
<group targetFramework="netstandard1.3">
<dependency id="NETStandard.Library" version="1.6.1" />
<dependency id="System.Net.Security" version="4.3.1" />
<dependency id="System.Net.Security" version="4.3.2" />
</group>
<group targetFramework="uap10.0">
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.3.3" />
<dependency id="Microsoft.NETCore.UniversalWindowsPlatform" version="5.4.0" />
</group>
</dependencies>
@@ -85,13 +85,18 @@ namespace MQTTnet.Implementations
{
try
{
var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null);
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttV311PacketSerializer());
var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null).ConfigureAwait(false);
var tcpChannel = new MqttTcpChannel(clientSocket, null);
var clientAdapter = new MqttChannelCommunicationAdapter(tcpChannel, new MqttPacketSerializer());
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
}
catch (Exception exception) when (!(exception is ObjectDisposedException))
{
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while acceping connection at default endpoint.");
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
//excessive CPU consumed if in endless loop of socket errors
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
}
}
}
@@ -102,17 +107,21 @@ namespace MQTTnet.Implementations
{
try
{
var clientSocket = await Task.Factory.FromAsync(_defaultEndpointSocket.BeginAccept, _defaultEndpointSocket.EndAccept, null);
var clientSocket = await Task.Factory.FromAsync(_tlsEndpointSocket.BeginAccept, _tlsEndpointSocket.EndAccept, null).ConfigureAwait(false);
var sslStream = new SslStream(new NetworkStream(clientSocket));
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false);
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttV311PacketSerializer());
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
var tcpChannel = new MqttTcpChannel(clientSocket, sslStream);
var clientAdapter = new MqttChannelCommunicationAdapter(tcpChannel, new MqttPacketSerializer());
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while acceping connection at TLS endpoint.");
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint.");
//excessive CPU consumed if in endless loop of socket errors
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
}
}
}
@@ -6,108 +6,95 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Core.Exceptions;
using System.IO;
namespace MQTTnet.Implementations
{
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
{
private readonly Socket _socket;
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
private Socket _socket;
private SslStream _sslStream;
/// <summary>
/// called on client sockets are created in connect
/// </summary>
public MqttTcpChannel()
{
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
}
/// <summary>
/// called on server, sockets are passed in
/// connect will not be called
/// </summary>
public MqttTcpChannel(Socket socket, SslStream sslStream)
{
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
_sslStream = sslStream;
CreateStreams(socket, sslStream);
}
public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }
public Stream RawReceiveStream { get; private set; }
public async Task ConnectAsync(MqttClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
try
{
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)
if (_socket == null)
{
throw new MqttCommunicationException(exception);
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
}
await Task.Factory.FromAsync(_socket.BeginConnect, _socket.EndConnect, options.Server, options.GetPort(), null).ConfigureAwait(false);
if (options.TlsOptions.UseTls)
{
_sslStream = new SslStream(new NetworkStream(_socket, true));
await _sslStream.AuthenticateAsClientAsync(options.Server, LoadCertificates(options), SslProtocols.Tls12, options.TlsOptions.CheckCertificateRevocation).ConfigureAwait(false);
}
CreateStreams(_socket, _sslStream);
}
public Task DisconnectAsync()
{
try
{
_sslStream.Dispose();
_socket.Dispose();
return Task.FromResult(0);
}
catch (SocketException exception)
{
throw new MqttCommunicationException(exception);
}
}
public Task WriteAsync(byte[] buffer)
{
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
try
{
if (_sslStream != null)
{
return _sslStream.WriteAsync(buffer, 0, buffer.Length);
}
return Task.Factory.FromAsync(
// ReSharper disable once AssignNullToNotNullAttribute
_socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, null, null),
_socket.EndSend);
}
catch (SocketException exception)
{
throw new MqttCommunicationException(exception);
}
}
public Task ReadAsync(byte[] buffer)
{
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
try
{
if (_sslStream != null)
{
return _sslStream.ReadAsync(buffer, 0, buffer.Length);
}
return Task.Factory.FromAsync(
// ReSharper disable once AssignNullToNotNullAttribute
_socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, null, null),
_socket.EndReceive);
}
catch (SocketException exception)
{
throw new MqttCommunicationException(exception);
}
Dispose();
return Task.FromResult(0);
}
public void Dispose()
{
RawReceiveStream?.Dispose();
RawReceiveStream = null;
ReceiveStream?.Dispose();
ReceiveStream = null;
SendStream?.Dispose();
SendStream = null;
_socket?.Dispose();
_socket = null;
_sslStream?.Dispose();
_sslStream = null;
}
private void CreateStreams(Socket socket, Stream sslStream)
{
RawReceiveStream = sslStream ?? new NetworkStream(socket);
//cannot use this as default buffering prevents from receiving the first connect message
//need two streams otherwise read and write have to be synchronized
SendStream = new BufferedStream(RawReceiveStream, BufferSize);
ReceiveStream = new BufferedStream(RawReceiveStream, BufferSize);
}
private static X509CertificateCollection LoadCertificates(MqttClientOptions options)
@@ -0,0 +1,55 @@
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 ClientWebSocket _webSocket;
public Stream RawReceiveStream { get; private set; }
public Stream SendStream => RawReceiveStream;
public Stream ReceiveStream => RawReceiveStream;
public async Task ConnectAsync(MqttClientOptions options)
{
var uri = options.Server;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase))
{
uri = "ws://" + uri;
}
if (options.Port.HasValue)
{
uri += ":" + options.Port;
}
_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,8 @@
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Implementations\MqttWebSocketChannel.cs" />
<Compile Include="Implementations\WebSocketStream.cs" />
<Compile Include="MqttClientFactory.cs" />
<Compile Include="MqttServerFactory.cs" />
<Compile Include="Implementations\MqttServerAdapter.cs" />
@@ -1,18 +1,33 @@
using System;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Core.Serializer;
using MQTTnet.Implementations;
namespace MQTTnet
{
public class MqttClientFactory
public class MqttClientFactory : IMqttClientFactory
{
public IMqttClient CreateMqttClient(MqttClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttV311PacketSerializer()));
return new MqttClient(options, new MqttChannelCommunicationAdapter(GetMqttCommunicationChannel(options), new MqttPacketSerializer()));
}
private static IMqttCommunicationChannel GetMqttCommunicationChannel(MqttClientOptions options)
{
switch (options.ConnectionType)
{
case MqttConnectionType.Tcp:
return new MqttTcpChannel();
case MqttConnectionType.Ws:
return new MqttWebSocketChannel();
default:
throw new NotSupportedException();
}
}
}
}
@@ -6,7 +6,7 @@ using MQTTnet.Implementations;
namespace MQTTnet
{
public class MqttServerFactory
public class MqttServerFactory : IMqttServerFactory
{
public IMqttServer CreateMqttServer(MqttServerOptions options)
{
@@ -11,5 +11,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("a480ef90-0eaa-4d9a-b271-47a9c47f6f7d")]
[assembly: AssemblyVersion("2.1.5.1")]
[assembly: AssemblyFileVersion("2.1.5.1")]
[assembly: AssemblyVersion("2.3.1.0")]
[assembly: AssemblyFileVersion("2.3.1.0")]
@@ -27,6 +27,7 @@ namespace MQTTnet.Implementations
public void Start(MqttServerOptions options)
{
if (_isRunning) throw new InvalidOperationException("Server is already started.");
_isRunning = true;
_cancellationTokenSource = new CancellationTokenSource();
@@ -83,13 +84,16 @@ namespace MQTTnet.Implementations
{
try
{
var clientSocket = await _defaultEndpointSocket.AcceptAsync();
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttV311PacketSerializer());
var clientSocket = await _defaultEndpointSocket.AcceptAsync().ConfigureAwait(false);
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, null), new MqttPacketSerializer());
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while acceping connection at default endpoint.");
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
//excessive CPU consumed if in endless loop of socket errors
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
}
}
}
@@ -100,17 +104,20 @@ namespace MQTTnet.Implementations
{
try
{
var clientSocket = await _defaultEndpointSocket.AcceptAsync();
var clientSocket = await _tlsEndpointSocket.AcceptAsync().ConfigureAwait(false);
var sslStream = new SslStream(new NetworkStream(clientSocket));
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false);
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttV311PacketSerializer());
await sslStream.AuthenticateAsServerAsync(_tlsCertificate, false, SslProtocols.Tls12, false).ConfigureAwait(false);
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(clientSocket, sslStream), new MqttPacketSerializer());
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(clientSocket.RemoteEndPoint.ToString(), clientAdapter));
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while acceping connection at TLS endpoint.");
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at TLS endpoint.");
//excessive CPU consumed if in endless loop of socket errors
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
}
}
}
@@ -6,101 +6,73 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Core.Exceptions;
using System.IO;
namespace MQTTnet.Implementations
{
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
{
private readonly Socket _socket;
private Socket _socket;
private SslStream _sslStream;
/// <summary>
/// called on client sockets are created in connect
/// </summary>
public MqttTcpChannel()
{
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
}
/// <summary>
/// called on server, sockets are passed in
/// connect will not be called
/// </summary>
public MqttTcpChannel(Socket socket, SslStream sslStream)
{
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
_sslStream = sslStream;
ReceiveStream = (Stream)sslStream ?? new NetworkStream(socket);
}
public Stream SendStream => ReceiveStream;
public Stream ReceiveStream { get; private set; }
public Stream RawReceiveStream => ReceiveStream;
public async Task ConnectAsync(MqttClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
try
if (_socket == null)
{
await _socket.ConnectAsync(options.Server, options.GetPort());
if (options.TlsOptions.UseTls)
{
_sslStream = new SslStream(new NetworkStream(_socket, true));
await _sslStream.AuthenticateAsClientAsync(options.Server, LoadCertificates(options), SslProtocols.Tls12, options.TlsOptions.CheckCertificateRevocation);
}
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
}
catch (SocketException exception)
await _socket.ConnectAsync(options.Server, options.GetPort()).ConfigureAwait(false);
if (options.TlsOptions.UseTls)
{
throw new MqttCommunicationException(exception);
_sslStream = new SslStream(new NetworkStream(_socket, true));
ReceiveStream = _sslStream;
await _sslStream.AuthenticateAsClientAsync(options.Server, LoadCertificates(options), SslProtocols.Tls12, options.TlsOptions.CheckCertificateRevocation).ConfigureAwait(false);
}
else
{
ReceiveStream = new NetworkStream(_socket);
}
}
public Task DisconnectAsync()
{
try
{
_sslStream.Dispose();
_socket.Dispose();
return Task.FromResult(0);
}
catch (SocketException exception)
{
throw new MqttCommunicationException(exception);
}
}
public Task WriteAsync(byte[] buffer)
{
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
try
{
if (_sslStream != null)
{
return _sslStream.WriteAsync(buffer, 0, buffer.Length);
}
return _socket.SendAsync(new ArraySegment<byte>(buffer), SocketFlags.None);
}
catch (SocketException exception)
{
throw new MqttCommunicationException(exception);
}
}
public Task ReadAsync(byte[] buffer)
{
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
try
{
if (_sslStream != null)
{
return _sslStream.ReadAsync(buffer, 0, buffer.Length);
}
return _socket.ReceiveAsync(new ArraySegment<byte>(buffer), SocketFlags.None);
}
catch (SocketException exception)
{
throw new MqttCommunicationException(exception);
}
Dispose();
return Task.FromResult(0);
}
public void Dispose()
{
_socket?.Dispose();
_socket = null;
_sslStream?.Dispose();
_sslStream = null;
}
private static X509CertificateCollection LoadCertificates(MqttClientOptions options)
@@ -0,0 +1,55 @@
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 ClientWebSocket _webSocket;
public Stream SendStream => RawReceiveStream;
public Stream ReceiveStream => RawReceiveStream;
public Stream RawReceiveStream { get; private set; }
public async Task ConnectAsync(MqttClientOptions options)
{
var uri = options.Server;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase))
{
uri = "ws://" + uri;
}
if (options.Port.HasValue)
{
uri += ":" + options.Port;
}
_webSocket = new ClientWebSocket();
_webSocket.Options.KeepAliveInterval = options.KeepAlivePeriod;
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);
RawReceiveStream = new WebSocketStream(_webSocket);
}
public async Task DisconnectAsync()
{
RawReceiveStream = null;
if (_webSocket == null)
{
return;
}
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false);
}
public void Dispose()
{
_webSocket?.Dispose();
}
}
}
@@ -0,0 +1,79 @@
using System;
using System.IO;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Implementations
{
public class WebSocketStream : Stream
{
private readonly ClientWebSocket _webSocket;
public WebSocketStream(ClientWebSocket webSocket)
{
_webSocket = webSocket;
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush()
{
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var currentOffset = offset;
var targetOffset = offset + count;
while (_webSocket.State == WebSocketState.Open && currentOffset < targetOffset)
{
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, currentOffset, count), cancellationToken).ConfigureAwait(false);
currentOffset += response.Count;
if (response.MessageType == WebSocketMessageType.Close)
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
}
}
return currentOffset - offset;
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
}
public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
}
}
@@ -4,8 +4,8 @@
<TargetFramework>netstandard1.3</TargetFramework>
<AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace>
<AssemblyVersion>2.1.5.1</AssemblyVersion>
<FileVersion>2.1.5.1</FileVersion>
<AssemblyVersion>2.3.1.0</AssemblyVersion>
<FileVersion>2.3.1.0</FileVersion>
<Version>0.0.0.0</Version>
<Company />
<Product />
@@ -23,7 +23,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Net.Security" Version="4.3.1" />
<PackageReference Include="System.Net.Security" Version="4.3.2" />
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.1" />
<PackageReference Include="System.Threading.Thread" Version="4.3.0" />
</ItemGroup>
</Project>
@@ -1,18 +1,33 @@
using System;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Core.Serializer;
using MQTTnet.Implementations;
namespace MQTTnet
{
public class MqttClientFactory
public class MqttClientFactory : IMqttClientFactory
{
public IMqttClient CreateMqttClient(MqttClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttV311PacketSerializer()));
return new MqttClient(options, new MqttChannelCommunicationAdapter(GetMqttCommunicationChannel(options), new MqttPacketSerializer()));
}
private static IMqttCommunicationChannel GetMqttCommunicationChannel(MqttClientOptions options)
{
switch (options.ConnectionType)
{
case MqttConnectionType.Tcp:
return new MqttTcpChannel();
case MqttConnectionType.Ws:
return new MqttWebSocketChannel();
default:
throw new NotSupportedException();
}
}
}
}
}
@@ -6,7 +6,7 @@ using MQTTnet.Implementations;
namespace MQTTnet
{
public class MqttServerFactory
public class MqttServerFactory : IMqttServerFactory
{
public IMqttServer CreateMqttServer(MqttServerOptions options)
{
@@ -10,7 +10,7 @@ namespace MQTTnet.Implementations
public class MqttServerAdapter : IMqttServerAdapter, IDisposable
{
private StreamSocketListener _defaultEndpointSocket;
private bool _isRunning;
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
@@ -20,13 +20,14 @@ namespace MQTTnet.Implementations
if (options == null) throw new ArgumentNullException(nameof(options));
if (_isRunning) throw new InvalidOperationException("Server is already started.");
_isRunning = true;
if (options.DefaultEndpointOptions.IsEnabled)
{
_defaultEndpointSocket = new StreamSocketListener();
_defaultEndpointSocket.BindServiceNameAsync(options.GetDefaultEndpointPort().ToString(), SocketProtectionLevel.PlainSocket).GetAwaiter().GetResult();
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync;
_defaultEndpointSocket.ConnectionReceived += AcceptDefaultEndpointConnectionsAsync;
}
if (options.TlsEndpointOptions.IsEnabled)
@@ -52,12 +53,12 @@ namespace MQTTnet.Implementations
{
try
{
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(args.Socket), new MqttV311PacketSerializer());
var clientAdapter = new MqttChannelCommunicationAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer());
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(args.Socket.Information.RemoteAddress.ToString(), clientAdapter));
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while acceping connection at default endpoint.");
MqttTrace.Error(nameof(MqttServerAdapter), exception, "Error while accepting connection at default endpoint.");
}
}
}
@@ -1,105 +1,89 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Security.Cryptography.Certificates;
using Windows.Storage.Streams;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Core.Exceptions;
namespace MQTTnet.Implementations
{
public sealed class MqttTcpChannel : IMqttCommunicationChannel, IDisposable
{
private readonly StreamSocket _socket;
private StreamSocket _socket;
public MqttTcpChannel()
{
_socket = new StreamSocket();
}
public MqttTcpChannel(StreamSocket socket)
{
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
CreateStreams();
}
public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }
public Stream RawReceiveStream { get; private set; }
public async Task ConnectAsync(MqttClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
try
{
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 (_socket == null)
{
throw new MqttCommunicationException(exception);
_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);
}
CreateStreams();
}
public Task DisconnectAsync()
{
try
{
_socket.Dispose();
return Task.FromResult(0);
}
catch (SocketException exception)
{
throw new MqttCommunicationException(exception);
}
}
public async Task WriteAsync(byte[] buffer)
{
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
try
{
await _socket.OutputStream.WriteAsync(buffer.AsBuffer());
await _socket.OutputStream.FlushAsync();
}
catch (SocketException exception)
{
throw new MqttCommunicationException(exception);
}
}
public async Task ReadAsync(byte[] buffer)
{
if (buffer == null) throw new ArgumentNullException(nameof(buffer));
try
{
await _socket.InputStream.ReadAsync(buffer.AsBuffer(), (uint)buffer.Length, InputStreamOptions.None);
}
catch (SocketException exception)
{
throw new MqttCommunicationException(exception);
}
Dispose();
return Task.FromResult(0);
}
public void Dispose()
{
RawReceiveStream?.Dispose();
RawReceiveStream = null;
SendStream?.Dispose();
SendStream = null;
ReceiveStream?.Dispose();
ReceiveStream = null;
_socket?.Dispose();
_socket = null;
}
private void CreateStreams()
{
SendStream = _socket.OutputStream.AsStreamForWrite();
ReceiveStream = _socket.InputStream.AsStreamForRead();
RawReceiveStream = ReceiveStream;
}
private static Certificate LoadCertificate(MqttClientOptions options)
@@ -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 ClientWebSocket _webSocket = new ClientWebSocket();
public Stream RawReceiveStream { get; private set; }
public Stream SendStream => RawReceiveStream;
public Stream ReceiveStream => RawReceiveStream;
public async Task ConnectAsync(MqttClientOptions options)
{
var uri = options.Server;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase))
{
uri = "ws://" + uri;
}
if (options.Port.HasValue)
{
uri += ":" + options.Port;
}
_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,13 @@
<DocumentationFile>bin\x64\Release\MQTTnet.XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="Implementations\MqttWebSocketChannel.cs" />
<Compile Include="MqttClientFactory.cs" />
<Compile Include="Implementations\MqttServerAdapter.cs" />
<Compile Include="MqttServerFactory.cs" />
<Compile Include="Implementations\MqttTcpChannel.cs" />
<Compile Include="MqttServerFactory.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Implementations\WebSocketStream.cs" />
<EmbeddedResource Include="Properties\MQTTnet.Universal.rd.xml" />
</ItemGroup>
<ItemGroup>
@@ -125,7 +127,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>5.3.3</Version>
<Version>5.4.0</Version>
</PackageReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
@@ -1,18 +1,33 @@
using System;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Core.Serializer;
using MQTTnet.Implementations;
namespace MQTTnet
{
public class MqttClientFactory
public class MqttClientFactory : IMqttClientFactory
{
public IMqttClient CreateMqttClient(MqttClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
return new MqttClient(options, new MqttChannelCommunicationAdapter(new MqttTcpChannel(), new MqttV311PacketSerializer()));
return new MqttClient(options, new MqttChannelCommunicationAdapter(GetMqttCommunicationChannel(options), new MqttPacketSerializer()));
}
private static IMqttCommunicationChannel GetMqttCommunicationChannel(MqttClientOptions options)
{
switch (options.ConnectionType)
{
case MqttConnectionType.Tcp:
return new MqttTcpChannel();
case MqttConnectionType.Ws:
return new MqttWebSocketChannel();
default:
throw new NotSupportedException();
}
}
}
}
@@ -6,7 +6,7 @@ using MQTTnet.Implementations;
namespace MQTTnet
{
public class MqttServerFactory
public class MqttServerFactory : IMqttServerFactory
{
public IMqttServer CreateMqttServer(MqttServerOptions options)
{
@@ -10,5 +10,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: AssemblyVersion("2.1.5.1")]
[assembly: AssemblyFileVersion("2.1.5.1")]
[assembly: AssemblyVersion("2.3.1.0")]
[assembly: AssemblyFileVersion("2.3.1.0")]
@@ -1,18 +1,23 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Client;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Serializer;
namespace MQTTnet.Core.Adapter
{
public interface IMqttCommunicationAdapter
{
Task ConnectAsync(MqttClientOptions options, TimeSpan timeout);
IMqttPacketSerializer PacketSerializer { get; }
Task DisconnectAsync();
Task ConnectAsync(TimeSpan timeout, MqttClientOptions options);
Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout);
Task DisconnectAsync(TimeSpan timeout);
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout);
Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets);
Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken);
}
}
@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Serializer;
@@ -11,78 +15,180 @@ namespace MQTTnet.Core.Adapter
{
public class MqttChannelCommunicationAdapter : IMqttCommunicationAdapter
{
private readonly IMqttPacketSerializer _serializer;
private readonly IMqttCommunicationChannel _channel;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public MqttChannelCommunicationAdapter(IMqttCommunicationChannel channel, IMqttPacketSerializer serializer)
{
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
PacketSerializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
}
public async Task ConnectAsync(MqttClientOptions options, TimeSpan timeout)
{
var task = _channel.ConnectAsync(options);
if (await Task.WhenAny(Task.Delay(timeout), task) != task)
{
throw new MqttCommunicationTimedOutException();
}
}
public IMqttPacketSerializer PacketSerializer { get; }
public async Task DisconnectAsync()
public async Task ConnectAsync(TimeSpan timeout, MqttClientOptions options)
{
await _channel.DisconnectAsync();
}
public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout)
{
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"TX >>> {packet} [Timeout={timeout}]");
bool hasTimeout;
try
{
var task = _serializer.SerializeAsync(packet, _channel);
hasTimeout = await Task.WhenAny(Task.Delay(timeout), task) != task;
await _channel.ConnectAsync(options).TimeoutAfter(timeout).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
throw;
}
catch (MqttCommunicationTimedOutException)
{
throw;
}
catch (MqttCommunicationException)
{
throw;
}
catch (Exception exception)
{
throw new MqttCommunicationException(exception);
}
}
if (hasTimeout)
public async Task DisconnectAsync(TimeSpan timeout)
{
try
{
throw new MqttCommunicationTimedOutException();
await _channel.DisconnectAsync().TimeoutAfter(timeout).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
throw;
}
catch (MqttCommunicationTimedOutException)
{
throw;
}
catch (MqttCommunicationException)
{
throw;
}
catch (Exception exception)
{
throw new MqttCommunicationException(exception);
}
}
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout)
public async Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
{
MqttBasePacket packet;
if (timeout > TimeSpan.Zero)
{
var workerTask = _serializer.DeserializeAsync(_channel);
var timeoutTask = Task.Delay(timeout);
var hasTimeout = Task.WhenAny(timeoutTask, workerTask) == timeoutTask;
await _semaphore.WaitAsync(cancellationToken);
if (hasTimeout)
try
{
foreach (var packet in packets)
{
throw new MqttCommunicationTimedOutException();
if (packet == null)
{
continue;
}
MqttTrace.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);
}
packet = workerTask.Result;
if (timeout > TimeSpan.Zero)
{
await _channel.SendStream.FlushAsync(cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
}
else
{
await _channel.SendStream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
}
else
catch (TaskCanceledException)
{
packet = await _serializer.DeserializeAsync(_channel);
throw;
}
catch (MqttCommunicationTimedOutException)
{
throw;
}
catch (MqttCommunicationException)
{
throw;
}
catch (Exception exception)
{
throw new MqttCommunicationException(exception);
}
finally
{
_semaphore.Release();
}
}
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
try
{
ReceivedMqttPacket receivedMqttPacket;
if (timeout > TimeSpan.Zero)
{
receivedMqttPacket = await ReceiveAsync(_channel.RawReceiveStream, cancellationToken).TimeoutAfter(timeout).ConfigureAwait(false);
}
else
{
receivedMqttPacket = await ReceiveAsync(_channel.ReceiveStream, cancellationToken).ConfigureAwait(false);
}
if (cancellationToken.IsCancellationRequested)
{
throw new TaskCanceledException();
}
var packet = PacketSerializer.Deserialize(receivedMqttPacket);
if (packet == null)
{
throw new MqttProtocolViolationException("Received malformed packet.");
}
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), "RX <<< {0}", packet);
return packet;
}
catch (TaskCanceledException)
{
throw;
}
catch (MqttCommunicationTimedOutException)
{
throw;
}
catch (MqttCommunicationException)
{
throw;
}
catch (Exception exception)
{
throw new MqttCommunicationException(exception);
}
}
private static async Task<ReceivedMqttPacket> ReceiveAsync(Stream stream, CancellationToken cancellationToken)
{
var header = MqttPacketReader.ReadHeaderFromSource(stream, cancellationToken);
if (header.BodyLength == 0)
{
return new ReceivedMqttPacket(header, new MemoryStream(0));
}
if (packet == null)
{
throw new MqttProtocolViolationException("Received malformed packet.");
}
var body = new byte[header.BodyLength];
MqttTrace.Information(nameof(MqttChannelCommunicationAdapter), $"RX <<< {packet}");
return packet;
var offset = 0;
do
{
var readBytesCount = await stream.ReadAsync(body, offset, body.Length - offset, cancellationToken).ConfigureAwait(false);
offset += readBytesCount;
} while (offset < header.BodyLength);
return new ReceivedMqttPacket(header, new MemoryStream(body, 0, body.Length));
}
}
}
}
@@ -0,0 +1,15 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Adapter
{
public static class MqttCommunicationAdapterExtensions
{
public static Task SendPacketsAsync(this IMqttCommunicationAdapter adapter, TimeSpan timeout, CancellationToken cancellationToken, params MqttBasePacket[] packets)
{
return adapter.SendPacketsAsync(timeout, cancellationToken, packets);
}
}
}
@@ -5,7 +5,7 @@ namespace MQTTnet.Core.Adapter
{
public class MqttConnectingFailedException : MqttCommunicationException
{
public MqttConnectingFailedException(MqttConnectReturnCode returnCode)
public MqttConnectingFailedException(MqttConnectReturnCode returnCode)
: base($"Connecting with MQTT server failed ({returnCode}).")
{
ReturnCode = returnCode;
@@ -0,0 +1,19 @@
using System;
using System.IO;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Adapter
{
public class ReceivedMqttPacket
{
public ReceivedMqttPacket(MqttPacketHeader header, MemoryStream body)
{
Header = header ?? throw new ArgumentNullException(nameof(header));
Body = body ?? throw new ArgumentNullException(nameof(body));
}
public MqttPacketHeader Header { get; }
public MemoryStream Body { get; }
}
}
@@ -1,16 +1,16 @@
using System.Threading.Tasks;
using MQTTnet.Core.Client;
using System.IO;
namespace MQTTnet.Core.Channel
{
public interface IMqttCommunicationChannel
{
Stream SendStream { get; }
Stream ReceiveStream { get; }
Stream RawReceiveStream { get; }
Task ConnectAsync(MqttClientOptions options);
Task DisconnectAsync();
Task WriteAsync(byte[] buffer);
Task ReadAsync(byte[] buffer);
}
}
+5 -5
View File
@@ -15,10 +15,10 @@ namespace MQTTnet.Core.Client
Task ConnectAsync(MqttApplicationMessage willApplicationMessage = null);
Task DisconnectAsync();
Task PublishAsync(MqttApplicationMessage applicationMessage);
Task<IList<MqttSubscribeResult>> SubscribeAsync(IList<TopicFilter> topicFilters);
Task<IList<MqttSubscribeResult>> SubscribeAsync(params TopicFilter[] topicFilters);
Task Unsubscribe(IList<string> topicFilters);
Task Unsubscribe(params string[] topicFilters);
Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters);
Task UnsubscribeAsync(IEnumerable<string> topicFilters);
Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages);
}
}
@@ -0,0 +1,7 @@
namespace MQTTnet.Core.Client
{
public interface IMqttClientFactory
{
IMqttClient CreateMqttClient(MqttClientOptions options);
}
}
+251 -175
View File
@@ -15,11 +15,11 @@ namespace MQTTnet.Core.Client
public class MqttClient : IMqttClient
{
private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>();
private readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
private readonly MqttClientOptions _options;
private readonly IMqttCommunicationAdapter _adapter;
private bool _isReceivingPackets;
private int _latestPacketIdentifier;
private CancellationTokenSource _cancellationTokenSource;
@@ -27,25 +27,151 @@ namespace MQTTnet.Core.Client
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
_adapter.PacketSerializer.ProtocolVersion = options.ProtocolVersion;
}
public event EventHandler Connected;
public event EventHandler Disconnected;
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
public bool IsConnected { get; private set; }
public bool IsConnected => _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested;
public async Task ConnectAsync(MqttApplicationMessage willApplicationMessage = null)
{
MqttTrace.Verbose(nameof(MqttClient), "Trying to connect.");
ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
if (IsConnected)
try
{
throw new MqttProtocolViolationException("It is not allowed to connect with a server after the connection is established.");
_cancellationTokenSource = new CancellationTokenSource();
_latestPacketIdentifier = 0;
_packetDispatcher.Reset();
MqttTrace.Verbose(nameof(MqttClient), "Trying to connect with server.");
await _adapter.ConnectAsync(_options.DefaultCommunicationTimeout, _options).ConfigureAwait(false);
MqttTrace.Verbose(nameof(MqttClient), "Connection with server established.");
await SetupIncomingPacketProcessingAsync();
await AuthenticateAsync(willApplicationMessage);
MqttTrace.Verbose(nameof(MqttClient), "MQTT connection with server established.");
if (_options.KeepAlivePeriod != TimeSpan.Zero)
{
StartSendKeepAliveMessages(_cancellationTokenSource.Token);
}
Connected?.Invoke(this, EventArgs.Empty);
}
catch (Exception)
{
await DisconnectInternalAsync().ConfigureAwait(false);
throw;
}
}
public async Task DisconnectAsync()
{
if (!IsConnected)
{
return;
}
try
{
await SendAsync(new MqttDisconnectPacket()).ConfigureAwait(false);
}
finally
{
await DisconnectInternalAsync().ConfigureAwait(false);
}
}
public async Task<IList<MqttSubscribeResult>> SubscribeAsync(IEnumerable<TopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
ThrowIfNotConnected();
var subscribePacket = new MqttSubscribePacket
{
PacketIdentifier = GetNewPacketIdentifier(),
TopicFilters = topicFilters.ToList()
};
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket).ConfigureAwait(false);
if (response.SubscribeReturnCodes.Count != subscribePacket.TopicFilters.Count)
{
throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1].");
}
return subscribePacket.TopicFilters.Select((t, i) => new MqttSubscribeResult(t, response.SubscribeReturnCodes[i])).ToList();
}
public async Task UnsubscribeAsync(IEnumerable<string> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
ThrowIfNotConnected();
var unsubscribePacket = new MqttUnsubscribePacket
{
PacketIdentifier = GetNewPacketIdentifier(),
TopicFilters = topicFilters.ToList()
};
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket);
}
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
{
ThrowIfNotConnected();
var publishPackets = applicationMessages.Select(m => m.ToPublishPacket());
foreach (var qosGroup in publishPackets.GroupBy(p => p.QualityOfServiceLevel))
{
var qosPackets = qosGroup.ToArray();
switch (qosGroup.Key)
{
case MqttQualityOfServiceLevel.AtMostOnce:
{
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, qosPackets);
break;
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
foreach (var publishPacket in qosPackets)
{
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket);
}
break;
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
foreach (var publishPacket in qosPackets)
{
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket).ConfigureAwait(false);
await SendAndReceiveAsync<MqttPubCompPacket>(pubRecPacket.CreateResponse<MqttPubRelPacket>()).ConfigureAwait(false);
}
break;
}
default:
{
throw new InvalidOperationException();
}
}
}
}
private async Task AuthenticateAsync(MqttApplicationMessage willApplicationMessage)
{
var connectPacket = new MqttConnectPacket
{
ClientId = _options.ClientId,
@@ -56,115 +182,28 @@ namespace MQTTnet.Core.Client
WillMessage = willApplicationMessage
};
await _adapter.ConnectAsync(_options, _options.DefaultCommunicationTimeout);
MqttTrace.Verbose(nameof(MqttClient), "Connection with server established.");
_cancellationTokenSource = new CancellationTokenSource();
_latestPacketIdentifier = 0;
_packetDispatcher.Reset();
IsConnected = true;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => ReceivePackets(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket);
var response = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket).ConfigureAwait(false);
if (response.ConnectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
await DisconnectAsync();
throw new MqttConnectingFailedException(response.ConnectReturnCode);
}
}
private async Task SetupIncomingPacketProcessingAsync()
{
_isReceivingPackets = false;
if (_options.KeepAlivePeriod != TimeSpan.Zero)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => SendKeepAliveMessagesAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
Task.Factory.StartNew(
() => 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
}
Connected?.Invoke(this, EventArgs.Empty);
}
public async Task DisconnectAsync()
{
await SendAsync(new MqttDisconnectPacket());
await DisconnectInternalAsync();
}
public Task<IList<MqttSubscribeResult>> SubscribeAsync(params TopicFilter[] topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return SubscribeAsync(topicFilters.ToList());
}
public async Task<IList<MqttSubscribeResult>> SubscribeAsync(IList<TopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
if (!topicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.8.3-3].");
ThrowIfNotConnected();
var subscribePacket = new MqttSubscribePacket
while (!_isReceivingPackets && _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested)
{
PacketIdentifier = GetNewPacketIdentifier(),
TopicFilters = topicFilters
};
var response = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket);
if (response.SubscribeReturnCodes.Count != topicFilters.Count)
{
throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1].");
}
return topicFilters.Select((t, i) => new MqttSubscribeResult(t, response.SubscribeReturnCodes[i])).ToList();
}
public Task Unsubscribe(params string[] topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return Unsubscribe(topicFilters.ToList());
}
public async Task Unsubscribe(IList<string> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
if (!topicFilters.Any()) throw new MqttProtocolViolationException("At least one topic filter must be set [MQTT-3.10.3-2].");
ThrowIfNotConnected();
var unsubscribePacket = new MqttUnsubscribePacket
{
PacketIdentifier = GetNewPacketIdentifier(),
TopicFilters = topicFilters
};
await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket);
}
public async Task PublishAsync(MqttApplicationMessage applicationMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
ThrowIfNotConnected();
var publishPacket = applicationMessage.ToPublishPacket();
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await SendAsync(publishPacket);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
publishPacket.PacketIdentifier = GetNewPacketIdentifier();
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket);
await SendAndReceiveAsync<MqttPubCompPacket>(pubRecPacket.CreateResponse<MqttPubRelPacket>());
await Task.Delay(TimeSpan.FromMilliseconds(100));
}
}
@@ -173,86 +212,102 @@ namespace MQTTnet.Core.Client
if (!IsConnected) throw new MqttCommunicationException("The client is not connected.");
}
private void ThrowIfConnected(string message)
{
if (IsConnected) throw new MqttProtocolViolationException(message);
}
private async Task DisconnectInternalAsync()
{
var cts = _cancellationTokenSource;
if (cts == null || cts.IsCancellationRequested)
{
return;
}
cts.Cancel(false);
cts.Dispose();
_cancellationTokenSource = null;
try
{
await _adapter.DisconnectAsync();
await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
MqttTrace.Information(nameof(MqttClient), "Disconnected from adapter.");
}
catch
catch (Exception exception)
{
MqttTrace.Warning(nameof(MqttClient), exception, "Error while disconnecting from adapter.");
}
finally
{
_cancellationTokenSource?.Cancel(false);
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
IsConnected = false;
Disconnected?.Invoke(this, EventArgs.Empty);
}
}
private Task ProcessReceivedPacketAsync(MqttBasePacket mqttPacket)
private async Task ProcessReceivedPacketAsync(MqttBasePacket packet)
{
try
{
if (mqttPacket is MqttPingReqPacket)
MqttTrace.Information(nameof(MqttClient), "Received <<< {0}", packet);
if (packet is MqttPingReqPacket)
{
return SendAsync(new MqttPingRespPacket());
await SendAsync(new MqttPingRespPacket());
return;
}
if (mqttPacket is MqttDisconnectPacket)
if (packet is MqttDisconnectPacket)
{
return DisconnectAsync();
await DisconnectAsync();
return;
}
if (mqttPacket is MqttPublishPacket publishPacket)
if (packet is MqttPublishPacket publishPacket)
{
return ProcessReceivedPublishPacket(publishPacket);
await ProcessReceivedPublishPacket(publishPacket);
return;
}
if (mqttPacket is MqttPubRelPacket pubRelPacket)
if (packet is MqttPubRelPacket pubRelPacket)
{
return ProcessReceivedPubRelPacket(pubRelPacket);
await ProcessReceivedPubRelPacket(pubRelPacket);
return;
}
_packetDispatcher.Dispatch(mqttPacket);
_packetDispatcher.Dispatch(packet);
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttClient), exception, "Error while processing received packet.");
MqttTrace.Error(nameof(MqttClient), exception, "Unhandled exception while processing received packet.");
}
return Task.FromResult(0);
}
private void FireApplicationMessageReceivedEvent(MqttPublishPacket publishPacket)
{
var applicationMessage = publishPacket.ToApplicationMessage();
try
{
var applicationMessage = publishPacket.ToApplicationMessage();
ApplicationMessageReceived?.Invoke(this, new MqttApplicationMessageReceivedEventArgs(applicationMessage));
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttClient), exception, "Unhandled exception while handling application message.");
MqttTrace.Error(nameof(MqttClient), exception, "Unhandled exception while handling application message.");
}
}
private Task ProcessReceivedPublishPacket(MqttPublishPacket publishPacket)
private async Task ProcessReceivedPublishPacket(MqttPublishPacket publishPacket)
{
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
return Task.FromResult(0);
return;
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
FireApplicationMessageReceivedEvent(publishPacket);
return SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
await SendAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
return;
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
@@ -264,53 +319,33 @@ namespace MQTTnet.Core.Client
}
FireApplicationMessageReceivedEvent(publishPacket);
return SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
await SendAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
return;
}
throw new MqttCommunicationException("Received a not supported QoS level.");
}
private async Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
private Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket)
{
lock (_unacknowledgedPublishPackets)
{
_unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier);
}
await SendAsync(pubRelPacket.CreateResponse<MqttPubCompPacket>());
return SendAsync(pubRelPacket.CreateResponse<MqttPubCompPacket>());
}
private Task SendAsync(MqttBasePacket packet)
{
return _adapter.SendPacketAsync(packet, _options.DefaultCommunicationTimeout);
return _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, packet);
}
private async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttBasePacket requestPacket) where TResponsePacket : MqttBasePacket
{
bool ResponsePacketSelector(MqttBasePacket p)
{
var p1 = p as TResponsePacket;
if (p1 == null)
{
return false;
}
var pi1 = requestPacket as IMqttPacketWithIdentifier;
var pi2 = p as IMqttPacketWithIdentifier;
if (pi1 != null && pi2 != null)
{
if (pi1.PacketIdentifier != pi2.PacketIdentifier)
{
return false;
}
}
return true;
}
await _adapter.SendPacketAsync(requestPacket, _options.DefaultCommunicationTimeout);
return (TResponsePacket)await _packetDispatcher.WaitForPacketAsync(ResponsePacketSelector, _options.DefaultCommunicationTimeout);
var packetAwaiter = _packetDispatcher.WaitForPacketAsync(requestPacket, typeof(TResponsePacket), _options.DefaultCommunicationTimeout);
await _adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, requestPacket).ConfigureAwait(false);
return (TResponsePacket)await packetAwaiter.ConfigureAwait(false);
}
private ushort GetNewPacketIdentifier()
@@ -326,53 +361,94 @@ namespace MQTTnet.Core.Client
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(_options.KeepAlivePeriod, cancellationToken);
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket());
await Task.Delay(_options.KeepAlivePeriod, cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return;
}
await SendAndReceiveAsync<MqttPingRespPacket>(new MqttPingReqPacket()).ConfigureAwait(false);
}
}
catch (TaskCanceledException)
{
}
catch (MqttCommunicationException exception)
{
MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication error while receiving packets.");
if (cancellationToken.IsCancellationRequested)
{
return;
}
MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
catch (Exception exception)
{
MqttTrace.Warning(nameof(MqttClient), exception, "Error while sending/receiving keep alive packets.");
MqttTrace.Warning(nameof(MqttClient), exception, "Unhandled exception while sending/receiving keep alive packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
finally
{
MqttTrace.Information(nameof(MqttClient), "Stopped sending keep alive packets.");
await DisconnectInternalAsync();
}
}
private async Task ReceivePackets(CancellationToken cancellationToken)
{
MqttTrace.Information(nameof(MqttClient), "Start receiving packets.");
try
{
while (!cancellationToken.IsCancellationRequested)
{
var mqttPacket = await _adapter.ReceivePacketAsync(TimeSpan.Zero);
MqttTrace.Information(nameof(MqttClient), $"Received <<< {mqttPacket}");
_isReceivingPackets = true;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => ProcessReceivedPacketAsync(mqttPacket), cancellationToken);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
var packet = await _adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return;
}
StartProcessReceivedPacket(packet, cancellationToken);
}
}
catch (TaskCanceledException)
{
}
catch (MqttCommunicationException exception)
{
MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication error while receiving packets.");
if (cancellationToken.IsCancellationRequested)
{
return;
}
MqttTrace.Warning(nameof(MqttClient), exception, "MQTT communication exception while receiving packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttClient), exception, "Error while receiving packets.");
MqttTrace.Error(nameof(MqttClient), exception, "Unhandled exception while receiving packets.");
await DisconnectInternalAsync().ConfigureAwait(false);
}
finally
{
MqttTrace.Information(nameof(MqttClient), "Stopped receiving packets.");
await DisconnectInternalAsync();
}
}
private void StartProcessReceivedPacket(MqttBasePacket packet, CancellationToken cancellationToken)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => ProcessReceivedPacketAsync(packet), cancellationToken).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(CancellationToken cancellationToken)
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Factory.StartNew(() => SendKeepAliveMessagesAsync(cancellationToken), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
}
}
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Client
{
public static class MqttClientExtensions
{
public static Task PublishAsync(this IMqttClient client, params MqttApplicationMessage[] applicationMessages)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
return client.PublishAsync(applicationMessages);
}
public static Task<IList<MqttSubscribeResult>> SubscribeAsync(this IMqttClient client, params TopicFilter[] topicFilters)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return client.SubscribeAsync(topicFilters.ToList());
}
public static Task UnsubscribeAsync(this IMqttClient client, params string[] topicFilters)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return client.UnsubscribeAsync(topicFilters.ToList());
}
}
}
+7 -2
View File
@@ -1,4 +1,5 @@
using System;
using MQTTnet.Core.Serializer;
namespace MQTTnet.Core.Client
{
@@ -7,8 +8,8 @@ namespace MQTTnet.Core.Client
public string Server { get; set; }
public int? Port { get; set; }
public MqttClientTlsOptions TlsOptions { get; } = new MqttClientTlsOptions();
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
public string UserName { get; set; }
@@ -21,5 +22,9 @@ namespace MQTTnet.Core.Client
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(5);
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(10);
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
public MqttConnectionType ConnectionType { get; set; } = MqttConnectionType.Tcp;
}
}
@@ -0,0 +1,8 @@
namespace MQTTnet.Core.Client
{
public enum MqttConnectionType
{
Tcp,
Ws
}
}
-16
View File
@@ -1,16 +0,0 @@
using System;
using System.Threading.Tasks;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Client
{
public class MqttPacketAwaiter : TaskCompletionSource<MqttBasePacket>
{
public MqttPacketAwaiter(Func<MqttBasePacket, bool> packetSelector)
{
PacketSelector = packetSelector ?? throw new ArgumentNullException(nameof(packetSelector));
}
public Func<MqttBasePacket, bool> PacketSelector { get; }
}
}
+52 -67
View File
@@ -1,111 +1,96 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Internal;
using MQTTnet.Core.Packets;
using System.Collections.Concurrent;
namespace MQTTnet.Core.Client
{
public class MqttPacketDispatcher
{
private readonly object _syncRoot = new object();
private readonly List<MqttBasePacket> _receivedPackets = new List<MqttBasePacket>();
private readonly List<MqttPacketAwaiter> _packetAwaiters = new List<MqttPacketAwaiter>();
private readonly ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>> _packetByResponseType = new ConcurrentDictionary<Type, TaskCompletionSource<MqttBasePacket>>();
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>> _packetByResponseTypeAndIdentifier = new ConcurrentDictionary<Type, ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>>();
public async Task<MqttBasePacket> WaitForPacketAsync(Func<MqttBasePacket, bool> selector, TimeSpan timeout)
public async Task<MqttBasePacket> WaitForPacketAsync(MqttBasePacket request, Type responseType, TimeSpan timeout)
{
if (selector == null) throw new ArgumentNullException(nameof(selector));
if (request == null) throw new ArgumentNullException(nameof(request));
var packetAwaiter = AddPacketAwaiter(selector);
DispatchPendingPackets();
var hasTimeout = await Task.WhenAny(Task.Delay(timeout), packetAwaiter.Task) != packetAwaiter.Task;
RemovePacketAwaiter(packetAwaiter);
if (hasTimeout)
var packetAwaiter = AddPacketAwaiter(request, responseType);
try
{
MqttTrace.Warning(nameof(MqttPacketDispatcher), "Timeout while waiting for packet.");
throw new MqttCommunicationTimedOutException();
return await packetAwaiter.Task.TimeoutAfter(timeout).ConfigureAwait(false);
}
catch (MqttCommunicationTimedOutException)
{
MqttTrace.Warning(nameof(MqttPacketDispatcher), "Timeout while waiting for packet of type '{0}'.", responseType.Name);
throw;
}
finally
{
RemovePacketAwaiter(request, responseType);
}
return packetAwaiter.Task.Result;
}
public void Dispatch(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
var packetDispatched = false;
foreach (var packetAwaiter in GetPacketAwaiters())
var type = packet.GetType();
if (packet is IMqttPacketWithIdentifier withIdentifier)
{
if (packetAwaiter.PacketSelector(packet))
if (_packetByResponseTypeAndIdentifier.TryGetValue(type, out var byid))
{
packetAwaiter.SetResult(packet);
packetDispatched = true;
break;
if (byid.TryRemove(withIdentifier.PacketIdentifier, out var tcs))
{
tcs.TrySetResult(packet);
return;
}
}
}
else if (_packetByResponseType.TryRemove(type, out var tcs))
{
tcs.TrySetResult(packet);
return;
}
lock (_syncRoot)
{
if (!packetDispatched)
{
_receivedPackets.Add(packet);
}
else
{
_receivedPackets.Remove(packet);
}
}
throw new InvalidOperationException($"Packet of type '{type.Name}' not handled or dispatched.");
}
public void Reset()
{
lock (_syncRoot)
{
_packetAwaiters.Clear();
_receivedPackets.Clear();
}
_packetByResponseTypeAndIdentifier.Clear();
_packetByResponseType.Clear();
}
private List<MqttPacketAwaiter> GetPacketAwaiters()
private TaskCompletionSource<MqttBasePacket> AddPacketAwaiter(MqttBasePacket request, Type responseType)
{
lock (_packetAwaiters)
var tcs = new TaskCompletionSource<MqttBasePacket>();
if (request is IMqttPacketWithIdentifier requestWithIdentifier)
{
return new List<MqttPacketAwaiter>(_packetAwaiters);
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
byId[requestWithIdentifier.PacketIdentifier] = tcs;
}
else
{
_packetByResponseType[responseType] = tcs;
}
return tcs;
}
private MqttPacketAwaiter AddPacketAwaiter(Func<MqttBasePacket, bool> selector)
private void RemovePacketAwaiter(MqttBasePacket request, Type responseType)
{
lock (_syncRoot)
if (request is IMqttPacketWithIdentifier requestWithIdentifier)
{
var packetAwaiter = new MqttPacketAwaiter(selector);
_packetAwaiters.Add(packetAwaiter);
return packetAwaiter;
var byId = _packetByResponseTypeAndIdentifier.GetOrAdd(responseType, key => new ConcurrentDictionary<ushort, TaskCompletionSource<MqttBasePacket>>());
byId.TryRemove(requestWithIdentifier.PacketIdentifier, out var _);
}
}
private void RemovePacketAwaiter(MqttPacketAwaiter packetAwaiter)
{
lock (_syncRoot)
else
{
_packetAwaiters.Remove(packetAwaiter);
}
}
private void DispatchPendingPackets()
{
List<MqttBasePacket> receivedPackets;
lock (_syncRoot)
{
receivedPackets = new List<MqttBasePacket>(_receivedPackets);
}
foreach (var pendingPacket in receivedPackets)
{
Dispatch(pendingPacket);
_packetByResponseType.TryRemove(responseType, out var _);
}
}
}
+33 -14
View File
@@ -6,39 +6,58 @@ namespace MQTTnet.Core.Diagnostics
{
public static event EventHandler<MqttTraceMessagePublishedEventArgs> TraceMessagePublished;
public static void Verbose(string source, string message)
public static void Verbose(string source, string message, params object[] parameters)
{
Publish(source, MqttTraceLevel.Verbose, null, message);
Publish(source, MqttTraceLevel.Verbose, null, message, parameters);
}
public static void Information(string source, string message)
public static void Information(string source, string message, params object[] parameters)
{
Publish(source, MqttTraceLevel.Information, null, message);
Publish(source, MqttTraceLevel.Information, null, message, parameters);
}
public static void Warning(string source, string message)
public static void Warning(string source, string message, params object[] parameters)
{
Publish(source, MqttTraceLevel.Warning, null, message);
Publish(source, MqttTraceLevel.Warning, null, message, parameters);
}
public static void Warning(string source, Exception exception, string message)
public static void Warning(string source, Exception exception, string message, params object[] parameters)
{
Publish(source, MqttTraceLevel.Warning, exception, message);
Publish(source, MqttTraceLevel.Warning, exception, message, parameters);
}
public static void Error(string source, string message)
public static void Error(string source, string message, params object[] parameters)
{
Publish(source, MqttTraceLevel.Error, null, message);
Publish(source, MqttTraceLevel.Error, null, message, parameters);
}
public static void Error(string source, Exception exception, string message)
public static void Error(string source, Exception exception, string message, params object[] parameters)
{
Publish(source, MqttTraceLevel.Error, exception, message);
Publish(source, MqttTraceLevel.Error, exception, message, parameters);
}
private static void Publish(string source, MqttTraceLevel traceLevel, Exception exception, string message)
private static void Publish(string source, MqttTraceLevel traceLevel, Exception exception, string message, params object[] parameters)
{
TraceMessagePublished?.Invoke(null, new MqttTraceMessagePublishedEventArgs(Environment.CurrentManagedThreadId, source, traceLevel, message, exception));
var handler = TraceMessagePublished;
if (handler == null)
{
return;
}
if (parameters?.Length > 0)
{
try
{
message = string.Format(message, parameters);
}
catch (Exception formatException)
{
Error(nameof(MqttTrace), formatException, "Error while tracing message: " + message);
return;
}
}
handler.Invoke(null, new MqttTraceMessagePublishedEventArgs(Environment.CurrentManagedThreadId, source, traceLevel, message, exception));
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
namespace MQTTnet.Core.Diagnostics
{
public enum MqttTraceLevel
public enum MqttTraceLevel
{
Verbose,
Information,
@@ -4,7 +4,7 @@ namespace MQTTnet.Core.Exceptions
{
public class MqttCommunicationException : Exception
{
public MqttCommunicationException()
protected MqttCommunicationException()
{
}
@@ -17,5 +17,10 @@ namespace MQTTnet.Core.Exceptions
: base(message)
{
}
public MqttCommunicationException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
@@ -4,7 +4,7 @@ namespace MQTTnet.Core.Exceptions
{
public sealed class MqttProtocolViolationException : Exception
{
public MqttProtocolViolationException(string message)
public MqttProtocolViolationException(string message)
: base(message)
{
}
@@ -1,32 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MQTTnet.Core.Internal
{
public sealed class AsyncGate
{
private readonly Queue<TaskCompletionSource<bool>> _waitingTasks = new Queue<TaskCompletionSource<bool>>();
public Task WaitOneAsync()
{
var tcs = new TaskCompletionSource<bool>();
lock (_waitingTasks)
{
_waitingTasks.Enqueue(tcs);
}
return tcs.Task;
}
public void Set()
{
lock (_waitingTasks)
{
if (_waitingTasks.Count > 0)
{
_waitingTasks.Dequeue().SetResult(true);
}
}
}
}
}
+74
View File
@@ -0,0 +1,74 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Exceptions;
namespace MQTTnet.Core.Internal
{
public static class TaskExtensions
{
public static async Task TimeoutAfter(this Task task, TimeSpan timeout)
{
using (var cancellationTokenSource = new CancellationTokenSource())
{
try
{
var timeoutTask = Task.Delay(timeout, cancellationTokenSource.Token);
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
if (finishedTask == timeoutTask)
{
throw new MqttCommunicationTimedOutException();
}
if (task.IsCanceled)
{
throw new TaskCanceledException();
}
if (task.IsFaulted)
{
throw new MqttCommunicationException(task.Exception.GetBaseException());
}
}
finally
{
cancellationTokenSource.Cancel();
}
}
}
public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout)
{
using (var cancellationTokenSource = new CancellationTokenSource())
{
try
{
var timeoutTask = Task.Delay(timeout, cancellationTokenSource.Token);
var finishedTask = await Task.WhenAny(timeoutTask, task).ConfigureAwait(false);
if (finishedTask == timeoutTask)
{
throw new MqttCommunicationTimedOutException();
}
if (task.IsCanceled)
{
throw new TaskCanceledException();
}
if (task.IsFaulted)
{
throw new MqttCommunicationException(task.Exception.GetBaseException());
}
return task.Result;
}
finally
{
cancellationTokenSource.Cancel();
}
}
}
}
}
+3 -2
View File
@@ -5,6 +5,7 @@
<AssemblyName>MQTTnet.Core</AssemblyName>
<RootNamespace>MQTTnet.Core</RootNamespace>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<DebugType>Full</DebugType>
<Description></Description>
<Product></Product>
<Company></Company>
@@ -16,8 +17,8 @@
<PackageIconUrl></PackageIconUrl>
<RepositoryUrl></RepositoryUrl>
<PackageTags></PackageTags>
<FileVersion>2.1.5.1</FileVersion>
<AssemblyVersion>2.1.5.1</AssemblyVersion>
<FileVersion>2.3.1.0</FileVersion>
<AssemblyVersion>2.3.1.0</AssemblyVersion>
<PackageLicenseUrl></PackageLicenseUrl>
</PropertyGroup>
@@ -0,0 +1,58 @@
using System;
using System.Globalization;
using System.IO;
using System.Text;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core
{
public class MqttApplicationMessageFactory
{
public MqttApplicationMessage CreateApplicationMessage(string topic, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
{
if (topic == null) throw new ArgumentNullException(nameof(topic));
return CreateApplicationMessage(topic, new byte[0], qualityOfServiceLevel, retain);
}
public MqttApplicationMessage CreateApplicationMessage(string topic, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
{
if (topic == null) throw new ArgumentNullException(nameof(topic));
if (payload == null)
{
payload = new byte[0];
}
return new MqttApplicationMessage(topic, payload, qualityOfServiceLevel, retain);
}
public MqttApplicationMessage CreateApplicationMessage<TPayload>(string topic, TPayload payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
{
if (topic == null) throw new ArgumentNullException(nameof(topic));
var payloadString = Convert.ToString(payload, CultureInfo.InvariantCulture);
var payloadBuffer = string.IsNullOrEmpty(payloadString) ? new byte[0] : Encoding.UTF8.GetBytes(payloadString);
return CreateApplicationMessage(topic, payloadBuffer, qualityOfServiceLevel, retain);
}
public MqttApplicationMessage CreateApplicationMessage(string topic, Stream payload, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, bool retain = false)
{
if (topic == null) throw new ArgumentNullException(nameof(topic));
byte[] payloadBuffer;
if (payload == null || payload.Length == 0)
{
payloadBuffer = new byte[0];
}
else
{
payloadBuffer = new byte[payload.Length - payload.Position];
payload.Read(payloadBuffer, 0, payloadBuffer.Length);
}
return CreateApplicationMessage(topic, payloadBuffer, qualityOfServiceLevel, retain);
}
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ namespace MQTTnet.Core.Packets
public override string ToString()
{
return $"{nameof(MqttConnAckPacket)}: [ConnectReturnCode={ConnectReturnCode}] [IsSessionPresent={IsSessionPresent}]";
return nameof(MqttConnAckPacket) + ": [ConnectReturnCode=" + ConnectReturnCode + "] [IsSessionPresent=" + IsSessionPresent + "]";
}
}
}
+7 -3
View File
@@ -1,7 +1,11 @@
namespace MQTTnet.Core.Packets
using MQTTnet.Core.Serializer;
namespace MQTTnet.Core.Packets
{
public sealed class MqttConnectPacket: MqttBasePacket
public sealed class MqttConnectPacket : MqttBasePacket
{
public MqttProtocolVersion ProtocolVersion { get; set; }
public string ClientId { get; set; }
public string Username { get; set; }
@@ -16,7 +20,7 @@
public override string ToString()
{
return $"{nameof(MqttConnectPacket)} [ClientId={ClientId}] [Username={Username}] [Password={Password}] [KeepAlivePeriod={KeepAlivePeriod}] [CleanSession={CleanSession}]";
return nameof(MqttConnectPacket) + ": [ClientId=" + ClientId + "] [Username=" + Username + "] [Password=" + Password + "] [KeepAlivePeriod=" + KeepAlivePeriod + "] [CleanSession=" + CleanSession + "]";
}
}
}
+13
View File
@@ -0,0 +1,13 @@
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Packets
{
public class MqttPacketHeader
{
public MqttControlPacketType ControlPacketType { get; set; }
public byte FixedHeader { get; set; }
public int BodyLength { get; set; }
}
}
+8 -3
View File
@@ -1,4 +1,4 @@
using System.Text;
using System;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Packets
@@ -17,8 +17,13 @@ namespace MQTTnet.Core.Packets
public override string ToString()
{
return
$"{nameof(MqttPublishPacket)} [Topic={Topic}] [Payload={Encoding.UTF8.GetString(Payload, 0, Payload.Length)}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]";
return nameof(MqttPublishPacket) +
": [Topic=" + Topic + "]" +
" [Payload=" + Convert.ToBase64String(Payload) + "]" +
" [QoSLevel=" + QualityOfServiceLevel + "]" +
" [Dup=" + Dup + "]" +
" [Retain=" + Retain + "]" +
" [PacketIdentifier=" + PacketIdentifier + "]";
}
}
}
+1 -2
View File
@@ -13,8 +13,7 @@ namespace MQTTnet.Core.Packets
public override string ToString()
{
var subscribeReturnCodesText = string.Join(",", SubscribeReturnCodes.Select(f => f.ToString()));
return
$"{nameof(MqttSubAckPacket)} [PacketIdentifier={PacketIdentifier}] [SubscribeReturnCodes={subscribeReturnCodesText}]";
return nameof(MqttSubAckPacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [SubscribeReturnCodes=" + subscribeReturnCodesText + "]";
}
}
}
+2 -3
View File
@@ -6,14 +6,13 @@ namespace MQTTnet.Core.Packets
public sealed class MqttSubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
public IList<TopicFilter> TopicFilters { get; set; } = new List<TopicFilter>();
public override string ToString()
{
var topicFiltersText = string.Join(",", TopicFilters.Select(f => $"{f.Topic}@{f.QualityOfServiceLevel}"));
return
$"{nameof(MqttSubscribePacket)} [PacketIdentifier={PacketIdentifier}] [TopicFilters={topicFiltersText}]";
return nameof(MqttSubscribePacket) + ": [PacketIdentifier=" + PacketIdentifier + "] [TopicFilters=" + topicFiltersText + "]";
}
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ namespace MQTTnet.Core.Packets
public sealed class MqttUnsubscribePacket : MqttBasePacket, IMqttPacketWithIdentifier
{
public ushort PacketIdentifier { get; set; }
public IList<string> TopicFilters { get; set; } = new List<string>();
}
}
@@ -1,13 +1,14 @@
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Serializer
{
public interface IMqttPacketSerializer
{
Task SerializeAsync(MqttBasePacket mqttPacket, IMqttCommunicationChannel destination);
MqttProtocolVersion ProtocolVersion { get; set; }
Task<MqttBasePacket> DeserializeAsync(IMqttCommunicationChannel source);
byte[] Serialize(MqttBasePacket mqttPacket);
MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket);
}
}
+37 -86
View File
@@ -1,56 +1,44 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Protocol;
using MQTTnet.Core.Packets;
namespace MQTTnet.Core.Serializer
{
public sealed class MqttPacketReader : IDisposable
public sealed class MqttPacketReader : BinaryReader
{
private readonly MemoryStream _remainingData = new MemoryStream();
private readonly IMqttCommunicationChannel _source;
private int _remainingLength;
public MqttPacketReader(IMqttCommunicationChannel source)
private readonly ReceivedMqttPacket _receivedMqttPacket;
public MqttPacketReader(ReceivedMqttPacket receivedMqttPacket)
: base(receivedMqttPacket.Body, Encoding.UTF8, true)
{
_source = source ?? throw new ArgumentNullException(nameof(source));
_receivedMqttPacket = receivedMqttPacket;
}
public MqttControlPacketType ControlPacketType { get; private set; }
public bool EndOfRemainingData => BaseStream.Position == _receivedMqttPacket.Header.BodyLength;
public byte FixedHeader { get; private set; }
public bool EndOfRemainingData => _remainingData.Position == _remainingData.Length;
public async Task ReadToEndAsync()
public static MqttPacketHeader ReadHeaderFromSource(Stream stream, CancellationToken cancellationToken)
{
await ReadFixedHeaderAsync();
await ReadRemainingLengthAsync();
var fixedHeader = (byte)stream.ReadByte();
var controlPacketType = (MqttControlPacketType)(fixedHeader >> 4);
var bodyLength = ReadBodyLengthFromSource(stream, cancellationToken);
if (_remainingLength == 0)
return new MqttPacketHeader
{
return;
}
var buffer = new byte[_remainingLength];
await ReadFromSourceAsync(buffer);
_remainingData.Write(buffer, 0, buffer.Length);
_remainingData.Position = 0;
FixedHeader = fixedHeader,
ControlPacketType = controlPacketType,
BodyLength = bodyLength
};
}
public async Task<byte> ReadRemainingDataByteAsync()
public override ushort ReadUInt16()
{
return (await ReadRemainingDataAsync(1))[0];
}
public async Task<ushort> ReadRemainingDataUShortAsync()
{
var buffer = await ReadRemainingDataAsync(2);
var buffer = ReadBytes(2);
var temp = buffer[0];
buffer[0] = buffer[1];
@@ -59,32 +47,24 @@ namespace MQTTnet.Core.Serializer
return BitConverter.ToUInt16(buffer, 0);
}
public async Task<string> ReadRemainingDataStringWithLengthPrefixAsync()
public string ReadStringWithLengthPrefix()
{
var buffer = await ReadRemainingDataWithLengthPrefixAsync();
var buffer = ReadWithLengthPrefix();
return Encoding.UTF8.GetString(buffer, 0, buffer.Length);
}
public async Task<byte[]> ReadRemainingDataWithLengthPrefixAsync()
public byte[] ReadWithLengthPrefix()
{
var length = await ReadRemainingDataUShortAsync();
return await ReadRemainingDataAsync(length);
var length = ReadUInt16();
return ReadBytes(length);
}
public Task<byte[]> ReadRemainingDataAsync()
public byte[] ReadRemainingData()
{
return ReadRemainingDataAsync(_remainingLength - (int)_remainingData.Position);
return ReadBytes(_receivedMqttPacket.Header.BodyLength - (int)BaseStream.Position);
}
public async Task<byte[]> ReadRemainingDataAsync(int length)
{
var buffer = new byte[length];
await _remainingData.ReadAsync(buffer, 0, buffer.Length);
return buffer;
}
private async Task ReadRemainingLengthAsync()
private static int ReadBodyLengthFromSource(Stream stream, CancellationToken cancellationToken)
{
// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
var multiplier = 1;
@@ -92,49 +72,20 @@ namespace MQTTnet.Core.Serializer
byte encodedByte;
do
{
encodedByte = await ReadStreamByteAsync();
if (cancellationToken.IsCancellationRequested)
{
throw new TaskCanceledException();
}
encodedByte = (byte)stream.ReadByte();
value += (encodedByte & 127) * multiplier;
multiplier *= 128;
if (multiplier > 128 * 128 * 128)
{
throw new MqttProtocolViolationException("Remaining length is ivalid.");
throw new MqttProtocolViolationException("Remaining length is invalid.");
}
} while ((encodedByte & 128) != 0);
_remainingLength = value;
}
private Task ReadFromSourceAsync(byte[] buffer)
{
try
{
return _source.ReadAsync(buffer);
}
catch (Exception exception)
{
throw new MqttCommunicationException(exception);
}
}
private async Task<byte> ReadStreamByteAsync()
{
var buffer = new byte[1];
await ReadFromSourceAsync(buffer);
return buffer[0];
}
private async Task ReadFixedHeaderAsync()
{
FixedHeader = await ReadStreamByteAsync();
var byteReader = new ByteReader(FixedHeader);
byteReader.Read(4);
ControlPacketType = (MqttControlPacketType)byteReader.Read(4);
}
public void Dispose()
{
_remainingData?.Dispose();
return value;
}
}
}
@@ -0,0 +1,609 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Serializer
{
public sealed class MqttPacketSerializer : IMqttPacketSerializer
{
private static byte[] ProtocolVersionV311Name { get; } = Encoding.UTF8.GetBytes("MQTT");
private static byte[] ProtocolVersionV310Name { get; } = Encoding.UTF8.GetBytes("MQIs");
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
public byte[] Serialize(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
using (var stream = new MemoryStream())
using (var writer = new MqttPacketWriter(stream))
{
var header = new List<byte> { SerializePacket(packet, writer) };
var body = stream.ToArray();
MqttPacketWriter.BuildLengthHeader(body.Length, header);
var headerArray = header.ToArray();
var writeBuffer = new byte[header.Count + body.Length];
Buffer.BlockCopy(headerArray, 0, writeBuffer, 0, headerArray.Length);
Buffer.BlockCopy(body, 0, writeBuffer, headerArray.Length, body.Length);
return writeBuffer;
}
}
private byte SerializePacket(MqttBasePacket packet, MqttPacketWriter writer)
{
if (packet is MqttConnectPacket connectPacket)
{
return Serialize(connectPacket, writer);
}
if (packet is MqttConnAckPacket connAckPacket)
{
return Serialize(connAckPacket, writer);
}
if (packet is MqttDisconnectPacket disconnectPacket)
{
return Serialize(disconnectPacket, writer);
}
if (packet is MqttPingReqPacket pingReqPacket)
{
return Serialize(pingReqPacket, writer);
}
if (packet is MqttPingRespPacket pingRespPacket)
{
return Serialize(pingRespPacket, writer);
}
if (packet is MqttPublishPacket publishPacket)
{
return Serialize(publishPacket, writer);
}
if (packet is MqttPubAckPacket pubAckPacket)
{
return Serialize(pubAckPacket, writer);
}
if (packet is MqttPubRecPacket pubRecPacket)
{
return Serialize(pubRecPacket, writer);
}
if (packet is MqttPubRelPacket pubRelPacket)
{
return Serialize(pubRelPacket, writer);
}
if (packet is MqttPubCompPacket pubCompPacket)
{
return Serialize(pubCompPacket, writer);
}
if (packet is MqttSubscribePacket subscribePacket)
{
return Serialize(subscribePacket, writer);
}
if (packet is MqttSubAckPacket subAckPacket)
{
return Serialize(subAckPacket, writer);
}
if (packet is MqttUnsubscribePacket unsubscribePacket)
{
return Serialize(unsubscribePacket, writer);
}
if (packet is MqttUnsubAckPacket unsubAckPacket)
{
return Serialize(unsubAckPacket, writer);
}
throw new MqttProtocolViolationException("Packet type invalid.");
}
public MqttBasePacket Deserialize(ReceivedMqttPacket receivedMqttPacket)
{
if (receivedMqttPacket == null) throw new ArgumentNullException(nameof(receivedMqttPacket));
using (var reader = new MqttPacketReader(receivedMqttPacket))
{
return Deserialize(receivedMqttPacket.Header, reader);
}
}
private static MqttBasePacket Deserialize(MqttPacketHeader header, MqttPacketReader reader)
{
switch (header.ControlPacketType)
{
case MqttControlPacketType.Connect:
{
return DeserializeConnect(reader);
}
case MqttControlPacketType.ConnAck:
{
return DeserializeConnAck(reader);
}
case MqttControlPacketType.Disconnect:
{
return new MqttDisconnectPacket();
}
case MqttControlPacketType.Publish:
{
return DeserializePublish(reader, header);
}
case MqttControlPacketType.PubAck:
{
return new MqttPubAckPacket
{
PacketIdentifier = reader.ReadUInt16()
};
}
case MqttControlPacketType.PubRec:
{
return new MqttPubRecPacket
{
PacketIdentifier = reader.ReadUInt16()
};
}
case MqttControlPacketType.PubRel:
{
return new MqttPubRelPacket
{
PacketIdentifier = reader.ReadUInt16()
};
}
case MqttControlPacketType.PubComp:
{
return new MqttPubCompPacket
{
PacketIdentifier = reader.ReadUInt16()
};
}
case MqttControlPacketType.PingReq:
{
return new MqttPingReqPacket();
}
case MqttControlPacketType.PingResp:
{
return new MqttPingRespPacket();
}
case MqttControlPacketType.Subscribe:
{
return DeserializeSubscribe(reader);
}
case MqttControlPacketType.SubAck:
{
return DeserializeSubAck(reader);
}
case MqttControlPacketType.Unsubscibe:
{
return DeserializeUnsubscribe(reader);
}
case MqttControlPacketType.UnsubAck:
{
return new MqttUnsubAckPacket
{
PacketIdentifier = reader.ReadUInt16()
};
}
default:
{
throw new MqttProtocolViolationException(
$"Packet type ({(int)header.ControlPacketType}) not supported.");
}
}
}
private static MqttBasePacket DeserializeUnsubscribe(MqttPacketReader reader)
{
var packet = new MqttUnsubscribePacket
{
PacketIdentifier = reader.ReadUInt16(),
};
while (!reader.EndOfRemainingData)
{
packet.TopicFilters.Add(reader.ReadStringWithLengthPrefix());
}
return packet;
}
private static MqttBasePacket DeserializeSubscribe(MqttPacketReader reader)
{
var packet = new MqttSubscribePacket
{
PacketIdentifier = reader.ReadUInt16()
};
while (!reader.EndOfRemainingData)
{
packet.TopicFilters.Add(new TopicFilter(
reader.ReadStringWithLengthPrefix(),
(MqttQualityOfServiceLevel)reader.ReadByte()));
}
return packet;
}
private static MqttBasePacket DeserializePublish(MqttPacketReader reader, MqttPacketHeader mqttPacketHeader)
{
var fixedHeader = new ByteReader(mqttPacketHeader.FixedHeader);
var retain = fixedHeader.Read();
var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2);
var dup = fixedHeader.Read();
var topic = reader.ReadStringWithLengthPrefix();
ushort packetIdentifier = 0;
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
packetIdentifier = reader.ReadUInt16();
}
var packet = new MqttPublishPacket
{
Retain = retain,
QualityOfServiceLevel = qualityOfServiceLevel,
Dup = dup,
Topic = topic,
Payload = reader.ReadRemainingData(),
PacketIdentifier = packetIdentifier
};
return packet;
}
private static MqttBasePacket DeserializeConnect(MqttPacketReader reader)
{
reader.ReadBytes(2); // Skip 2 bytes
MqttProtocolVersion protocolVersion;
var protocolName = reader.ReadBytes(4);
if (protocolName.SequenceEqual(ProtocolVersionV310Name))
{
reader.ReadBytes(2);
protocolVersion = MqttProtocolVersion.V310;
}
else if (protocolName.SequenceEqual(ProtocolVersionV311Name))
{
protocolVersion = MqttProtocolVersion.V311;
}
else
{
throw new MqttProtocolViolationException("Protocol name is not supported.");
}
var protocolLevel = reader.ReadByte();
var connectFlags = reader.ReadByte();
var connectFlagsReader = new ByteReader(connectFlags);
connectFlagsReader.Read(); // Reserved.
var packet = new MqttConnectPacket
{
ProtocolVersion = protocolVersion,
CleanSession = connectFlagsReader.Read()
};
var willFlag = connectFlagsReader.Read();
var willQoS = connectFlagsReader.Read(2);
var willRetain = connectFlagsReader.Read();
var passwordFlag = connectFlagsReader.Read();
var usernameFlag = connectFlagsReader.Read();
packet.KeepAlivePeriod = reader.ReadUInt16();
packet.ClientId = reader.ReadStringWithLengthPrefix();
if (willFlag)
{
packet.WillMessage = new MqttApplicationMessage(
reader.ReadStringWithLengthPrefix(),
reader.ReadWithLengthPrefix(),
(MqttQualityOfServiceLevel)willQoS,
willRetain);
}
if (usernameFlag)
{
packet.Username = reader.ReadStringWithLengthPrefix();
}
if (passwordFlag)
{
packet.Password = reader.ReadStringWithLengthPrefix();
}
ValidateConnectPacket(packet);
return packet;
}
private static MqttBasePacket DeserializeSubAck(MqttPacketReader reader)
{
var packet = new MqttSubAckPacket
{
PacketIdentifier = reader.ReadUInt16()
};
while (!reader.EndOfRemainingData)
{
packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)reader.ReadByte());
}
return packet;
}
private static MqttBasePacket DeserializeConnAck(MqttPacketReader reader)
{
var variableHeader1 = reader.ReadByte();
var variableHeader2 = reader.ReadByte();
var packet = new MqttConnAckPacket
{
IsSessionPresent = new ByteReader(variableHeader1).Read(),
ConnectReturnCode = (MqttConnectReturnCode)variableHeader2
};
return packet;
}
private static void ValidateConnectPacket(MqttConnectPacket packet)
{
if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession)
{
throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7].");
}
}
private static void ValidatePublishPacket(MqttPublishPacket packet)
{
if (packet.QualityOfServiceLevel == 0 && packet.Dup)
{
throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2].");
}
}
private byte Serialize(MqttConnectPacket packet, MqttPacketWriter writer)
{
ValidateConnectPacket(packet);
// Write variable header
writer.Write(0x00, 0x04); // 3.1.2.1 Protocol Name
if (ProtocolVersion == MqttProtocolVersion.V311)
{
writer.Write(ProtocolVersionV311Name);
writer.Write(0x04); // 3.1.2.2 Protocol Level (4)
}
else
{
writer.Write(ProtocolVersionV310Name);
writer.Write(0x64, 0x70, 0x03); // Protocol Level (0x03)
}
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);
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 byte Serialize(MqttConnAckPacket packet, MqttPacketWriter writer)
{
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 byte Serialize(MqttPubRelPacket packet, MqttPacketWriter writer)
{
writer.Write(packet.PacketIdentifier);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.PubRel, 0x02);
}
private static byte Serialize(MqttDisconnectPacket packet, MqttPacketWriter writer)
{
return SerializeEmptyPacketAsync(MqttControlPacketType.Disconnect, writer);
}
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);
writer.WriteWithLengthPrefix(packet.Topic);
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
writer.Write(packet.PacketIdentifier);
}
else
{
if (packet.PacketIdentifier > 0)
{
throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5].");
}
}
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)
{
writer.WriteWithLengthPrefix(topicFilter.Topic);
writer.Write((byte)topicFilter.QualityOfServiceLevel);
}
}
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)
{
writer.Write((byte)packetSubscribeReturnCode);
}
}
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.SubAck);
}
private static byte Serialize(MqttUnsubscribePacket packet, MqttPacketWriter writer)
{
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)
{
foreach (var topicFilter in packet.TopicFilters)
{
writer.WriteWithLengthPrefix(topicFilter);
}
}
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
}
private static byte Serialize(MqttUnsubAckPacket packet, MqttPacketWriter writer)
{
writer.Write(packet.PacketIdentifier);
return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.UnsubAck);
}
private static byte SerializeEmptyPacketAsync(MqttControlPacketType type, MqttPacketWriter writer)
{
return MqttPacketWriter.BuildFixedHeader(type);
}
}
}
+28 -51
View File
@@ -1,47 +1,46 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Protocol;
using System.Collections.Generic;
namespace MQTTnet.Core.Serializer
{
public sealed class MqttPacketWriter : IDisposable
public sealed class MqttPacketWriter : BinaryWriter
{
private readonly MemoryStream _buffer = new MemoryStream(512);
public void InjectFixedHeader(MqttControlPacketType packetType, byte flags = 0)
public MqttPacketWriter(Stream stream)
: base(stream)
{
var fixedHeader = (byte)((byte)packetType << 4);
}
public static byte BuildFixedHeader(MqttControlPacketType packetType, byte flags = 0)
{
var fixedHeader = (int)packetType << 4;
fixedHeader |= flags;
InjectFixedHeader(fixedHeader);
return (byte)fixedHeader;
}
public void Write(byte value)
{
_buffer.WriteByte(value);
}
public void Write(ushort value)
public override void Write(ushort value)
{
var buffer = BitConverter.GetBytes(value);
_buffer.WriteByte(buffer[1]);
_buffer.WriteByte(buffer[0]);
Write(buffer[1], buffer[0]);
}
public new void Write(params byte[] values)
{
base.Write(values);
}
public new void Write(byte value)
{
base.Write(value);
}
public void Write(ByteWriter value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
_buffer.WriteByte(value.Value);
}
public void Write(params byte[] value)
{
if (value == null) throw new ArgumentNullException(nameof(value));
_buffer.Write(value, 0, value.Length);
Write(value.Value);
}
public void WriteWithLengthPrefix(string value)
@@ -57,36 +56,16 @@ namespace MQTTnet.Core.Serializer
Write(value);
}
public Task WriteToAsync(IMqttCommunicationChannel destination)
public static void BuildLengthHeader(int length, List<byte> header)
{
if (destination == null) throw new ArgumentNullException(nameof(destination));
return destination.WriteAsync(_buffer.ToArray());
}
public void Dispose()
{
_buffer?.Dispose();
}
private void InjectFixedHeader(byte fixedHeader)
{
if (_buffer.Length == 0)
if (length == 0)
{
Write(fixedHeader);
Write(0);
header.Add(0);
return;
}
var backupBuffer = _buffer.ToArray();
var remainingLength = (int)_buffer.Length;
_buffer.SetLength(0);
_buffer.WriteByte(fixedHeader);
// Alorithm taken from http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html.
var x = remainingLength;
var x = length;
do
{
var encodedByte = x % 128;
@@ -96,10 +75,8 @@ namespace MQTTnet.Core.Serializer
encodedByte = encodedByte | 128;
}
_buffer.WriteByte((byte)encodedByte);
header.Add((byte)encodedByte);
} while (x > 0);
_buffer.Write(backupBuffer, 0, backupBuffer.Length);
}
}
}
@@ -0,0 +1,8 @@
namespace MQTTnet.Core.Serializer
{
public enum MqttProtocolVersion
{
V311,
V310
}
}
@@ -1,598 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Exceptions;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
namespace MQTTnet.Core.Serializer
{
public sealed class MqttV311PacketSerializer : IMqttPacketSerializer
{
private static readonly byte[] MqttV311Prefix = Encoding.UTF8.GetBytes("MQTT");
public Task SerializeAsync(MqttBasePacket packet, IMqttCommunicationChannel destination)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
if (destination == null) throw new ArgumentNullException(nameof(destination));
if (packet is MqttConnectPacket connectPacket)
{
return SerializeAsync(connectPacket, destination);
}
if (packet is MqttConnAckPacket connAckPacket)
{
return SerializeAsync(connAckPacket, destination);
}
if (packet is MqttDisconnectPacket disconnectPacket)
{
return SerializeAsync(disconnectPacket, destination);
}
if (packet is MqttPingReqPacket pingReqPacket)
{
return SerializeAsync(pingReqPacket, destination);
}
if (packet is MqttPingRespPacket pingRespPacket)
{
return SerializeAsync(pingRespPacket, destination);
}
if (packet is MqttPublishPacket publishPacket)
{
return SerializeAsync(publishPacket, destination);
}
if (packet is MqttPubAckPacket pubAckPacket)
{
return SerializeAsync(pubAckPacket, destination);
}
if (packet is MqttPubRecPacket pubRecPacket)
{
return SerializeAsync(pubRecPacket, destination);
}
if (packet is MqttPubRelPacket pubRelPacket)
{
return SerializeAsync(pubRelPacket, destination);
}
if (packet is MqttPubCompPacket pubCompPacket)
{
return SerializeAsync(pubCompPacket, destination);
}
if (packet is MqttSubscribePacket subscribePacket)
{
return SerializeAsync(subscribePacket, destination);
}
if (packet is MqttSubAckPacket subAckPacket)
{
return SerializeAsync(subAckPacket, destination);
}
if (packet is MqttUnsubscribePacket unsubscribePacket)
{
return SerializeAsync(unsubscribePacket, destination);
}
if (packet is MqttUnsubAckPacket unsubAckPacket)
{
return SerializeAsync(unsubAckPacket, destination);
}
throw new MqttProtocolViolationException("Packet type invalid.");
}
public async Task<MqttBasePacket> DeserializeAsync(IMqttCommunicationChannel source)
{
if (source == null) throw new ArgumentNullException(nameof(source));
using (var mqttPacketReader = new MqttPacketReader(source))
{
await mqttPacketReader.ReadToEndAsync();
switch (mqttPacketReader.ControlPacketType)
{
case MqttControlPacketType.Connect:
{
return await DeserializeConnectAsync(mqttPacketReader);
}
case MqttControlPacketType.ConnAck:
{
return await DeserializeConnAck(mqttPacketReader);
}
case MqttControlPacketType.Disconnect:
{
return new MqttDisconnectPacket();
}
case MqttControlPacketType.Publish:
{
return await DeserializePublishAsync(mqttPacketReader);
}
case MqttControlPacketType.PubAck:
{
return new MqttPubAckPacket
{
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
};
}
case MqttControlPacketType.PubRec:
{
return new MqttPubRecPacket
{
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
};
}
case MqttControlPacketType.PubRel:
{
return new MqttPubRelPacket
{
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
};
}
case MqttControlPacketType.PubComp:
{
return new MqttPubCompPacket
{
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
};
}
case MqttControlPacketType.PingReq:
{
return new MqttPingReqPacket();
}
case MqttControlPacketType.PingResp:
{
return new MqttPingRespPacket();
}
case MqttControlPacketType.Subscribe:
{
return await DeserializeSubscribeAsync(mqttPacketReader);
}
case MqttControlPacketType.SubAck:
{
return await DeserializeSubAck(mqttPacketReader);
}
case MqttControlPacketType.Unsubscibe:
{
return await DeserializeUnsubscribeAsync(mqttPacketReader);
}
case MqttControlPacketType.UnsubAck:
{
return new MqttUnsubAckPacket
{
PacketIdentifier = await mqttPacketReader.ReadRemainingDataUShortAsync()
};
}
default:
{
throw new MqttProtocolViolationException($"Packet type ({(int)mqttPacketReader.ControlPacketType}) not supported.");
}
}
}
}
private static async Task<MqttBasePacket> DeserializeUnsubscribeAsync(MqttPacketReader reader)
{
var packet = new MqttUnsubscribePacket
{
PacketIdentifier = await reader.ReadRemainingDataUShortAsync(),
};
while (!reader.EndOfRemainingData)
{
packet.TopicFilters.Add(await reader.ReadRemainingDataStringWithLengthPrefixAsync());
}
return packet;
}
private static async Task<MqttBasePacket> DeserializeSubscribeAsync(MqttPacketReader reader)
{
var packet = new MqttSubscribePacket
{
PacketIdentifier = await reader.ReadRemainingDataUShortAsync(),
};
while (!reader.EndOfRemainingData)
{
packet.TopicFilters.Add(new TopicFilter(
await reader.ReadRemainingDataStringWithLengthPrefixAsync(),
(MqttQualityOfServiceLevel)await reader.ReadRemainingDataByteAsync()));
}
return packet;
}
private static async Task<MqttBasePacket> DeserializePublishAsync(MqttPacketReader reader)
{
var fixedHeader = new ByteReader(reader.FixedHeader);
var retain = fixedHeader.Read();
var qualityOfServiceLevel = (MqttQualityOfServiceLevel)fixedHeader.Read(2);
var dup = fixedHeader.Read();
var topic = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
ushort packetIdentifier = 0;
if (qualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
packetIdentifier = await reader.ReadRemainingDataUShortAsync();
}
var packet = new MqttPublishPacket
{
Retain = retain,
QualityOfServiceLevel = qualityOfServiceLevel,
Dup = dup,
Topic = topic,
Payload = await reader.ReadRemainingDataAsync(),
PacketIdentifier = packetIdentifier
};
return packet;
}
private static async Task<MqttBasePacket> DeserializeConnectAsync(MqttPacketReader reader)
{
await reader.ReadRemainingDataAsync(2); // Skip 2 bytes
var protocolName = await reader.ReadRemainingDataAsync(4);
if (Encoding.UTF8.GetString(protocolName, 0, protocolName.Length) != "MQTT")
{
throw new MqttProtocolViolationException("Protocol name is not 'MQTT'.");
}
var protocolLevel = await reader.ReadRemainingDataByteAsync();
var connectFlags = await reader.ReadRemainingDataByteAsync();
var connectFlagsReader = new ByteReader(connectFlags);
connectFlagsReader.Read(); // Reserved.
var packet = new MqttConnectPacket
{
CleanSession = connectFlagsReader.Read()
};
var willFlag = connectFlagsReader.Read();
var willQoS = connectFlagsReader.Read(2);
var willRetain = connectFlagsReader.Read();
var passwordFlag = connectFlagsReader.Read();
var usernameFlag = connectFlagsReader.Read();
packet.KeepAlivePeriod = await reader.ReadRemainingDataUShortAsync();
packet.ClientId = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
if (willFlag)
{
packet.WillMessage = new MqttApplicationMessage(
await reader.ReadRemainingDataStringWithLengthPrefixAsync(),
await reader.ReadRemainingDataWithLengthPrefixAsync(),
(MqttQualityOfServiceLevel)willQoS,
willRetain);
}
if (usernameFlag)
{
packet.Username = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
}
if (passwordFlag)
{
packet.Password = await reader.ReadRemainingDataStringWithLengthPrefixAsync();
}
ValidateConnectPacket(packet);
return packet;
}
private static async Task<MqttBasePacket> DeserializeSubAck(MqttPacketReader reader)
{
var packet = new MqttSubAckPacket
{
PacketIdentifier = await reader.ReadRemainingDataUShortAsync()
};
while (!reader.EndOfRemainingData)
{
packet.SubscribeReturnCodes.Add((MqttSubscribeReturnCode)await reader.ReadRemainingDataByteAsync());
}
return packet;
}
private static async Task<MqttBasePacket> DeserializeConnAck(MqttPacketReader reader)
{
var variableHeader1 = await reader.ReadRemainingDataByteAsync();
var variableHeader2 = await reader.ReadRemainingDataByteAsync();
var packet = new MqttConnAckPacket
{
IsSessionPresent = new ByteReader(variableHeader1).Read(),
ConnectReturnCode = (MqttConnectReturnCode)variableHeader2
};
return packet;
}
private static void ValidateConnectPacket(MqttConnectPacket packet)
{
if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession)
{
throw new MqttProtocolViolationException("CleanSession must be set if ClientId is empty [MQTT-3.1.3-7].");
}
}
private static void ValidatePublishPacket(MqttPublishPacket packet)
{
if (packet.QualityOfServiceLevel == 0 && packet.Dup)
{
throw new MqttProtocolViolationException("Dup flag must be false for QoS 0 packets [MQTT-3.3.1-2].");
}
}
private static Task SerializeAsync(MqttConnectPacket packet, IMqttCommunicationChannel destination)
{
ValidateConnectPacket(packet);
using (var output = new MqttPacketWriter())
{
// Write variable header
output.Write(0x00, 0x04); // 3.1.2.1 Protocol Name
output.Write(MqttV311Prefix);
output.Write(0x04); // 3.1.2.2 Protocol Level
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);
}
}
private static Task SerializeAsync(MqttConnAckPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
var connectAcknowledgeFlags = new ByteWriter();
connectAcknowledgeFlags.Write(packet.IsSessionPresent);
output.Write(connectAcknowledgeFlags);
output.Write((byte)packet.ConnectReturnCode);
output.InjectFixedHeader(MqttControlPacketType.ConnAck);
return output.WriteToAsync(destination);
}
}
private static Task SerializeAsync(MqttDisconnectPacket packet, IMqttCommunicationChannel destination)
{
return SerializeEmptyPacketAsync(MqttControlPacketType.Disconnect, destination);
}
private static Task SerializeAsync(MqttPingReqPacket packet, IMqttCommunicationChannel destination)
{
return SerializeEmptyPacketAsync(MqttControlPacketType.PingReq, destination);
}
private static Task SerializeAsync(MqttPingRespPacket packet, IMqttCommunicationChannel destination)
{
return SerializeEmptyPacketAsync(MqttControlPacketType.PingResp, destination);
}
private static Task SerializeAsync(MqttPublishPacket packet, IMqttCommunicationChannel destination)
{
ValidatePublishPacket(packet);
using (var output = new MqttPacketWriter())
{
output.WriteWithLengthPrefix(packet.Topic);
if (packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
output.Write(packet.PacketIdentifier);
}
else
{
if (packet.PacketIdentifier > 0)
{
throw new MqttProtocolViolationException("Packet identifier must be empty if QoS == 0 [MQTT-2.3.1-5].");
}
}
if (packet.Payload?.Length > 0)
{
output.Write(packet.Payload);
}
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);
}
}
private static Task SerializeAsync(MqttPubAckPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
output.InjectFixedHeader(MqttControlPacketType.PubAck);
return output.WriteToAsync(destination);
}
}
private static Task SerializeAsync(MqttPubRecPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
output.InjectFixedHeader(MqttControlPacketType.PubRec);
return output.WriteToAsync(destination);
}
}
private static async Task SerializeAsync(MqttPubRelPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
output.InjectFixedHeader(MqttControlPacketType.PubRel, 0x02);
await output.WriteToAsync(destination);
}
}
private static Task SerializeAsync(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)
{
output.WriteWithLengthPrefix(topicFilter.Topic);
output.Write((byte)topicFilter.QualityOfServiceLevel);
}
}
output.InjectFixedHeader(MqttControlPacketType.Subscribe, 0x02);
return output.WriteToAsync(destination);
}
}
private static Task SerializeAsync(MqttSubAckPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
if (packet.SubscribeReturnCodes?.Any() == true)
{
foreach (var packetSubscribeReturnCode in packet.SubscribeReturnCodes)
{
output.Write((byte)packetSubscribeReturnCode);
}
}
output.InjectFixedHeader(MqttControlPacketType.SubAck);
return output.WriteToAsync(destination);
}
}
private static Task SerializeAsync(MqttUnsubscribePacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
if (packet.TopicFilters?.Any() == true)
{
foreach (var topicFilter in packet.TopicFilters)
{
output.WriteWithLengthPrefix(topicFilter);
}
}
output.InjectFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
return output.WriteToAsync(destination);
}
}
private static Task SerializeAsync(MqttUnsubAckPacket packet, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.Write(packet.PacketIdentifier);
output.InjectFixedHeader(MqttControlPacketType.UnsubAck);
return output.WriteToAsync(destination);
}
}
private static Task SerializeEmptyPacketAsync(MqttControlPacketType type, IMqttCommunicationChannel destination)
{
using (var output = new MqttPacketWriter())
{
output.InjectFixedHeader(type);
return output.WriteToAsync(destination);
}
}
}
}
@@ -0,0 +1,11 @@
using MQTTnet.Core.Serializer;
namespace MQTTnet.Core.Server
{
public class ConnectedMqttClient
{
public string ClientId { get; set; }
public MqttProtocolVersion ProtocolVersion { get; set; }
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ namespace MQTTnet.Core.Server
event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
IList<string> GetConnectedClients();
IList<ConnectedMqttClient> GetConnectedClients();
void InjectClient(string identifier, IMqttCommunicationAdapter adapter);
void Publish(MqttApplicationMessage applicationMessage);
void Start();
@@ -0,0 +1,7 @@
namespace MQTTnet.Core.Server
{
public interface IMqttServerFactory
{
IMqttServer CreateMqttServer(MqttServerOptions options);
}
}
+31 -76
View File
@@ -1,24 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
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.Internal;
using MQTTnet.Core.Packets;
using System.Linq;
namespace MQTTnet.Core.Server
{
public sealed class MqttClientMessageQueue
{
private readonly List<MqttClientPublishPacketContext> _pendingPublishPackets = new List<MqttClientPublishPacketContext>();
private readonly AsyncGate _gate = new AsyncGate();
private readonly BlockingCollection<MqttPublishPacket> _pendingPublishPackets = new BlockingCollection<MqttPublishPacket>();
private readonly MqttServerOptions _options;
private CancellationTokenSource _cancellationTokenSource;
private IMqttCommunicationAdapter _adapter;
public MqttClientMessageQueue(MqttServerOptions options)
{
@@ -32,102 +29,60 @@ namespace MQTTnet.Core.Server
throw new InvalidOperationException($"{nameof(MqttClientMessageQueue)} already started.");
}
_adapter = adapter ?? throw new ArgumentNullException(nameof(adapter));
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
_cancellationTokenSource = new CancellationTokenSource();
Task.Run(() => SendPendingPublishPacketsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
Task.Run(() => SendPendingPublishPacketsAsync(_cancellationTokenSource.Token, adapter), _cancellationTokenSource.Token);
}
public void Stop()
{
_adapter = null;
_cancellationTokenSource?.Cancel();
_cancellationTokenSource = null;
_pendingPublishPackets?.Dispose();
}
public void Enqueue(MqttPublishPacket publishPacket)
{
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
lock (_pendingPublishPackets)
{
_pendingPublishPackets.Add(new MqttClientPublishPacketContext(publishPacket));
_gate.Set();
}
_pendingPublishPackets.Add(publishPacket);
}
private async Task SendPendingPublishPacketsAsync(CancellationToken cancellationToken)
private async Task SendPendingPublishPacketsAsync(CancellationToken cancellationToken, IMqttCommunicationAdapter adapter)
{
var consumable = _pendingPublishPackets.GetConsumingEnumerable();
while (!cancellationToken.IsCancellationRequested)
{
if (_pendingPublishPackets.Count == 0)
{
await Task.Delay(TimeSpan.FromMilliseconds(5), cancellationToken).ConfigureAwait(false);
continue;
}
var packets = consumable.Take(_pendingPublishPackets.Count).ToList();
try
{
await _gate.WaitOneAsync();
if (cancellationToken.IsCancellationRequested)
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packets).ConfigureAwait(false);
}
catch (MqttCommunicationException exception)
{
MqttTrace.Warning(nameof(MqttClientMessageQueue), exception, "Sending publish packet failed.");
foreach (var publishPacket in packets)
{
return;
}
if (_adapter == null)
{
continue;
}
List<MqttClientPublishPacketContext> pendingPublishPackets;
lock (_pendingPublishPackets)
{
pendingPublishPackets = _pendingPublishPackets.ToList();
}
foreach (var publishPacket in pendingPublishPackets)
{
await TrySendPendingPublishPacketAsync(publishPacket);
publishPacket.Dup = true;
_pendingPublishPackets.Add(publishPacket, cancellationToken);
}
}
catch (Exception e)
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttClientMessageQueue), e, "Error while sending pending publish packets.");
MqttTrace.Error(nameof(MqttClientMessageQueue), exception, "Sending publish packet failed.");
foreach (var publishPacket in packets)
{
publishPacket.Dup = true;
_pendingPublishPackets.Add(publishPacket, cancellationToken);
}
}
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);
}
}
}
@@ -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; }
}
}
+18 -17
View File
@@ -21,7 +21,6 @@ namespace MQTTnet.Core.Server
private readonly MqttServerOptions _options;
private CancellationTokenSource _cancellationTokenSource;
private IMqttCommunicationAdapter _adapter;
private string _identifier;
private MqttApplicationMessage _willApplicationMessage;
@@ -36,7 +35,9 @@ namespace MQTTnet.Core.Server
public string ClientId { get; }
public bool IsConnected => _adapter != null;
public bool IsConnected => Adapter != null;
public IMqttCommunicationAdapter Adapter { get; private set; }
public async Task RunAsync(string identifier, MqttApplicationMessage willApplicationMessage, IMqttCommunicationAdapter adapter)
{
@@ -47,14 +48,14 @@ namespace MQTTnet.Core.Server
try
{
_identifier = identifier;
_adapter = adapter;
Adapter = adapter;
_cancellationTokenSource = new CancellationTokenSource();
_messageQueue.Start(adapter);
while (!_cancellationTokenSource.IsCancellationRequested)
{
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero);
await HandleIncomingPacketAsync(packet);
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, _cancellationTokenSource.Token).ConfigureAwait(false);
await HandleIncomingPacketAsync(packet).ConfigureAwait(false);
}
}
catch (MqttCommunicationException)
@@ -62,7 +63,7 @@ namespace MQTTnet.Core.Server
}
catch (Exception exception)
{
MqttTrace.Error(nameof(MqttClientSession), exception, $"Client '{_identifier}': Unhandled exception while processing client packets.");
MqttTrace.Error(nameof(MqttClientSession), exception, "Client '{0}': Unhandled exception while processing client packets.", _identifier);
}
finally
{
@@ -73,9 +74,9 @@ namespace MQTTnet.Core.Server
_messageQueue.Stop();
_cancellationTokenSource.Cancel();
_adapter = null;
Adapter = null;
MqttTrace.Information(nameof(MqttClientSession), $"Client '{_identifier}': Disconnected.");
MqttTrace.Information(nameof(MqttClientSession), "Client '{0}': Disconnected.", _identifier);
}
}
@@ -89,7 +90,7 @@ namespace MQTTnet.Core.Server
}
_messageQueue.Enqueue(publishPacket);
MqttTrace.Verbose(nameof(MqttClientSession), $"Client '{_identifier}: Enqueued pending publish packet.");
MqttTrace.Verbose(nameof(MqttClientSession), "Client '{0}': Enqueued pending publish packet.", _identifier);
}
public void Dispose()
@@ -102,12 +103,12 @@ namespace MQTTnet.Core.Server
{
if (packet is MqttSubscribePacket subscribePacket)
{
return _adapter.SendPacketAsync(_subscriptionsManager.Subscribe(subscribePacket), _options.DefaultCommunicationTimeout);
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, _subscriptionsManager.Subscribe(subscribePacket));
}
if (packet is MqttUnsubscribePacket unsubscribePacket)
{
return _adapter.SendPacketAsync(_subscriptionsManager.Unsubscribe(unsubscribePacket), _options.DefaultCommunicationTimeout);
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, _subscriptionsManager.Unsubscribe(unsubscribePacket));
}
if (packet is MqttPublishPacket publishPacket)
@@ -122,7 +123,7 @@ namespace MQTTnet.Core.Server
if (packet is MqttPubRecPacket pubRecPacket)
{
return _adapter.SendPacketAsync(pubRecPacket.CreateResponse<MqttPubRelPacket>(), _options.DefaultCommunicationTimeout);
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, pubRecPacket.CreateResponse<MqttPubRelPacket>());
}
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
@@ -133,7 +134,7 @@ namespace MQTTnet.Core.Server
if (packet is MqttPingReqPacket)
{
return _adapter.SendPacketAsync(new MqttPingRespPacket(), _options.DefaultCommunicationTimeout);
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPingRespPacket());
}
if (packet is MqttDisconnectPacket || packet is MqttConnectPacket)
@@ -142,7 +143,7 @@ namespace MQTTnet.Core.Server
return Task.FromResult((object)null);
}
MqttTrace.Warning(nameof(MqttClientSession), $"Client '{_identifier}': Received not supported packet ({packet}). Closing connection.");
MqttTrace.Warning(nameof(MqttClientSession), "Client '{0}': Received not supported packet ({1}). Closing connection.", _identifier, packet);
_cancellationTokenSource.Cancel();
return Task.FromResult((object)null);
@@ -159,7 +160,7 @@ namespace MQTTnet.Core.Server
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
_publishPacketReceivedCallback(this, publishPacket);
return _adapter.SendPacketAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
@@ -172,7 +173,7 @@ namespace MQTTnet.Core.Server
_publishPacketReceivedCallback(this, publishPacket);
return _adapter.SendPacketAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier });
}
throw new MqttCommunicationException("Received a not supported QoS level.");
@@ -185,7 +186,7 @@ namespace MQTTnet.Core.Server
_unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier);
}
return _adapter.SendPacketAsync(new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout);
return Adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, _cancellationTokenSource.Token, new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier });
}
}
}
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Diagnostics;
@@ -28,32 +29,34 @@ namespace MQTTnet.Core.Server
{
try
{
var connectPacket = await eventArgs.ClientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout) as MqttConnectPacket;
if (connectPacket == null)
if (!(await eventArgs.ClientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false) is MqttConnectPacket connectPacket))
{
throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
}
// Switch to the required protocol version before sending any response.
eventArgs.ClientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
var connectReturnCode = ValidateConnection(connectPacket);
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
{
await eventArgs.ClientAdapter.SendPacketAsync(new MqttConnAckPacket
await eventArgs.ClientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
{
ConnectReturnCode = connectReturnCode
}, _options.DefaultCommunicationTimeout);
}).ConfigureAwait(false);
return;
}
var clientSession = GetOrCreateClientSession(connectPacket);
await eventArgs.ClientAdapter.SendPacketAsync(new MqttConnAckPacket
await eventArgs.ClientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, CancellationToken.None, new MqttConnAckPacket
{
ConnectReturnCode = connectReturnCode,
IsSessionPresent = clientSession.IsExistingSession
}, _options.DefaultCommunicationTimeout);
}).ConfigureAwait(false);
await clientSession.Session.RunAsync(eventArgs.Identifier, connectPacket.WillMessage, eventArgs.ClientAdapter);
await clientSession.Session.RunAsync(eventArgs.Identifier, connectPacket.WillMessage, eventArgs.ClientAdapter).ConfigureAwait(false);
}
catch (Exception exception)
{
@@ -61,7 +64,7 @@ namespace MQTTnet.Core.Server
}
finally
{
await eventArgs.ClientAdapter.DisconnectAsync();
await eventArgs.ClientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
}
}
@@ -73,11 +76,15 @@ namespace MQTTnet.Core.Server
}
}
public IList<string> GetConnectedClients()
public IList<ConnectedMqttClient> GetConnectedClients()
{
lock (_syncRoot)
{
return _clientSessions.Where(s => s.Value.IsConnected).Select(s => s.Key).ToList();
return _clientSessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
{
ClientId = s.Value.ClientId,
ProtocolVersion = s.Value.Adapter.PacketSerializer.ProtocolVersion
}).ToList();
}
}
@@ -104,11 +111,11 @@ namespace MQTTnet.Core.Server
_clientSessions.Remove(connectPacket.ClientId);
clientSession.Dispose();
clientSession = null;
MqttTrace.Verbose(nameof(MqttClientSessionsManager), $"Disposed existing session of client '{connectPacket.ClientId}'.");
MqttTrace.Verbose(nameof(MqttClientSessionsManager), "Disposed existing session of client '{0}'.", connectPacket.ClientId);
}
else
{
MqttTrace.Verbose(nameof(MqttClientSessionsManager), $"Reusing existing session of client '{connectPacket.ClientId}'.");
MqttTrace.Verbose(nameof(MqttClientSessionsManager), "Reusing existing session of client '{0}'.", connectPacket.ClientId);
}
}
@@ -120,7 +127,7 @@ namespace MQTTnet.Core.Server
clientSession = new MqttClientSession(connectPacket.ClientId, _options, DispatchPublishPacket);
_clientSessions[connectPacket.ClientId] = clientSession;
MqttTrace.Verbose(nameof(MqttClientSessionsManager), $"Created a new session for client '{connectPacket.ClientId}'.");
MqttTrace.Verbose(nameof(MqttClientSessionsManager), "Created a new session for client '{0}'.", connectPacket.ClientId);
}
return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
+4 -4
View File
@@ -20,12 +20,12 @@ namespace MQTTnet.Core.Server
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_adapters = adapters ?? throw new ArgumentNullException(nameof(adapters));
_clientSessionsManager = new MqttClientSessionsManager(options);
_clientSessionsManager.ApplicationMessageReceived += (s, e) => ApplicationMessageReceived?.Invoke(s, e);
}
public IList<string> GetConnectedClients()
public IList<ConnectedMqttClient> GetConnectedClients()
{
return _clientSessionsManager.GetConnectedClients();
}
@@ -61,7 +61,7 @@ namespace MQTTnet.Core.Server
adapter.ClientConnected += OnClientConnected;
adapter.Start(_options);
}
MqttTrace.Information(nameof(MqttServer), "Started.");
}
@@ -84,7 +84,7 @@ namespace MQTTnet.Core.Server
private void OnClientConnected(object sender, MqttClientConnectedEventArgs eventArgs)
{
MqttTrace.Information(nameof(MqttServer), $"Client '{eventArgs.Identifier}': Connected.");
MqttTrace.Information(nameof(MqttServer), "Client '{0}': Connected.", eventArgs.Identifier);
ClientConnected?.Invoke(this, eventArgs);
Task.Run(() => _clientSessionsManager.RunClientSessionAsync(eventArgs), _cancellationTokenSource.Token);
+1 -1
View File
@@ -9,7 +9,7 @@ namespace MQTTnet.Core.Server
public MqttServerDefaultEndpointOptions DefaultEndpointOptions { get; } = new MqttServerDefaultEndpointOptions();
public MqttServerTlsEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsEndpointOptions();
public int ConnectionBacklog { get; set; } = 10;
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15);
+40 -13
View File
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.15
VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Core.Tests", "Tests\MQTTnet.Core.Tests\MQTTnet.Core.Tests.csproj", "{A7FF0C91-25DE-4BA6-B39E-F54E8DADF1CC}"
EndProject
@@ -27,6 +27,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{67C28AC1
Build\MQTTnet.nuspec = Build\MQTTnet.nuspec
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp.NetCore", "Tests\MQTTnet.TestApp.NetCore\MQTTnet.TestApp.NetCore.csproj", "{3D283AAD-AAA8-4339-8394-52F80B6304DB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -91,18 +98,18 @@ Global
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Debug|ARM.ActiveCfg = Debug|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Debug|ARM.Build.0 = Debug|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Debug|x64.ActiveCfg = Debug|x64
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Debug|x64.Build.0 = Debug|x64
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Debug|x86.ActiveCfg = Debug|x86
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Debug|x86.Build.0 = Debug|x86
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Debug|x64.ActiveCfg = Debug|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Debug|x64.Build.0 = Debug|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Debug|x86.ActiveCfg = Debug|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Debug|x86.Build.0 = Debug|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|Any CPU.Build.0 = Release|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|ARM.ActiveCfg = Release|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|ARM.Build.0 = Release|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x64.ActiveCfg = Release|x64
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x64.Build.0 = Release|x64
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x86.ActiveCfg = Release|x86
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x86.Build.0 = Release|x86
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x64.ActiveCfg = Release|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x64.Build.0 = Release|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x86.ActiveCfg = Release|Any CPU
{2ECB99E4-72D0-4C23-99BA-93D511D3967D}.Release|x86.Build.0 = Release|Any CPU
{D9D74F33-6943-49B2-B765-7BD589082098}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9D74F33-6943-49B2-B765-7BD589082098}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9D74F33-6943-49B2-B765-7BD589082098}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -145,10 +152,10 @@ Global
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|ARM.ActiveCfg = Debug|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|ARM.Build.0 = Debug|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|x64.ActiveCfg = Debug|x64
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|x64.Build.0 = Debug|x64
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|x86.ActiveCfg = Debug|x86
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|x86.Build.0 = Debug|x86
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|x64.ActiveCfg = Debug|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|x64.Build.0 = Debug|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|x86.ActiveCfg = Debug|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|x86.Build.0 = Debug|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|Any CPU.Build.0 = Release|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|ARM.ActiveCfg = Release|Any CPU
@@ -157,6 +164,22 @@ Global
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|x64.Build.0 = Release|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|x86.ActiveCfg = Release|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|x86.Build.0 = Release|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|ARM.ActiveCfg = Debug|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|ARM.Build.0 = Debug|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|x64.ActiveCfg = Debug|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|x64.Build.0 = Debug|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|x86.ActiveCfg = Debug|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Debug|x86.Build.0 = Debug|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|Any CPU.Build.0 = Release|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|ARM.ActiveCfg = Release|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|ARM.Build.0 = Release|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x64.ActiveCfg = Release|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x64.Build.0 = Release|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x86.ActiveCfg = Release|Any CPU
{3D283AAD-AAA8-4339-8394-52F80B6304DB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -168,5 +191,9 @@ Global
{D9D74F33-6943-49B2-B765-7BD589082098} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
{FF1F72D6-9524-4422-9497-3CC0002216ED} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
{3587E506-55A2-4EB3-99C7-DC01E42D25D2} = {32A630A7-2598-41D7-B625-204CD906F5FB}
{3D283AAD-AAA8-4339-8394-52F80B6304DB} = {9248C2E1-B9D6-40BF-81EC-86004D7765B4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
EndGlobalSection
EndGlobal
+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>
+36 -33
View File
@@ -3,42 +3,54 @@
</p>
[![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet)
[![BCH compliance](https://bettercodehub.com/edge/badge/chkr1011/MQTTnet?branch=master)](https://bettercodehub.com/)
# MQTTnet
MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server. The implementation is based on the documentation from http://mqtt.org/.
MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). The implementation is based on the documentation from http://mqtt.org/.
## Features
* MQTT client included
* MQTT server (broker) included
* TLS 1.2 support for client and server (but not UWP servers)
# Features
### General
* Async support
* Rx support (via another project)
* List of connected clients available (server only)
* Extensible communication channels (i.e. In-Memory, TCP, TCP+SSL, WebSockets (not included in this project))
* Server is able to publish its own messages (no loopback client required)
* Access to internal trace messages
* Extensible client credential validation (server only)
* Unit tested (50+ tests)
* TLS 1.2 support for client and server (but not UWP servers)
* Extensible communication channels (i.e. In-Memory, TCP, TCP+TLS, WS)
* Lightweight (only the low level implementation of MQTT, no overhead)
* Performance optimized (processing ~27.000 messages / second)*
* Interfaces included for mocking and testing
* Access to internal trace messages
* Unit tested (57+ tests)
## Supported frameworks
\* Tested on local machine with MQTTnet client and server running in the same process using the TCP channel. The app for verification is part of this repository and stored in _/Tests/MQTTnet.TestApp.NetFramework_.
### Client
* Rx support (via another project)
* Communication via TCP (+TLS) or WS (WebSocket)
### Server (broker)
* List of connected clients available
* Supports connected clients with different protocol versions at the same time
* Able to publish its own messages (no loopback client required)
* Able to receive every messages (no loopback client required)
* Extensible client credential validation
# Supported frameworks
* .NET Standard 1.3+
* .NET Core 1.1+
* .NET Core App 1.1+
* .NET Framework 4.5.2+ (x86, x64, AnyCPU)
* Universal Windows (UWP) 10.0.10240+ (x86, x64, ARM, AnyCPU)
## Supported MQTT versions
# Supported MQTT versions
* 3.1.1
* 3.1.0
## Nuget
# Nuget
This library is available as a nuget package: https://www.nuget.org/packages/MQTTnet/
## Contributions
# Contributions
If you want to contribute to this project just create a pull request.
## References
# References
This library is used in the following projects:
* MQTT Client Rx (Wrapper for Reactive Extensions, https://github.com/1iveowl/MQTTClient.rx)
@@ -46,8 +58,8 @@ This library is used in the following projects:
If you use this library and want to see your project here please let me know.
# MqttClient
## Example
# Examples
## MqttClient
```csharp
var options = new MqttClientOptions
@@ -68,14 +80,12 @@ client.ApplicationMessageReceived += (s, e) =>
client.Connected += async (s, e) =>
{
Console.WriteLine("### CONNECTED WITH SERVER ###");
Console.WriteLine("### CONNECTED WITH SERVER, SUBSCRIBING ###");
await client.SubscribeAsync(new List<TopicFilter>
{
new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)
});
Console.WriteLine("### SUBSCRIBED ###");
};
client.Disconnected += async (s, e) =>
@@ -104,24 +114,17 @@ catch
Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###");
var messageFactory = new MqttApplicationMessageFactory();
while (true)
{
Console.ReadLine();
var applicationMessage = new MqttApplicationMessage(
"A/B/C",
Encoding.UTF8.GetBytes("Hello World"),
MqttQualityOfServiceLevel.AtLeastOnce,
false
);
var applicationMessage = messageFactory.CreateApplicationMessage("myTopic", "Hello World", MqttQualityOfServiceLevel.AtLeastOnce);
await client.PublishAsync(applicationMessage);
}
```
# MqttServer
## Example
## MqttServer
```csharp
var options = new MqttServerOptions
@@ -147,4 +150,4 @@ 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}");
}
}
}
}
@@ -86,7 +86,8 @@
<ItemGroup>
<Compile Include="ByteReaderTests.cs" />
<Compile Include="ByteWriterTests.cs" />
<Compile Include="DefaultMqttV311PacketSerializerTests.cs" />
<Compile Include="ExtensionTests.cs" />
<Compile Include="MqttPacketSerializerTests.cs" />
<Compile Include="MqttServerTests.cs" />
<Compile Include="MqttSubscriptionsManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@@ -1,8 +1,10 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Channel;
using MQTTnet.Core.Client;
using MQTTnet.Core.Packets;
@@ -12,8 +14,23 @@ using MQTTnet.Core.Serializer;
namespace MQTTnet.Core.Tests
{
[TestClass]
public class DefaultMqttV311PacketSerializerTests
public class MqttPacketSerializerTests
{
[TestMethod]
public void SerializeV310_MqttConnectPacket()
{
var p = new MqttConnectPacket
{
ClientId = "XYZ",
Password = "PASS",
Username = "USER",
KeepAlivePeriod = 123,
CleanSession = true
};
SerializeAndCompare(p, "EB0ABE1RSXNkcAPCAHsAA1hZWgAEVVNFUgAEUEFTUw==", MqttProtocolVersion.V310);
}
[TestMethod]
public void SerializeV311_MqttConnectPacket()
{
@@ -96,6 +113,17 @@ namespace MQTTnet.Core.Tests
SerializeAndCompare(p, "IAIBBQ==");
}
[TestMethod]
public void SerializeV310_MqttConnAckPacket()
{
var p = new MqttConnAckPacket
{
ConnectReturnCode = MqttConnectReturnCode.ConnectionAccepted
};
SerializeAndCompare(p, "IAIAAA==", MqttProtocolVersion.V310);
}
[TestMethod]
public void DeserializeV311_MqttConnAckPacket()
{
@@ -365,6 +393,12 @@ namespace MQTTnet.Core.Tests
{
private readonly MemoryStream _stream = new MemoryStream();
public Stream ReceiveStream => _stream;
public Stream RawReceiveStream => _stream;
public Stream SendStream => _stream;
public bool IsConnected { get; } = true;
public TestChannel()
@@ -377,24 +411,14 @@ namespace MQTTnet.Core.Tests
_stream.Position = 0;
}
public async Task ConnectAsync(MqttClientOptions options)
public Task ConnectAsync(MqttClientOptions options)
{
await Task.FromResult(0);
return Task.FromResult(0);
}
public async Task DisconnectAsync()
public Task DisconnectAsync()
{
await Task.FromResult(0);
}
public async Task WriteAsync(byte[] buffer)
{
await _stream.WriteAsync(buffer, 0, buffer.Length);
}
public async Task ReadAsync(byte[] buffer)
{
await _stream.ReadAsync(buffer, 0, buffer.Length);
return Task.FromResult(0);
}
public byte[] ToArray()
@@ -403,32 +427,32 @@ namespace MQTTnet.Core.Tests
}
}
private void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value)
private static void SerializeAndCompare(MqttBasePacket packet, string expectedBase64Value, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
{
var serializer = new MqttV311PacketSerializer();
var channel = new TestChannel();
serializer.SerializeAsync(packet, channel).Wait();
var buffer = channel.ToArray();
var serializer = new MqttPacketSerializer { ProtocolVersion = protocolVersion };
var buffer = serializer.Serialize(packet);
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(buffer));
}
private void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value)
private static void DeserializeAndCompare(MqttBasePacket packet, string expectedBase64Value)
{
var serializer = new MqttV311PacketSerializer();
var serializer = new MqttPacketSerializer();
var channel1 = new TestChannel();
serializer.SerializeAsync(packet, channel1).Wait();
var buffer1 = channel1.ToArray();
var buffer1 = serializer.Serialize(packet);
var channel2 = new TestChannel(buffer1);
var deserializedPacket = serializer.DeserializeAsync(channel2).Result;
var buffer2 = channel2.ToArray();
using (var headerStream = new MemoryStream(buffer1))
{
var header = MqttPacketReader.ReadHeaderFromSource(headerStream, CancellationToken.None);
var channel3 = new TestChannel(buffer2);
serializer.SerializeAsync(deserializedPacket, channel3).Wait();
using (var bodyStream = new MemoryStream(buffer1, (int)headerStream.Position, header.BodyLength))
{
var deserializedPacket = serializer.Deserialize(new ReceivedMqttPacket(header, bodyStream));
var buffer2 = serializer.Serialize(deserializedPacket);
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(channel3.ToArray()));
Assert.AreEqual(expectedBase64Value, Convert.ToBase64String(buffer2));
}
}
}
}
}
+11 -11
View File
@@ -13,36 +13,36 @@ namespace MQTTnet.Core.Tests
public class MqttServerTests
{
[TestMethod]
public async Task MqttServer_PublishSimple_AtMostOnce()
public void MqttServer_PublishSimple_AtMostOnce()
{
await TestPublishAsync(
TestPublishAsync(
"A/B/C",
MqttQualityOfServiceLevel.AtMostOnce,
"A/B/C",
MqttQualityOfServiceLevel.AtMostOnce,
1);
1).Wait();
}
[TestMethod]
public async Task MqttServer_PublishSimple_AtLeastOnce()
public void MqttServer_PublishSimple_AtLeastOnce()
{
await TestPublishAsync(
TestPublishAsync(
"A/B/C",
MqttQualityOfServiceLevel.AtLeastOnce,
"A/B/C",
MqttQualityOfServiceLevel.AtLeastOnce,
1);
1).Wait();
}
[TestMethod]
public async Task MqttServer_PublishSimple_ExactlyOnce()
public void MqttServer_PublishSimple_ExactlyOnce()
{
await TestPublishAsync(
TestPublishAsync(
"A/B/C",
MqttQualityOfServiceLevel.ExactlyOnce,
"A/B/C",
MqttQualityOfServiceLevel.ExactlyOnce,
1);
1).Wait();
}
[TestMethod]
@@ -91,7 +91,7 @@ namespace MQTTnet.Core.Tests
await Task.Delay(500);
Assert.AreEqual(1, receivedMessagesCount);
await c1.Unsubscribe("a");
await c1.UnsubscribeAsync("a");
await c2.PublishAsync(message);
await Task.Delay(500);
@@ -158,7 +158,7 @@ namespace MQTTnet.Core.Tests
await c2.PublishAsync(new MqttApplicationMessage(topic, new byte[0], qualityOfServiceLevel, false));
await Task.Delay(500);
await c1.Unsubscribe(topicFilter);
await c1.UnsubscribeAsync(topicFilter);
await Task.Delay(500);
@@ -1,9 +1,12 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Core.Adapter;
using MQTTnet.Core.Client;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Serializer;
namespace MQTTnet.Core.Tests
{
@@ -13,29 +16,35 @@ namespace MQTTnet.Core.Tests
public TestMqttCommunicationAdapter Partner { get; set; }
public async Task ConnectAsync(MqttClientOptions options, TimeSpan timeout)
public IMqttPacketSerializer PacketSerializer { get; } = new MqttPacketSerializer();
public Task ConnectAsync(TimeSpan timeout, MqttClientOptions options)
{
await Task.FromResult(0);
return Task.FromResult(0);
}
public async Task DisconnectAsync()
public Task DisconnectAsync(TimeSpan timeout)
{
await Task.FromResult(0);
return Task.FromResult(0);
}
public async Task SendPacketAsync(MqttBasePacket packet, TimeSpan timeout)
public Task SendPacketsAsync(TimeSpan timeout, CancellationToken cancellationToken, IEnumerable<MqttBasePacket> packets)
{
ThrowIfPartnerIsNull();
Partner.SendPacketInternal(packet);
await Task.FromResult(0);
foreach (var packet in packets)
{
Partner.SendPacketInternal(packet);
}
return Task.FromResult(0);
}
public async Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout)
public Task<MqttBasePacket> ReceivePacketAsync(TimeSpan timeout, CancellationToken cancellationToken)
{
ThrowIfPartnerIsNull();
return await Task.Run(() => _incomingPackets.Take());
return Task.Run(() => _incomingPackets.Take(), cancellationToken);
}
private void SendPacketInternal(MqttBasePacket packet)
@@ -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>
+171
View File
@@ -0,0 +1,171 @@
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)
{
MqttTrace.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 MqttClientOptions
{
Server = "localhost",
ClientId = "XYZ",
CleanSession = true,
ConnectionType = MqttConnectionType.Ws
};
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 (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)
{
MqttTrace.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.Start();
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
mqttServer.Stop();
}
catch (Exception e)
{
Console.WriteLine(e);
}
Console.ReadLine();
}
}
}
@@ -82,6 +82,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="PerformanceTest.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
@@ -90,7 +91,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Frameworks\MQTTnet.NetFramework\MQTTnet.NetFramework.csproj">
<Project>{A480EF90-0EAA-4D9A-B271-47A9C47F6F7D}</Project>
<Project>{a480ef90-0eaa-4d9a-b271-47a9c47f6f7d}</Project>
<Name>MQTTnet.NetFramework</Name>
</ProjectReference>
<ProjectReference Include="..\..\MQTTnet.Core\MQTTnet.Core.csproj">
@@ -0,0 +1,219 @@
using MQTTnet.Core;
using MQTTnet.Core.Client;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
using MQTTnet.Core.Server;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.TestApp.NetFramework
{
public static class PerformanceTest
{
public static async Task RunAsync()
{
var server = Task.Factory.StartNew(RunServerAsync, TaskCreationOptions.LongRunning);
var client = Task.Factory.StartNew(() => RunClientAsync(2000, TimeSpan.FromMilliseconds(10)), TaskCreationOptions.LongRunning);
await Task.WhenAll(server, client).ConfigureAwait(false);
}
private static Task RunClientsAsync(int msgChunkSize, TimeSpan interval)
{
return Task.WhenAll(Enumerable.Range(0, 3).Select(i => Task.Run(() => RunClientAsync(msgChunkSize, interval))));
}
private static async Task RunClientAsync(int msgChunkSize, TimeSpan interval)
{
try
{
var options = new MqttClientOptions
{
Server = "localhost",
ClientId = "XYZ",
CleanSession = true,
DefaultCommunicationTimeout = TimeSpan.FromMinutes(10)
};
var client = new MqttClientFactory().CreateMqttClient(options);
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();
}
catch
{
Console.WriteLine("### RECONNECTING FAILED ###");
}
};
try
{
await client.ConnectAsync();
}
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 void 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();
}
};
mqttServer.Start();
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
mqttServer.Stop();
}
catch (Exception e)
{
Console.WriteLine(e);
}
Console.ReadLine();
}
}
}
@@ -19,6 +19,7 @@ namespace MQTTnet.TestApp.NetFramework
Console.WriteLine("MQTTnet - TestApp.NetFramework");
Console.WriteLine("1 = Start client");
Console.WriteLine("2 = Start server");
Console.WriteLine("3 = Start performance test");
var pressedKey = Console.ReadKey(true);
if (pressedKey.Key == ConsoleKey.D1)
{
@@ -30,10 +31,16 @@ namespace MQTTnet.TestApp.NetFramework
Task.Run(() => RunServerAsync(args));
Thread.Sleep(Timeout.Infinite);
}
else if (pressedKey.Key == ConsoleKey.D3)
{
Task.Run(PerformanceTest.RunAsync);
Thread.Sleep(Timeout.Infinite);
}
}
private static async Task RunClientAsync(string[] arguments)
{
MqttTrace.TraceMessagePublished += (s, e) =>
{
Console.WriteLine($">> [{e.ThreadId}] [{e.Source}] [{e.Level}]: {e.Message}");
@@ -71,8 +78,6 @@ namespace MQTTnet.TestApp.NetFramework
{
new TopicFilter("#", MqttQualityOfServiceLevel.AtMostOnce)
});
Console.WriteLine("### SUBSCRIBED ###");
};
client.Disconnected += async (s, e) =>
@@ -101,17 +106,12 @@ namespace MQTTnet.TestApp.NetFramework
Console.WriteLine("### WAITING FOR APPLICATION MESSAGES ###");
var messageFactory = new MqttApplicationMessageFactory();
while (true)
{
Console.ReadLine();
var applicationMessage = new MqttApplicationMessage(
"A/B/C",
Encoding.UTF8.GetBytes("Hello World"),
MqttQualityOfServiceLevel.AtLeastOnce,
false
);
var applicationMessage = messageFactory.CreateApplicationMessage("myTopic", "Hello World", MqttQualityOfServiceLevel.AtLeastOnce);
await client.PublishAsync(applicationMessage);
}
}
@@ -2,7 +2,5 @@
x:Class="MQTTnet.TestApp.UniversalWindows.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MQTTnet.TestApp.UniversalWindows"
RequestedTheme="Light">
</Application>
@@ -18,7 +18,7 @@
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WindowsXamlEnableOverview>true</WindowsXamlEnableOverview>
<PackageCertificateKeyFile>MQTTnet.TestApp.UniversalWindows_TemporaryKey.pfx</PackageCertificateKeyFile>
<PackageCertificateThumbprint>C5051872F588878B167FB976FDF249518AA2DDF0</PackageCertificateThumbprint>
<PackageCertificateThumbprint>13E377A693C923EE9E88EE84B32A4B9881657362</PackageCertificateThumbprint>
<RuntimeIdentifiers>win10;win10-arm;win10-arm-aot;win10-x86;win10-x86-aot;win10-x64;win10-x64-aot</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
@@ -42,7 +42,7 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
<UseDotNetNativeToolchain>false</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<DebugSymbols>true</DebugSymbols>
@@ -137,7 +137,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>5.3.3</Version>
<Version>5.4.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls">
<Version>2.0.0</Version>
</PackageReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
@@ -2,9 +2,10 @@
x:Class="MQTTnet.TestApp.UniversalWindows.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MQTTnet.TestApp.UniversalWindows"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="800"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid Margin="10">
@@ -12,28 +13,86 @@
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock>Server:</TextBlock>
<TextBox x:Name="Server"></TextBox>
<TextBlock>User:</TextBlock>
<TextBox x:Name="User"></TextBox>
<TextBlock>Password:</TextBlock>
<TextBox x:Name="Password"></TextBox>
<TextBlock>ClientId:</TextBlock>
<TextBox x:Name="ClientId"></TextBox>
<Pivot Grid.Row="0" >
<PivotItem Header="Connection">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock>Server:</TextBlock>
<TextBox x:Name="Server"></TextBox>
<TextBlock>User:</TextBlock>
<TextBox x:Name="User"></TextBox>
<TextBlock>Password:</TextBlock>
<TextBox x:Name="Password"></TextBox>
<TextBlock>ClientId:</TextBlock>
<TextBox x:Name="ClientId"></TextBox>
<CheckBox x:Name="UseTls">Use TLS</CheckBox>
<Button Click="Connect">Connect</Button>
<TextBlock>Trace:</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<RadioButton x:Name="UseTcp" IsChecked="True" GroupName="connection">TCP</RadioButton>
<RadioButton x:Name="UseWs" GroupName="connection">WS</RadioButton>
<CheckBox x:Name="UseTls">Use TLS</CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Click="Connect" Width="120" Margin="0,0,10,0">Connect</Button>
<Button Click="Disconnect" Width="120">Disconnect</Button>
</StackPanel>
</StackPanel>
<ScrollViewer Grid.Row="1" Background="Black" Foreground="WhiteSmoke" FontFamily="Consolas" HorizontalScrollMode="Enabled" VerticalScrollMode="Enabled" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<TextBlock x:Name="Trace"></TextBlock>
</PivotItem>
<PivotItem Header="Publish">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock>Topic:</TextBlock>
<TextBox x:Name="Topic"></TextBox>
<TextBlock>Payload:</TextBlock>
<TextBox x:Name="Payload"></TextBox>
<StackPanel Orientation="Horizontal">
<RadioButton x:Name="Text" IsChecked="True" GroupName="payload">Text</RadioButton>
<RadioButton x:Name="Base64" GroupName="payload">Base64</RadioButton>
</StackPanel>
<TextBlock>Retain:</TextBlock>
<CheckBox x:Name="Retain"></CheckBox>
<TextBlock>QoS:</TextBlock>
<StackPanel Orientation="Horizontal">
<RadioButton Margin="0,0,10,0" x:Name="QoS0" IsChecked="True" GroupName="qos">0 (At most once)</RadioButton>
<RadioButton Margin="0,0,10,0" x:Name="QoS1" GroupName="qos">1 (At least once)</RadioButton>
<RadioButton Margin="0,0,10,0" x:Name="QoS2" GroupName="qos">2 (Exactly once)</RadioButton>
</StackPanel>
<Button Click="Publish" Width="120">Publish</Button>
</StackPanel>
</PivotItem>
<PivotItem Header="Subscribe">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock>Topic:</TextBlock>
<TextBox x:Name="SubscribeTopic"></TextBox>
<TextBlock>QoS:</TextBlock>
<StackPanel Orientation="Horizontal">
<RadioButton Margin="0,0,10,0" x:Name="SubscribeQoS0" IsChecked="True" GroupName="sqos">0 (At most once)</RadioButton>
<RadioButton Margin="0,0,10,0" x:Name="SubscribeQoS1" GroupName="sqos">1 (At least once)</RadioButton>
<RadioButton Margin="0,0,10,0" x:Name="SubscribeQoS2" GroupName="sqos">2 (Exactly once)</RadioButton>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Click="Subscribe" Width="120" Margin="0,0,10,0">Subscribe</Button>
<Button Click="Unsubscribe" Width="120">Unsubscribe</Button>
</StackPanel>
</StackPanel>
</PivotItem>
</Pivot>
<ScrollViewer Margin="12,10,12,0" Grid.Row="1" Background="Black" Foreground="WhiteSmoke" FontFamily="Consolas" HorizontalScrollMode="Enabled" VerticalScrollMode="Enabled" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<TextBlock x:Name="Trace" FontSize="11"></TextBlock>
</ScrollViewer>
<StackPanel Margin="12,0,12,10" Grid.Row="2">
<Button Click="Clear" Width="120">Clear</Button>
</StackPanel>
</Grid>
</Page>
@@ -1,8 +1,12 @@
using System;
using System.Text;
using Windows.UI.Core;
using Windows.UI.Xaml;
using MQTTnet.Core;
using MQTTnet.Core.Client;
using MQTTnet.Core.Diagnostics;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
namespace MQTTnet.TestApp.UniversalWindows
{
@@ -21,7 +25,7 @@ namespace MQTTnet.TestApp.UniversalWindows
{
await Trace.Dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
{
var text = $"[{DateTime.Now:O}] [{e.Level}] [{e.Source}] [{e.ThreadId}] [{e.Message}]{Environment.NewLine}";
var text = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] [{e.Level}] [{e.Source}] [{e.ThreadId}] [{e.Message}]{Environment.NewLine}";
if (e.Exception != null)
{
text += $"{e.Exception}{Environment.NewLine}";
@@ -38,11 +42,11 @@ namespace MQTTnet.TestApp.UniversalWindows
Server = Server.Text,
UserName = User.Text,
Password = Password.Text,
ClientId = ClientId.Text
ClientId = ClientId.Text,
TlsOptions = { UseTls = UseTls.IsChecked == true },
ConnectionType = UseTcp.IsChecked == true ? MqttConnectionType.Tcp : MqttConnectionType.Ws
};
options.TlsOptions.UseTls = UseTls.IsChecked == true;
try
{
if (_mqttClient != null)
@@ -59,5 +63,112 @@ namespace MQTTnet.TestApp.UniversalWindows
Trace.Text += exception + Environment.NewLine;
}
}
private async void Publish(object sender, RoutedEventArgs e)
{
if (_mqttClient == null)
{
return;
}
try
{
var qos = MqttQualityOfServiceLevel.AtMostOnce;
if (QoS1.IsChecked == true)
{
qos = MqttQualityOfServiceLevel.AtLeastOnce;
}
if (QoS2.IsChecked == true)
{
qos = MqttQualityOfServiceLevel.ExactlyOnce;
}
var payload = new byte[0];
if (Text.IsChecked == true)
{
payload = Encoding.UTF8.GetBytes(Payload.Text);
}
if (Base64.IsChecked == true)
{
payload = Convert.FromBase64String(Payload.Text);
}
var message = new MqttApplicationMessage(
Topic.Text,
payload,
qos,
Retain.IsChecked == true);
await _mqttClient.PublishAsync(message);
}
catch (Exception exception)
{
Trace.Text += exception + Environment.NewLine;
}
}
private async void Disconnect(object sender, RoutedEventArgs e)
{
try
{
await _mqttClient.DisconnectAsync();
}
catch (Exception exception)
{
Trace.Text += exception + Environment.NewLine;
}
}
private void Clear(object sender, RoutedEventArgs e)
{
Trace.Text = string.Empty;
}
private async void Subscribe(object sender, RoutedEventArgs e)
{
if (_mqttClient == null)
{
return;
}
try
{
var qos = MqttQualityOfServiceLevel.AtMostOnce;
if (SubscribeQoS1.IsChecked == true)
{
qos = MqttQualityOfServiceLevel.AtLeastOnce;
}
if (SubscribeQoS2.IsChecked == true)
{
qos = MqttQualityOfServiceLevel.ExactlyOnce;
}
await _mqttClient.SubscribeAsync(new TopicFilter(SubscribeTopic.Text, qos));
}
catch (Exception exception)
{
Trace.Text += exception + Environment.NewLine;
}
}
private async void Unsubscribe(object sender, RoutedEventArgs e)
{
if (_mqttClient == null)
{
return;
}
try
{
await _mqttClient.UnsubscribeAsync(SubscribeTopic.Text);
}
catch (Exception exception)
{
Trace.Text += exception + Environment.NewLine;
}
}
}
}
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap mp">
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" IgnorableNamespaces="uap mp uap3">
<Identity Name="4fa21172-9128-4e84-8a6d-74b9acde4d58" Publisher="CN=Christian" Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="4fa21172-9128-4e84-8a6d-74b9acde4d58" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
@@ -15,7 +15,7 @@
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="MQTTnet.TestApp.UniversalWindows.App">
<uap:VisualElements DisplayName="MQTTnet.TestApp.UniversalWindows" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="MQTTnet.TestApp.UniversalWindows" BackgroundColor="transparent">
<uap:VisualElements DisplayName="MQTTnet Test App" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="Test application for MQTT based communication" BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png">
</uap:DefaultTile>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
@@ -24,5 +24,7 @@
</Applications>
<Capabilities>
<Capability Name="internetClient" />
<Capability Name="internetClientServer" />
<Capability Name="privateNetworkClientServer" />
</Capabilities>
</Package>