From a5e1a4d6dd2eaa3b274b81f29fdd338f3cbeeff1 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Sat, 26 Nov 2022 14:16:37 +0100
Subject: [PATCH 01/48] Update version
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b69b8710..86873f2e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request]
env:
- VERSION: "4.1.3.${{github.run_number}}"
+ VERSION: "4.1.4.${{github.run_number}}"
jobs:
build:
From c45cb8b04ec9d1fc07d56291d3e46905f54ba77a Mon Sep 17 00:00:00 2001
From: Mario Zelger
Date: Sat, 26 Nov 2022 14:19:51 +0100
Subject: [PATCH 02/48] Dispose the AsyncSignalWaiter before setting it to null
(#1586)
dispose the AsyncSignalWaiter before setting it to null to prevent memory leak
Co-authored-by: Mario Zelger
---
Source/MQTTnet/Internal/AsyncSignal.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/Source/MQTTnet/Internal/AsyncSignal.cs b/Source/MQTTnet/Internal/AsyncSignal.cs
index 217a5cce..2861595a 100644
--- a/Source/MQTTnet/Internal/AsyncSignal.cs
+++ b/Source/MQTTnet/Internal/AsyncSignal.cs
@@ -39,6 +39,7 @@ namespace MQTTnet.Internal
if (_waiter != null)
{
_waiter.Approve();
+ _waiter.Dispose();
_waiter = null;
// Since we already got a waiter the signal must be reset right now!
From 581a024ceb3757d0b7dd7e65b22206e99b00f61d Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Sat, 26 Nov 2022 14:45:34 +0100
Subject: [PATCH 03/48] Improve allocations for AsyncSignal.
---
.github/workflows/ReleaseNotes.md | 2 +-
Source/MQTTnet/Internal/AsyncSignal.cs | 23 ++++++++++++++++++-
.../Server/InjectedMqttApplicationMessage.cs | 1 +
3 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index 8b137891..c041e0d0 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -1 +1 @@
-
+[Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
diff --git a/Source/MQTTnet/Internal/AsyncSignal.cs b/Source/MQTTnet/Internal/AsyncSignal.cs
index 2861595a..bc3bcb86 100644
--- a/Source/MQTTnet/Internal/AsyncSignal.cs
+++ b/Source/MQTTnet/Internal/AsyncSignal.cs
@@ -34,7 +34,7 @@ namespace MQTTnet.Internal
_isSignaled = true;
Cleanup();
-
+
// If there is already a waiting task let it run.
if (_waiter != null)
{
@@ -103,6 +103,8 @@ namespace MQTTnet.Internal
// ReSharper disable once FieldCanBeMadeReadOnly.Local
CancellationTokenRegistration _cancellationTokenRegistration;
+ volatile bool _isCompleted;
+
public AsyncSignalWaiter(CancellationToken cancellationToken)
{
if (cancellationToken.CanBeCanceled)
@@ -115,18 +117,37 @@ namespace MQTTnet.Internal
public void Approve()
{
+ if (_isCompleted)
+ {
+ return;
+ }
+
+ _isCompleted = true;
_promise.TrySetResult(true);
}
public void Dispose()
{
_cancellationTokenRegistration.Dispose();
+
+ if (_isCompleted)
+ {
+ // Avoid allocation of _ObjectDisposedException_ which may not be used.
+ return;
+ }
+ _isCompleted = true;
_promise.TrySetException(new ObjectDisposedException(nameof(AsyncSignalWaiter)));
}
void Cancel()
{
+ if (_isCompleted)
+ {
+ return;
+ }
+
+ _isCompleted = true;
_promise.TrySetCanceled();
}
}
diff --git a/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs b/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs
index 0708cee3..90406134 100644
--- a/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs
+++ b/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Threading;
namespace MQTTnet.Server
{
From b5952a590ac2c64d83fbb3306a64e583a6c302e2 Mon Sep 17 00:00:00 2001
From: Dalibor Kundrat
Date: Sat, 26 Nov 2022 14:47:17 +0100
Subject: [PATCH 04/48] Extend server client `ResetStatistics()` to reset all
client statistics not only channel... (#1587)
Extend reset client statistic to reset all stats
---
Source/MQTTnet/Server/Internal/MqttClient.cs | 1 +
Source/MQTTnet/Server/Internal/MqttClientStatistics.cs | 8 +++++++-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/Source/MQTTnet/Server/Internal/MqttClient.cs b/Source/MQTTnet/Server/Internal/MqttClient.cs
index f505b00c..463ca2ec 100644
--- a/Source/MQTTnet/Server/Internal/MqttClient.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClient.cs
@@ -83,6 +83,7 @@ namespace MQTTnet.Server
public void ResetStatistics()
{
ChannelAdapter.ResetStatistics();
+ Statistics.ResetStatistics();
}
public async Task RunAsync()
diff --git a/Source/MQTTnet/Server/Internal/MqttClientStatistics.cs b/Source/MQTTnet/Server/Internal/MqttClientStatistics.cs
index a0d6c077..52718cce 100644
--- a/Source/MQTTnet/Server/Internal/MqttClientStatistics.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClientStatistics.cs
@@ -73,7 +73,13 @@ namespace MQTTnet.Server
LastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp;
}
}
-
+ public void ResetStatistics(){
+ Interlocked.Exchange(ref _sentApplicationMessagesCount,0);
+ Interlocked.Exchange(ref _receivedApplicationMessagesCount,0);
+ Interlocked.Exchange(ref _sentPacketsCount,0);
+ Interlocked.Exchange(ref _receivedPacketsCount,0);
+ }
+
public void HandleSentPacket(MqttPacket packet)
{
if (packet == null)
From 32cc1ff7b151dd1b5800b91fcd158c1de61aaec9 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Sat, 26 Nov 2022 14:49:28 +0100
Subject: [PATCH 05/48] Refactor code and update release notes
---
.github/workflows/ReleaseNotes.md | 1 +
MQTTnet.sln.DotSettings | 1 +
.../Server/Internal/MqttClientStatistics.cs | 60 ++++++++++---------
3 files changed, 33 insertions(+), 29 deletions(-)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index c041e0d0..7166dbea 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -1 +1,2 @@
[Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
+[Server] Fix not properly reset statistics (#1587, thanks to @damikun).
diff --git a/MQTTnet.sln.DotSettings b/MQTTnet.sln.DotSettings
index 83eed4bd..2dc673c8 100644
--- a/MQTTnet.sln.DotSettings
+++ b/MQTTnet.sln.DotSettings
@@ -240,6 +240,7 @@ See the LICENSE file in the project root for more information.
True
True
True
+ True
True
True
True
diff --git a/Source/MQTTnet/Server/Internal/MqttClientStatistics.cs b/Source/MQTTnet/Server/Internal/MqttClientStatistics.cs
index 52718cce..28f8e9cf 100644
--- a/Source/MQTTnet/Server/Internal/MqttClientStatistics.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClientStatistics.cs
@@ -10,15 +10,15 @@ namespace MQTTnet.Server
{
public sealed class MqttClientStatistics
{
+ long _receivedApplicationMessagesCount;
+
// Start with 1 because the CONNACK packet is not counted here.
long _receivedPacketsCount = 1;
+ long _sentApplicationMessagesCount;
// Start with 1 because the CONNECT packet is not counted here.
long _sentPacketsCount = 1;
- long _receivedApplicationMessagesCount;
- long _sentApplicationMessagesCount;
-
public MqttClientStatistics()
{
ConnectedTimestamp = DateTime.UtcNow;
@@ -28,36 +28,36 @@ namespace MQTTnet.Server
LastNonKeepAlivePacketReceivedTimestamp = ConnectedTimestamp;
}
-
+
public DateTime ConnectedTimestamp { get; }
- ///
- /// Timestamp of the last package that has been sent to the client ("received" from the client's perspective)
- ///
- public DateTime LastPacketReceivedTimestamp { get; private set; }
-
- ///
- /// Timestamp of the last package that has been received from the client ("sent" from the client's perspective)
- ///
- public DateTime LastPacketSentTimestamp { get; private set; }
-
public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; private set; }
- public long SentApplicationMessagesCount => Interlocked.Read(ref _sentApplicationMessagesCount);
-
+ ///
+ /// Timestamp of the last package that has been sent to the client ("received" from the client's perspective)
+ ///
+ public DateTime LastPacketReceivedTimestamp { get; private set; }
+
+ ///
+ /// Timestamp of the last package that has been received from the client ("sent" from the client's perspective)
+ ///
+ public DateTime LastPacketSentTimestamp { get; private set; }
+
public long ReceivedApplicationMessagesCount => Interlocked.Read(ref _receivedApplicationMessagesCount);
-
- public long SentPacketsCount => Interlocked.Read(ref _sentPacketsCount);
-
+
public long ReceivedPacketsCount => Interlocked.Read(ref _receivedPacketsCount);
-
+
+ public long SentApplicationMessagesCount => Interlocked.Read(ref _sentApplicationMessagesCount);
+
+ public long SentPacketsCount => Interlocked.Read(ref _sentPacketsCount);
+
public void HandleReceivedPacket(MqttPacket packet)
{
if (packet == null)
{
throw new ArgumentNullException(nameof(packet));
}
-
+
// This class is tracking all values from Clients perspective!
LastPacketSentTimestamp = DateTime.UtcNow;
@@ -73,20 +73,14 @@ namespace MQTTnet.Server
LastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp;
}
}
- public void ResetStatistics(){
- Interlocked.Exchange(ref _sentApplicationMessagesCount,0);
- Interlocked.Exchange(ref _receivedApplicationMessagesCount,0);
- Interlocked.Exchange(ref _sentPacketsCount,0);
- Interlocked.Exchange(ref _receivedPacketsCount,0);
- }
-
+
public void HandleSentPacket(MqttPacket packet)
{
if (packet == null)
{
throw new ArgumentNullException(nameof(packet));
}
-
+
// This class is tracking all values from Clients perspective!
LastPacketReceivedTimestamp = DateTime.UtcNow;
@@ -97,5 +91,13 @@ namespace MQTTnet.Server
Interlocked.Increment(ref _receivedApplicationMessagesCount);
}
}
+
+ public void ResetStatistics()
+ {
+ Interlocked.Exchange(ref _sentApplicationMessagesCount, 0);
+ Interlocked.Exchange(ref _receivedApplicationMessagesCount, 0);
+ Interlocked.Exchange(ref _sentPacketsCount, 0);
+ Interlocked.Exchange(ref _receivedPacketsCount, 0);
+ }
}
}
\ No newline at end of file
From bda4ab7025fc12efd719b1fed686d3d37cedf96b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=80=81=E4=B9=9D?= <366193849@qq.com>
Date: Sat, 26 Nov 2022 21:50:08 +0800
Subject: [PATCH 06/48] Set string.Empty as default value for SenderClientId
(#1583)
---
Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs b/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs
index 90406134..0190ec00 100644
--- a/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs
+++ b/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs
@@ -13,8 +13,8 @@ namespace MQTTnet.Server
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
-
- public string SenderClientId { get; set; }
+
+ public string SenderClientId { get; set; } = string.Empty;
public MqttApplicationMessage ApplicationMessage { get; set; }
}
From c37866e53437d2c19888afda9510737e18366b77 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Sat, 26 Nov 2022 14:51:36 +0100
Subject: [PATCH 07/48] Refactor code and update release notes
---
.github/workflows/ReleaseNotes.md | 1 +
Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs | 1 -
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index 7166dbea..2cb50c8f 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -1,2 +1,3 @@
[Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
[Server] Fix not properly reset statistics (#1587, thanks to @damikun).
+[Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
\ No newline at end of file
diff --git a/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs b/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs
index 0190ec00..7d027ab6 100644
--- a/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs
+++ b/Source/MQTTnet/Server/InjectedMqttApplicationMessage.cs
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
-using System.Threading;
namespace MQTTnet.Server
{
From e45b20737535fd2d7ab6f8791200e119d2a8b006 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Sat, 26 Nov 2022 14:53:03 +0100
Subject: [PATCH 08/48] Update ReleaseNotes.md
---
.github/workflows/ReleaseNotes.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index 2cb50c8f..1a307965 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -1,3 +1,3 @@
-[Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
-[Server] Fix not properly reset statistics (#1587, thanks to @damikun).
-[Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
\ No newline at end of file
+* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
+* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
+* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
\ No newline at end of file
From 41df2ec5dab2d4486aa1da79efe9b6cdf26741c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=80=81=E4=B9=9D?= <366193849@qq.com>
Date: Tue, 29 Nov 2022 04:02:13 +0800
Subject: [PATCH 09/48] Optimize SendPacketAsync method for MQTTnet.AspNetCore
(#1582)
* Avoid to call MqttPacketBuffer.Join() when SendPacketAsync
* Safely cleanup the PacketFormatterAdapter
* Keep buffer.Join when mqtt over webSocket
* null checking for IHttpContextFeature.HttpContext
---
.../MqttConnectionContext.cs | 45 +++++++++++++------
1 file changed, 31 insertions(+), 14 deletions(-)
diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
index 90ee5ce6..3844ff57 100644
--- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
+++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
@@ -8,28 +8,34 @@ using MQTTnet.Adapter;
using MQTTnet.AspNetCore.Client.Tcp;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
+using MQTTnet.Internal;
using MQTTnet.Packets;
using System;
using System.IO.Pipelines;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
-using MQTTnet.Internal;
namespace MQTTnet.AspNetCore
{
public sealed class MqttConnectionContext : IMqttChannelAdapter
{
readonly AsyncLock _writerLock = new AsyncLock();
-
+ readonly bool? _isOverWebSocket;
PipeReader _input;
PipeWriter _output;
-
+
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
{
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
+ var feature = connection.Features.Get();
+ if (feature != null && feature.HttpContext != null)
+ {
+ _isOverWebSocket = feature.HttpContext.WebSockets.IsWebSocketRequest;
+ }
+
if (Connection.Transport != null)
{
_input = Connection.Transport.Input;
@@ -62,7 +68,7 @@ namespace MQTTnet.AspNetCore
public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
public ConnectionContext Connection { get; }
-
+
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public long BytesSent { get; set; }
@@ -169,20 +175,31 @@ namespace MQTTnet.AspNetCore
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
{
- var formatter = PacketFormatterAdapter;
-
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
{
- var buffer = formatter.Encode(packet);
- var msg = buffer.Join().AsMemory();
- var output = _output;
- var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
- if (result.IsCompleted)
+ try
{
- BytesSent += msg.Length;
- }
+ var buffer = PacketFormatterAdapter.Encode(packet);
+ if (_isOverWebSocket == false)
+ {
+ await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
+ if (buffer.Payload.Count > 0)
+ {
+ await _output.WriteAsync(buffer.Payload, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ var bufferSegment = buffer.Join();
+ await _output.WriteAsync(bufferSegment, cancellationToken).ConfigureAwait(false);
+ }
- PacketFormatterAdapter.Cleanup();
+ BytesSent += buffer.Length;
+ }
+ finally
+ {
+ PacketFormatterAdapter.Cleanup();
+ }
}
}
From 9b20e0a430f0494350230833b007099f82061c62 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Mon, 28 Nov 2022 21:10:42 +0100
Subject: [PATCH 10/48] Refactor code
---
.../MQTTnet.AspNetCore.csproj | 1 +
.../MqttConnectionContext.cs | 58 +++++++++----------
2 files changed, 29 insertions(+), 30 deletions(-)
diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
index 953a7949..7fff4ab4 100644
--- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
+++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
@@ -33,6 +33,7 @@
true
true
1591
+ 7.3
diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
index 3844ff57..8dd380f8 100644
--- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
+++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
@@ -2,6 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
+using System.IO.Pipelines;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Features;
using MQTTnet.Adapter;
@@ -10,18 +15,13 @@ using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
-using System;
-using System.IO.Pipelines;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading;
-using System.Threading.Tasks;
namespace MQTTnet.AspNetCore
{
public sealed class MqttConnectionContext : IMqttChannelAdapter
{
- readonly AsyncLock _writerLock = new AsyncLock();
readonly bool? _isOverWebSocket;
+ readonly AsyncLock _writerLock = new AsyncLock();
PipeReader _input;
PipeWriter _output;
@@ -31,18 +31,23 @@ namespace MQTTnet.AspNetCore
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
var feature = connection.Features.Get();
- if (feature != null && feature.HttpContext != null)
+ if (feature?.HttpContext != null)
{
_isOverWebSocket = feature.HttpContext.WebSockets.IsWebSocketRequest;
}
- if (Connection.Transport != null)
- {
- _input = Connection.Transport.Input;
- _output = Connection.Transport.Output;
- }
+ _input = Connection.Transport.Input;
+ _output = Connection.Transport.Output;
}
+ public long BytesReceived { get; private set; }
+
+ public long BytesSent { get; private set; }
+
+ public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
+
+ public ConnectionContext Connection { get; }
+
public string Endpoint
{
get
@@ -63,20 +68,12 @@ namespace MQTTnet.AspNetCore
}
}
- public bool IsSecureConnection => Http?.HttpContext?.Request?.IsHttps ?? false;
+ public bool IsReadingPacket { get; private set; }
- public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
-
- public ConnectionContext Connection { get; }
+ public bool IsSecureConnection => Http?.HttpContext?.Request.IsHttps ?? false;
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
- public long BytesSent { get; set; }
-
- public long BytesReceived { get; set; }
-
- public bool IsReadingPacket { get; private set; }
-
IHttpContextFeature Http => Connection.Features.Get();
public async Task ConnectAsync(CancellationToken cancellationToken)
@@ -98,6 +95,10 @@ namespace MQTTnet.AspNetCore
return Task.CompletedTask;
}
+ public void Dispose()
+ {
+ }
+
public async Task ReceivePacketAsync(CancellationToken cancellationToken)
{
var input = Connection.Transport.Input;
@@ -151,11 +152,11 @@ namespace MQTTnet.AspNetCore
}
}
}
- catch (Exception e)
+ catch (Exception exception)
{
// completing the channel makes sure that there is no more data read after a protocol error
- _input?.Complete(e);
- _output?.Complete(e);
+ _input?.Complete(exception);
+ _output?.Complete(exception);
throw;
}
finally
@@ -180,6 +181,7 @@ namespace MQTTnet.AspNetCore
try
{
var buffer = PacketFormatterAdapter.Encode(packet);
+
if (_isOverWebSocket == false)
{
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
@@ -202,9 +204,5 @@ namespace MQTTnet.AspNetCore
}
}
}
-
- public void Dispose()
- {
- }
}
-}
+}
\ No newline at end of file
From 11e64ef0189a71409a0b179c291ef3a6424a6765 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Mon, 28 Nov 2022 21:10:46 +0100
Subject: [PATCH 11/48] Update ReleaseNotes.md
---
.github/workflows/ReleaseNotes.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index 1a307965..dded0d50 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -1,3 +1,4 @@
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
-* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
\ No newline at end of file
+* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
+* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
\ No newline at end of file
From 56750adfe40679554e1e19aad58528403b303142 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Tue, 29 Nov 2022 20:23:20 +0100
Subject: [PATCH 12/48] 1592 bug of mqttbufferreader (#1593)
---
.github/workflows/ReleaseNotes.md | 1 +
.../MqttBufferReaderBenchmark.cs | 43 +++++++
Source/MQTTnet.Benchmarks/Program.cs | 6 +-
.../Client/ManagedMqttClient_Tests.cs | 18 +--
.../Formatter/MqttBufferReader_Tests.cs | 110 ++++++++++++++++++
Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs | 4 +-
Source/MQTTnet.Tests/Protocol_Tests.cs | 33 +++++-
Source/MQTTnet.Tests/Server/QoS_Tests.cs | 2 +
Source/MQTTnet/Formatter/MqttBufferReader.cs | 92 +++++++++------
Source/MQTTnet/Formatter/MqttBufferWriter.cs | 31 +++--
Source/MQTTnet/Formatter/MqttPacketBuffer.cs | 4 +-
.../Formatter/V3/MqttV3PacketFormatter.cs | 2 +-
.../Formatter/V5/MqttV5PacketEncoder.cs | 6 +-
13 files changed, 283 insertions(+), 69 deletions(-)
create mode 100644 Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs
create mode 100644 Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index dded0d50..4401f369 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -1,4 +1,5 @@
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
+* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593).
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
\ No newline at end of file
diff --git a/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs
new file mode 100644
index 00000000..bfa3d209
--- /dev/null
+++ b/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs
@@ -0,0 +1,43 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using MQTTnet.Formatter;
+
+namespace MQTTnet.Benchmarks
+{
+ [SimpleJob(RuntimeMoniker.Net60)]
+ [MemoryDiagnoser]
+ public class MqttBufferReaderBenchmark
+ {
+ byte[] _buffer;
+ int _bufferLength;
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ var writer = new MqttBufferWriter(1024, 1024);
+ writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
+
+ _buffer = writer.GetBuffer();
+ _bufferLength = writer.Length;
+ }
+
+ [Benchmark]
+ public void Use_Span()
+ {
+ var span = _buffer.AsSpan(0, _bufferLength);
+ Encoding.UTF8.GetString(span);
+ }
+
+ [Benchmark]
+ public void Use_Encoding()
+ {
+ Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Benchmarks/Program.cs b/Source/MQTTnet.Benchmarks/Program.cs
index 1fcc670d..3cafa08d 100644
--- a/Source/MQTTnet.Benchmarks/Program.cs
+++ b/Source/MQTTnet.Benchmarks/Program.cs
@@ -30,6 +30,7 @@ namespace MQTTnet.Benchmarks
Console.WriteLine("d = UnsubscribeBenchmark");
Console.WriteLine("e = MessageDeliveryBenchmark");
Console.WriteLine("f = AsyncLockBenchmark");
+ Console.WriteLine("g = MqttBufferReaderBenchmark");
var pressedKey = Console.ReadKey(true);
switch (pressedKey.KeyChar)
@@ -79,9 +80,12 @@ namespace MQTTnet.Benchmarks
case 'f':
BenchmarkRunner.Run();
break;
+ case 'g':
+ BenchmarkRunner.Run();
+ break;
}
Console.ReadLine();
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/Client/ManagedMqttClient_Tests.cs b/Source/MQTTnet.Tests/Client/ManagedMqttClient_Tests.cs
index bfa662ac..69c9847b 100644
--- a/Source/MQTTnet.Tests/Client/ManagedMqttClient_Tests.cs
+++ b/Source/MQTTnet.Tests/Client/ManagedMqttClient_Tests.cs
@@ -47,10 +47,12 @@ namespace MQTTnet.Tests.Client
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
+ await LongTestDelay();
// Send test data.
await senderClient.PublishStringAsync("topic1");
await LongTestDelay();
+ await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
@@ -329,13 +331,7 @@ namespace MQTTnet.Tests.Client
await testEnvironment.StartServer().ConfigureAwait(false);
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
- await sendingClient.PublishAsync(
- new MqttApplicationMessage
- {
- Topic = "topic",
- Payload = new byte[] { 1 },
- Retain = true
- });
+ await sendingClient.PublishStringAsync("topic", "A", retain: true);
// Wait a bit for the retained message to be available
await LongTestDelay();
@@ -357,18 +353,15 @@ namespace MQTTnet.Tests.Client
await managedClient.SubscribeAsync("topic");
- await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
-
+ await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
await LongTestDelay();
Assert.AreEqual(1, receivedManagedMessages.Count);
await managedClient.StopAsync();
-
await LongTestDelay();
- await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
-
+ await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
await LongTestDelay();
// After reconnect and then some delay, the retained message must not be received,
@@ -377,7 +370,6 @@ namespace MQTTnet.Tests.Client
// Make sure that it gets received after subscribing again.
await managedClient.SubscribeAsync("topic");
-
await LongTestDelay();
Assert.AreEqual(2, receivedManagedMessages.Count);
diff --git a/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs
new file mode 100644
index 00000000..0c7bf994
--- /dev/null
+++ b/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs
@@ -0,0 +1,110 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MQTTnet.Exceptions;
+using MQTTnet.Formatter;
+
+namespace MQTTnet.Tests.Formatter
+{
+ [TestClass]
+ public sealed class MqttBufferReader_Tests
+ {
+ [TestMethod]
+ [ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
+ public void Fire_Exception_If_Not_Enough_Data()
+ {
+ var buffer = new byte[] { 0, 1, 2 };
+
+ var reader = new MqttBufferReader();
+
+ reader.SetBuffer(buffer, 0, 3);
+
+ // 1 byte is missing.
+ reader.ReadFourByteInteger();
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
+ public void Fire_Exception_If_Not_Enough_Data_With_Longer_Buffer()
+ {
+ var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6 };
+
+ var reader = new MqttBufferReader();
+
+ reader.SetBuffer(buffer, 0, 3);
+
+ // 1 byte is missing.
+ reader.ReadFourByteInteger();
+ }
+
+ [TestMethod]
+ public void Is_EndOfStream_Without_Buffer()
+ {
+ var reader = new MqttBufferReader();
+
+ Assert.IsTrue(reader.EndOfStream);
+ Assert.AreEqual(0, reader.BytesLeft);
+ }
+
+ [TestMethod]
+ public void Read_Remaining_Data_From_Larger_Buffer()
+ {
+ var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+
+ var reader = new MqttBufferReader();
+
+ // The used buffer contains more data than used!
+ reader.SetBuffer(buffer, 0, 5);
+
+ // This should only read 5 bytes even if more data is in the buffer
+ // due to custom bounds.
+ var remainingData = reader.ReadRemainingData();
+
+ Assert.IsTrue(reader.EndOfStream);
+ Assert.AreEqual(0, reader.BytesLeft);
+ Assert.AreEqual(5, remainingData.Length);
+ }
+
+ [TestMethod]
+ public void Report_Correct_Length_For_Full_Buffer()
+ {
+ var buffer = new byte[] { 5, 6, 7, 8, 9 };
+
+ var reader = new MqttBufferReader();
+ reader.SetBuffer(buffer, 0, 5);
+
+ Assert.IsFalse(reader.EndOfStream);
+ Assert.AreEqual(5, reader.BytesLeft);
+ }
+
+ [TestMethod]
+ public void Report_Correct_Length_For_Partial_End_Buffer()
+ {
+ var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+
+ var reader = new MqttBufferReader();
+
+ // The used buffer contains more data than used!
+ reader.SetBuffer(buffer, 5, 5);
+
+ Assert.IsFalse(reader.EndOfStream);
+ Assert.AreEqual(5, reader.BytesLeft);
+ }
+
+ [TestMethod]
+ public void Report_Correct_Length_For_Partial_Start_Buffer()
+ {
+ var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+
+ var reader = new MqttBufferReader();
+
+ // The used buffer contains more data than used!
+ reader.SetBuffer(buffer, 0, 5);
+
+ Assert.IsFalse(reader.EndOfStream);
+ Assert.AreEqual(5, reader.BytesLeft);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs b/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs
index a187972d..42dbb882 100644
--- a/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs
+++ b/Source/MQTTnet.Tests/MQTTv5/Server_Tests.cs
@@ -37,9 +37,9 @@ namespace MQTTnet.Tests.MQTTv5
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
var c2 = await testEnvironment.ConnectClient(clientOptions);
- c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
+ c2.Dispose(); // Dispose will not send a DISCONNECT packet first so the will message must be sent.
- await Task.Delay(1000);
+ await LongTestDelay();
Assert.AreEqual(1, receivedMessagesCount);
}
diff --git a/Source/MQTTnet.Tests/Protocol_Tests.cs b/Source/MQTTnet.Tests/Protocol_Tests.cs
index 41b703c3..6f5146c8 100644
--- a/Source/MQTTnet.Tests/Protocol_Tests.cs
+++ b/Source/MQTTnet.Tests/Protocol_Tests.cs
@@ -8,27 +8,48 @@ using MQTTnet.Formatter;
namespace MQTTnet.Tests
{
[TestClass]
- public class Protocol_Tests
+ public sealed class Protocol_Tests
{
[TestMethod]
public void Encode_Four_Byte_Integer()
{
var writer = new MqttBufferWriter(4, 4);
-
+
for (uint value = 0; value < 268435455; value++)
{
writer.WriteVariableByteInteger(value);
-
+
var buffer = writer.GetBuffer();
-
+
var reader = new MqttBufferReader();
reader.SetBuffer(buffer, 0, writer.Length);
var checkValue = reader.ReadVariableByteInteger();
Assert.AreEqual(value, checkValue);
-
+
+ writer.Reset(0);
+ }
+ }
+
+ [TestMethod]
+ public void Encode_Two_Byte_Integer()
+ {
+ var writer = new MqttBufferWriter(2, 2);
+
+ for (ushort value = 0; value < ushort.MaxValue; value++)
+ {
+ writer.WriteTwoByteInteger(value);
+
+ var buffer = writer.GetBuffer();
+
+ var reader = new MqttBufferReader();
+ reader.SetBuffer(buffer, 0, writer.Length);
+ var checkValue = reader.ReadTwoByteInteger();
+
+ Assert.AreEqual(value, checkValue);
+
writer.Reset(0);
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/Server/QoS_Tests.cs b/Source/MQTTnet.Tests/Server/QoS_Tests.cs
index d6c9e644..d053cac5 100644
--- a/Source/MQTTnet.Tests/Server/QoS_Tests.cs
+++ b/Source/MQTTnet.Tests/Server/QoS_Tests.cs
@@ -26,6 +26,8 @@ namespace MQTTnet.Tests.Server
var dummyClient = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false));
await dummyClient.SubscribeAsync("#", MqttQualityOfServiceLevel.AtLeastOnce);
dummyClient.Dispose();
+
+ await LongTestDelay();
// Now inject messages which are appended to the queue of the client.
await server.InjectApplicationMessage("T", "0", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
diff --git a/Source/MQTTnet/Formatter/MqttBufferReader.cs b/Source/MQTTnet/Formatter/MqttBufferReader.cs
index 665186ab..51c8225f 100644
--- a/Source/MQTTnet/Formatter/MqttBufferReader.cs
+++ b/Source/MQTTnet/Formatter/MqttBufferReader.cs
@@ -13,15 +13,14 @@ namespace MQTTnet.Formatter
public sealed class MqttBufferReader
{
byte[] _buffer = EmptyBuffer.Array;
- int _initialPosition;
- int _length;
- int _position;
+ int _maxPosition;
+ int _offset;
- public bool EndOfStream => _position == _length;
-
- public int Position => _position;
-
- public int BytesLeft => _length - _position;
+ public int BytesLeft => _maxPosition - Position;
+
+ public bool EndOfStream => BytesLeft == 0;
+
+ public int Position { get; private set; }
public byte[] ReadBinaryData()
{
@@ -35,8 +34,8 @@ namespace MQTTnet.Formatter
ValidateReceiveBuffer(length);
var result = new byte[length];
- Array.Copy(_buffer, _position, result, 0, length);
- _position += length;
+ Array.Copy(_buffer, Position + _offset, result, 0, length);
+ Position += length;
return result;
}
@@ -45,32 +44,42 @@ namespace MQTTnet.Formatter
{
ValidateReceiveBuffer(1);
- return _buffer[_position++];
+ return _buffer[Position++];
}
public uint ReadFourByteInteger()
{
ValidateReceiveBuffer(4);
- var byte0 = _buffer[_position++];
- var byte1 = _buffer[_position++];
- var byte2 = _buffer[_position++];
- var byte3 = _buffer[_position++];
+#if NETCOREAPP3_0_OR_GREATER
+ var value = System.Runtime.InteropServices.MemoryMarshal.Read(_buffer.AsSpan().Slice(Position));
+ value = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value);
+#else
+ var byte0 = _buffer[_offset + Position];
+ var byte1 = _buffer[_offset + Position + 1];
+ var byte2 = _buffer[_offset + Position + 2];
+ var byte3 = _buffer[_offset + Position + 3];
- return (uint)((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
+ var value = (uint)((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
+#endif
+
+ Position += 4;
+ return value;
}
public byte[] ReadRemainingData()
{
- var bufferLength = _length - _position;
+ var bufferLength = _maxPosition - Position - _offset;
if (bufferLength == 0)
{
return EmptyBuffer.Array;
}
-
+
var buffer = new byte[bufferLength];
- Array.Copy(_buffer, _position, buffer, 0, bufferLength);
+ Array.Copy(_buffer, Position + _offset, buffer, 0, bufferLength);
+
+ Position += bufferLength;
return buffer;
}
@@ -83,11 +92,17 @@ namespace MQTTnet.Formatter
{
return string.Empty;
}
-
+
ValidateReceiveBuffer(length);
- var result = Encoding.UTF8.GetString(_buffer, _position, length);
- _position += length;
+#if NETCOREAPP3_0_OR_GREATER
+ // AsSpan() version is slightly faster. Not much but at least a little bit.
+ var result = Encoding.UTF8.GetString(_buffer.AsSpan(_offset + Position, length));
+#else
+ var result = Encoding.UTF8.GetString(_buffer, _offset + Position, length);
+#endif
+
+ Position += length;
return result;
}
@@ -95,10 +110,18 @@ namespace MQTTnet.Formatter
{
ValidateReceiveBuffer(2);
- var msb = _buffer[_position++];
- var lsb = _buffer[_position++];
+#if NETCOREAPP3_0_OR_GREATER
+ var value = System.Runtime.InteropServices.MemoryMarshal.Read(_buffer.AsSpan().Slice(Position));
+ value = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value);
+#else
+ var msb = _buffer[_offset + Position];
+ var lsb = _buffer[_offset + Position + 1];
- return (ushort)((msb << 8) | lsb);
+ var value = (ushort)((msb << 8) | lsb);
+#endif
+
+ Position += 2;
+ return value;
}
public uint ReadVariableByteInteger()
@@ -125,23 +148,26 @@ namespace MQTTnet.Formatter
public void Seek(int position)
{
- _position = _initialPosition + position;
+ Position = position;
}
- public void SetBuffer(byte[] buffer, int position, int length)
+ public void SetBuffer(byte[] buffer, int offset, int length)
{
- _buffer = buffer;
- _initialPosition = position;
- _position = position;
- _length = length;
+ _buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
+ _offset = offset;
+
+ Position = 0;
+ _maxPosition = length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void ValidateReceiveBuffer(int length)
{
- if (_length < _position + length)
+ var newPosition = Position + _offset + length;
+
+ if (_maxPosition < newPosition)
{
- throw new MqttProtocolViolationException($"Expected at least {Position + length} bytes but there are only {_length} bytes");
+ throw new MqttProtocolViolationException($"Expected at least {Position + length} bytes but there are only {_maxPosition} bytes");
}
}
}
diff --git a/Source/MQTTnet/Formatter/MqttBufferWriter.cs b/Source/MQTTnet/Formatter/MqttBufferWriter.cs
index 701908b4..8757f12e 100644
--- a/Source/MQTTnet/Formatter/MqttBufferWriter.cs
+++ b/Source/MQTTnet/Formatter/MqttBufferWriter.cs
@@ -63,17 +63,30 @@ namespace MQTTnet.Formatter
return _buffer;
}
- public static int GetLengthOfVariableInteger(uint value)
+ public static int GetVariableByteIntegerSize(uint value)
{
- var result = 0;
- var x = value;
- do
+ // From RFC: Table 2.4 Size of Remaining Length field
+
+ // 0 (0x00) to 127 (0x7F)
+ if (value <= 127)
{
- x /= 128;
- result++;
- } while (x > 0);
+ return 1;
+ }
- return result;
+ // 128 (0x80, 0x01) to 16 383 (0xFF, 0x7F)
+ if (value <= 16383)
+ {
+ return 2;
+ }
+
+ // 16 384 (0x80, 0x80, 0x01) to 2 097 151 (0xFF, 0xFF, 0x7F)
+ if (value <= 2097151)
+ {
+ return 3;
+ }
+
+ // 2 097 152 (0x80, 0x80, 0x80, 0x01) to 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F)
+ return 4;
}
public void Reset(int length)
@@ -180,7 +193,7 @@ namespace MQTTnet.Formatter
_buffer[_position] = (byte)(writtenBytes >> 8);
_buffer[_position + 1] = (byte)writtenBytes;
-
+
IncreasePosition(writtenBytes + 2);
}
}
diff --git a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs
index fb8692f5..8bdd51c8 100644
--- a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs
+++ b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs
@@ -37,7 +37,7 @@ namespace MQTTnet.Formatter
public byte[] ToArray()
{
- if (Packet.Count == 0)
+ if (Payload.Count == 0)
{
return Packet.ToArray();
}
@@ -51,7 +51,7 @@ namespace MQTTnet.Formatter
public ArraySegment Join()
{
- if (Packet.Count == 0)
+ if (Payload.Count == 0)
{
return Packet;
}
diff --git a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs
index 7d315433..f9651d81 100644
--- a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs
+++ b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs
@@ -108,7 +108,7 @@ namespace MQTTnet.Formatter.V3
remainingLength += (uint)publishPacket.Payload.Length;
}
- var remainingLengthSize = MqttBufferWriter.GetLengthOfVariableInteger(remainingLength);
+ var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
var headerSize = FixedHeaderSize + remainingLengthSize;
var headerOffset = 5 - headerSize;
diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs
index 27478483..31092fa1 100644
--- a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs
+++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs
@@ -12,6 +12,8 @@ namespace MQTTnet.Formatter.V5
{
public sealed class MqttV5PacketEncoder
{
+ const int FixedHeaderSize = 1;
+
readonly MqttBufferWriter _bufferWriter;
readonly MqttV5PropertiesWriter _propertiesWriter = new MqttV5PropertiesWriter(new MqttBufferWriter(1024, 4096));
@@ -40,9 +42,9 @@ namespace MQTTnet.Formatter.V5
remainingLength += (uint)publishPacket.Payload.Length;
}
- var remainingLengthSize = MqttBufferWriter.GetLengthOfVariableInteger(remainingLength);
+ var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
- var headerSize = 1 + remainingLengthSize;
+ var headerSize = FixedHeaderSize + remainingLengthSize;
var headerOffset = 5 - headerSize;
// Position cursor on correct offset on beginning of array (has leading 0x0)
From 47df131e7fd7e07fa01a33e4705cffd8262021b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=80=81=E4=B9=9D?= <366193849@qq.com>
Date: Thu, 1 Dec 2022 00:56:05 +0800
Subject: [PATCH 13/48] Avoid to check the type of propertyWriter parameter
(#1600)
Refactor code of MqttBufferWriter
---
Source/MQTTnet/Formatter/MqttBufferWriter.cs | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/Source/MQTTnet/Formatter/MqttBufferWriter.cs b/Source/MQTTnet/Formatter/MqttBufferWriter.cs
index 8757f12e..7e740630 100644
--- a/Source/MQTTnet/Formatter/MqttBufferWriter.cs
+++ b/Source/MQTTnet/Formatter/MqttBufferWriter.cs
@@ -66,7 +66,7 @@ namespace MQTTnet.Formatter
public static int GetVariableByteIntegerSize(uint value)
{
// From RFC: Table 2.4 Size of Remaining Length field
-
+
// 0 (0x00) to 127 (0x7F)
if (value <= 127)
{
@@ -103,18 +103,12 @@ namespace MQTTnet.Formatter
public void Write(MqttBufferWriter propertyWriter)
{
- if (propertyWriter is MqttBufferWriter writer)
- {
- WriteBinary(writer._buffer, 0, writer.Length);
- return;
- }
-
if (propertyWriter == null)
{
throw new ArgumentNullException(nameof(propertyWriter));
}
- throw new InvalidOperationException($"{nameof(propertyWriter)} must be of type {nameof(MqttBufferWriter)}");
+ WriteBinary(propertyWriter._buffer, 0, propertyWriter.Length);
}
public void WriteBinary(byte[] buffer, int offset, int count)
From 01a5aeeb583e36515982e8e20c59967dcfacbc56 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Wed, 30 Nov 2022 18:09:34 +0100
Subject: [PATCH 14/48] Refactor benchmarks (#1601)
---
Source/MQTTnet.Benchmarks/BaseBenchmark.cs | 4 +
.../ChannelAdapterBenchmark.cs | 69 ++++---
.../Configurations/AllowNonOptimized.cs | 22 --
.../Configurations/BaseConfig.cs | 19 --
.../Configurations/RuntimeCompareConfig.cs | 19 --
Source/MQTTnet.Benchmarks/LoggerBenchmark.cs | 2 +-
.../MQTTnet.Benchmarks.csproj | 2 +-
.../MessageDeliveryBenchmark.cs | 11 +-
.../MessageProcessingBenchmark.cs | 2 +-
...rocessingMqttConnectionContextBenchmark.cs | 2 +-
.../MqttPacketReaderWriterBenchmark.cs | 7 +-
.../MqttTcpChannelBenchmark.cs | 2 +-
Source/MQTTnet.Benchmarks/Program.cs | 191 +++++++++++-------
.../RoundtripProcessingBenchmark.cs | 2 +-
.../ServerProcessingBenchmark.cs | 6 +-
.../MQTTnet.Benchmarks/SubscribeBenchmark.cs | 6 +-
.../MQTTnet.Benchmarks/TcpPipesBenchmark.cs | 2 +-
.../TopicFilterComparerBenchmark.cs | 2 +-
Source/MQTTnet.Benchmarks/TopicGenerator.cs | 33 +--
.../UnsubscribeBenchmark.cs | 6 +-
Source/MQTTnet.Benchmarks/packages.config | 54 -----
21 files changed, 210 insertions(+), 253 deletions(-)
delete mode 100644 Source/MQTTnet.Benchmarks/Configurations/AllowNonOptimized.cs
delete mode 100644 Source/MQTTnet.Benchmarks/Configurations/BaseConfig.cs
delete mode 100644 Source/MQTTnet.Benchmarks/Configurations/RuntimeCompareConfig.cs
delete mode 100644 Source/MQTTnet.Benchmarks/packages.config
diff --git a/Source/MQTTnet.Benchmarks/BaseBenchmark.cs b/Source/MQTTnet.Benchmarks/BaseBenchmark.cs
index a33ad7a1..a3a50ce9 100644
--- a/Source/MQTTnet.Benchmarks/BaseBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/BaseBenchmark.cs
@@ -1,3 +1,7 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
namespace MQTTnet.Benchmarks
{
public abstract class BaseBenchmark
diff --git a/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs
index f73e7b36..be2e0eb4 100644
--- a/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs
@@ -2,54 +2,25 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using BenchmarkDotNet.Attributes;
-using MQTTnet.Adapter;
-using MQTTnet.Internal;
-using MQTTnet.Packets;
using System;
using System.IO;
using System.Threading;
+using BenchmarkDotNet.Attributes;
+using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
+using MQTTnet.Packets;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
- public sealed class ChannelAdapterBenchmark
+ public sealed class ChannelAdapterBenchmark : BaseBenchmark
{
MqttChannelAdapter _channelAdapter;
int _iterations;
- MemoryStream _stream;
MqttPublishPacket _packet;
-
- [GlobalSetup]
- public void Setup()
- {
- _packet = new MqttPublishPacket
- {
- Topic = "A"
- };
-
- var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
-
- var serializedPacket = Join(serializer.Encode(_packet).Join());
-
- _iterations = 10000;
-
- _stream = new MemoryStream(_iterations * serializedPacket.Length);
-
- for (var i = 0; i < _iterations; i++)
- {
- _stream.Write(serializedPacket, 0, serializedPacket.Length);
- }
-
- _stream.Position = 0;
-
- var channel = new MemoryMqttChannel(_stream);
-
- _channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
- }
+ MemoryStream _stream;
[Benchmark]
public void Receive_10000_Messages()
@@ -77,6 +48,34 @@ namespace MQTTnet.Benchmarks
_stream.Position = 0;
}
+ [GlobalSetup]
+ public void Setup()
+ {
+ _packet = new MqttPublishPacket
+ {
+ Topic = "A"
+ };
+
+ var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
+
+ var serializedPacket = Join(serializer.Encode(_packet).Join());
+
+ _iterations = 10000;
+
+ _stream = new MemoryStream(_iterations * serializedPacket.Length);
+
+ for (var i = 0; i < _iterations; i++)
+ {
+ _stream.Write(serializedPacket, 0, serializedPacket.Length);
+ }
+
+ _stream.Position = 0;
+
+ var channel = new MemoryMqttChannel(_stream);
+
+ _channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
+ }
+
static byte[] Join(params ArraySegment[] chunks)
{
var buffer = new MemoryStream();
@@ -88,4 +87,4 @@ namespace MQTTnet.Benchmarks
return buffer.ToArray();
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Benchmarks/Configurations/AllowNonOptimized.cs b/Source/MQTTnet.Benchmarks/Configurations/AllowNonOptimized.cs
deleted file mode 100644
index 2ae1e19b..00000000
--- a/Source/MQTTnet.Benchmarks/Configurations/AllowNonOptimized.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using BenchmarkDotNet.Jobs;
-using BenchmarkDotNet.Validators;
-
-namespace MQTTnet.Benchmarks.Configurations
-{
- ///
- /// this options may be used to run benchmarks in debugmode and attach a performance profiler
- /// https://benchmarkdotnet.org/Configs/Configs.htm
- ///
- public class AllowNonOptimized : BaseConfig
- {
- public AllowNonOptimized()
- {
- AddValidator(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS
- AddJob(Job.InProcess);
- }
- }
-}
\ No newline at end of file
diff --git a/Source/MQTTnet.Benchmarks/Configurations/BaseConfig.cs b/Source/MQTTnet.Benchmarks/Configurations/BaseConfig.cs
deleted file mode 100644
index 15324a6a..00000000
--- a/Source/MQTTnet.Benchmarks/Configurations/BaseConfig.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using BenchmarkDotNet.Configs;
-using System.Linq;
-
-namespace MQTTnet.Benchmarks.Configurations
-{
- public class BaseConfig : ManualConfig
- {
- public BaseConfig()
- {
- AddLogger(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default
- AddExporter(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default
- AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default
- }
- }
-}
diff --git a/Source/MQTTnet.Benchmarks/Configurations/RuntimeCompareConfig.cs b/Source/MQTTnet.Benchmarks/Configurations/RuntimeCompareConfig.cs
deleted file mode 100644
index cd01b723..00000000
--- a/Source/MQTTnet.Benchmarks/Configurations/RuntimeCompareConfig.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using BenchmarkDotNet.Environments;
-using BenchmarkDotNet.Jobs;
-using BenchmarkDotNet.Toolchains.CsProj;
-
-namespace MQTTnet.Benchmarks.Configurations
-{
- public class RuntimeCompareConfig : BaseConfig
- {
- public RuntimeCompareConfig()
- {
- AddJob(Job.Default.WithRuntime(ClrRuntime.Net48));
- AddJob(Job.Default.WithRuntime(CoreRuntime.Core50).WithToolchain(CsProjCoreToolchain.NetCoreApp50));
- }
- }
-}
diff --git a/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs b/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs
index 0c325e04..70f0ff51 100644
--- a/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
- public class LoggerBenchmark
+ public class LoggerBenchmark : BaseBenchmark
{
MqttNetNullLogger _nullLogger;
MqttNetSourceLogger _sourceNullLogger;
diff --git a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
index 63f0fbca..9cac34d0 100644
--- a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
+++ b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
@@ -3,7 +3,7 @@
Exe
Full
- net5.0;net6.0
+ net6.0
7.2
false
false
diff --git a/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs b/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs
index 5bbcfdb7..3738a23a 100644
--- a/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/MessageDeliveryBenchmark.cs
@@ -1,5 +1,8 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Server;
using System;
@@ -8,14 +11,10 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-
namespace MQTTnet.Benchmarks
{
- ///
- /// Create a number of topics, publish, subscribe, and wait for response
- ///
[MemoryDiagnoser]
- public class MessageDeliveryBenchmark
+ public class MessageDeliveryBenchmark : BaseBenchmark
{
List _topicPublishMessages;
diff --git a/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs b/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs
index 98d60c07..c5387e76 100644
--- a/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs
@@ -12,7 +12,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
- public class MessageProcessingBenchmark
+ public class MessageProcessingBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttClient _mqttClient;
diff --git a/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs b/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs
index f66c5be8..fd812f46 100644
--- a/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
- public class MessageProcessingMqttConnectionContextBenchmark
+ public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
{
IWebHost _host;
IMqttClient _mqttClient;
diff --git a/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs
index ffa01db1..a0c474a1 100644
--- a/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs
@@ -1,7 +1,10 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
-using MQTTnet.AspNetCore;
using MQTTnet.Formatter;
using MQTTnet.Tests.Mockups;
@@ -9,7 +12,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
- public class MqttPacketReaderWriterBenchmark
+ public class MqttPacketReaderWriterBenchmark : BaseBenchmark
{
readonly byte[] _demoPayload = new byte[1024];
diff --git a/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs
index 5c3a1d1f..787e951d 100644
--- a/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs
@@ -17,7 +17,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
- public sealed class MqttTcpChannelBenchmark
+ public class MqttTcpChannelBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttChannel _serverChannel;
diff --git a/Source/MQTTnet.Benchmarks/Program.cs b/Source/MQTTnet.Benchmarks/Program.cs
index 3cafa08d..be231ff8 100644
--- a/Source/MQTTnet.Benchmarks/Program.cs
+++ b/Source/MQTTnet.Benchmarks/Program.cs
@@ -3,89 +3,140 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Generic;
+using System.Linq;
+using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
-using MQTTnet.Benchmarks.Configurations;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
public static class Program
{
- public static void Main(string[] args)
- {
- Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
- Console.WriteLine("--------------------------------------------------------");
- Console.WriteLine("1 = MessageProcessingBenchmark");
- Console.WriteLine("2 = SerializerBenchmark");
- Console.WriteLine("3 = LoggerBenchmark");
- Console.WriteLine("4 = TopicFilterComparerBenchmark");
- Console.WriteLine("5 = ChannelAdapterBenchmark");
- Console.WriteLine("6 = MqttTcpChannelBenchmark");
- Console.WriteLine("7 = TcpPipesBenchmark");
- Console.WriteLine("8 = MessageProcessingMqttConnectionContextBenchmark");
- Console.WriteLine("9 = ServerProcessingBenchmark");
- Console.WriteLine("a = MqttPacketReaderWriterBenchmark");
- Console.WriteLine("b = RoundtripBenchmark");
- Console.WriteLine("c = SubscribeBenchmark");
- Console.WriteLine("d = UnsubscribeBenchmark");
- Console.WriteLine("e = MessageDeliveryBenchmark");
- Console.WriteLine("f = AsyncLockBenchmark");
- Console.WriteLine("g = MqttBufferReaderBenchmark");
+ static int _selectedBenchmarkIndex;
+ static List _benchmarks;
- var pressedKey = Console.ReadKey(true);
- switch (pressedKey.KeyChar)
+ public static void Main(string[] arguments)
+ {
+ _benchmarks = CollectBenchmarks();
+ HandleArguments(arguments);
+
+ while (true)
{
- case '1':
- BenchmarkRunner.Run();
- break;
- case '2':
- BenchmarkRunner.Run();
- break;
- case '3':
- BenchmarkRunner.Run();
- break;
- case '4':
- BenchmarkRunner.Run();
- break;
- case '5':
- BenchmarkRunner.Run();
- break;
- case '6':
- BenchmarkRunner.Run();
- break;
- case '7':
- BenchmarkRunner.Run();
- break;
- case '8':
- BenchmarkRunner.Run(new RuntimeCompareConfig());
- break;
- case '9':
- BenchmarkRunner.Run();
- break;
- case 'a':
- BenchmarkRunner.Run(typeof(MqttPacketReaderWriterBenchmark));
- break;
- case 'b':
- BenchmarkRunner.Run();
- break;
- case 'c':
- BenchmarkRunner.Run();
- break;
- case 'd':
- BenchmarkRunner.Run();
- break;
- case 'e':
- BenchmarkRunner.Run();
- break;
- case 'f':
- BenchmarkRunner.Run();
- break;
- case 'g':
- BenchmarkRunner.Run();
+ RenderMenu();
+
+ var key = Console.ReadKey(true);
+
+ if (key.Key == ConsoleKey.DownArrow)
+ {
+ _selectedBenchmarkIndex++;
+ if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
+ {
+ _selectedBenchmarkIndex = 0;
+ }
+ }
+ else if (key.Key == ConsoleKey.UpArrow)
+ {
+ _selectedBenchmarkIndex--;
+ if (_selectedBenchmarkIndex < 0)
+ {
+ _selectedBenchmarkIndex = _benchmarks.Count - 1;
+ }
+ }
+ else if (key.Key == ConsoleKey.Escape)
+ {
+ Environment.Exit(0);
+ }
+ else if (key.Key == ConsoleKey.Enter)
+ {
break;
+ }
}
+ BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
+
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
+
+ static List CollectBenchmarks()
+ {
+ var benchmarks = new List();
+
+ var types = typeof(Program).Assembly.GetExportedTypes();
+ foreach (var type in types)
+ {
+ if (typeof(BaseBenchmark).IsAssignableFrom(type))
+ {
+ if (type != typeof(BaseBenchmark))
+ {
+ benchmarks.Add(type);
+ }
+ }
+ }
+
+ return benchmarks.OrderBy(b => b.Name).ToList();
+ }
+
+ static void HandleArguments(string[] arguments)
+ {
+ if (arguments.Length == 0)
+ {
+ return;
+ }
+
+ // Allow for preselection to avoid developer frustration.
+
+ if (int.TryParse(arguments[0], out var benchmarkIndex))
+ {
+ _selectedBenchmarkIndex = benchmarkIndex;
+ return;
+ }
+
+ _selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
+
+ if (_selectedBenchmarkIndex < 0)
+ {
+ _selectedBenchmarkIndex = 0;
+ }
+
+ if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
+ {
+ _selectedBenchmarkIndex = _benchmarks.Count - 1;
+ }
+ }
+
+ static void RenderMenu()
+ {
+ Console.Clear();
+
+ Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
+ Console.WriteLine("-----------------------------------------------");
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.WriteLine("Press arrow keys for benchmark selection");
+ Console.WriteLine("Press Enter for benchmark execution");
+ Console.WriteLine("Press Esc for exit");
+ Console.ForegroundColor = ConsoleColor.White;
+ Console.WriteLine();
+
+ for (var index = 0; index < _benchmarks.Count; index++)
+ {
+ var benchmark = _benchmarks[index];
+
+ if (_selectedBenchmarkIndex == index)
+ {
+ Console.Write("-> ");
+ }
+ else
+ {
+ Console.Write(" ");
+ }
+
+ Console.WriteLine(benchmark.Name);
+ }
+
+ Console.WriteLine();
+ }
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs b/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs
index 93ab9a29..e3358fb9 100644
--- a/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs
@@ -8,7 +8,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
- public class RoundtripProcessingBenchmark
+ public class RoundtripProcessingBenchmark : BaseBenchmark
{
[GlobalSetup]
public void GlobalSetup()
diff --git a/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs b/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs
index e4824a89..fbac6dc0 100644
--- a/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs
@@ -1,3 +1,7 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Tests.Mockups;
@@ -8,7 +12,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
- public class ServerProcessingBenchmark
+ public class ServerProcessingBenchmark : BaseBenchmark
{
[GlobalSetup]
public void GlobalSetup()
diff --git a/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs b/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs
index 0449fe62..7825a941 100644
--- a/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/SubscribeBenchmark.cs
@@ -1,3 +1,7 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
@@ -8,7 +12,7 @@ using System.Linq;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
- public class SubscribeBenchmark
+ public class SubscribeBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttClient _mqttClient;
diff --git a/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs b/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs
index 535e207f..cc84f379 100644
--- a/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
- public class TcpPipesBenchmark
+ public class TcpPipesBenchmark : BaseBenchmark
{
IDuplexPipe _client;
IDuplexPipe _server;
diff --git a/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs b/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs
index 0e080e8c..a75d3521 100644
--- a/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
- public class TopicFilterComparerBenchmark
+ public class TopicFilterComparerBenchmark : BaseBenchmark
{
static readonly char[] TopicLevelSeparator = { '/' };
diff --git a/Source/MQTTnet.Benchmarks/TopicGenerator.cs b/Source/MQTTnet.Benchmarks/TopicGenerator.cs
index cb17fee4..d4f9d8e7 100644
--- a/Source/MQTTnet.Benchmarks/TopicGenerator.cs
+++ b/Source/MQTTnet.Benchmarks/TopicGenerator.cs
@@ -1,31 +1,30 @@
using System;
using System.Collections.Generic;
-using System.Text;
namespace MQTTnet.Benchmarks
{
public class TopicGenerator
{
public static void Generate(
- int numPublishers, int numTopicsPerPublisher,
- out Dictionary> topicsByPublisher,
- out Dictionary> singleWildcardTopicsByPublisher,
- out Dictionary> multiWildcardTopicsByPublisher
- )
+ int numPublishers,
+ int numTopicsPerPublisher,
+ out Dictionary> topicsByPublisher,
+ out Dictionary> singleWildcardTopicsByPublisher,
+ out Dictionary> multiWildcardTopicsByPublisher)
{
topicsByPublisher = new Dictionary>();
singleWildcardTopicsByPublisher = new Dictionary>();
multiWildcardTopicsByPublisher = new Dictionary>();
// Find some reasonable distribution across three topic levels
- var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0));
+ var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
if (topicsPerLevel <= 0)
{
topicsPerLevel = 1;
}
- int numLevel1Topics = topicsPerLevel;
- int numLevel2Topics = topicsPerLevel;
+ var numLevel1Topics = topicsPerLevel;
+ var numLevel2Topics = topicsPerLevel;
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
if (maxNumLevel3Topics <= 0)
@@ -35,7 +34,7 @@ namespace MQTTnet.Benchmarks
for (var p = 0; p < numPublishers; ++p)
{
- int publisherTopicCount = 0;
+ var publisherTopicCount = 0;
var publisherName = "pub" + p;
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
{
@@ -44,19 +43,22 @@ namespace MQTTnet.Benchmarks
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
{
if (publisherTopicCount >= numTopicsPerPublisher)
+ {
break;
+ }
- var topic = string.Format("{0}/building{1}/level{2}/sensor{3}", publisherName, l1 + 1, l2 + 1, l3 + 1);
+ var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
AddPublisherTopic(publisherName, topic, topicsByPublisher);
if (l2 == 0)
{
- var singleWildcardTopic = string.Format("{0}/building{1}/+/sensor{2}", publisherName, l1 + 1, l3 + 1);
+ var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
}
- if ((l1 == 0) && (l3 == 0))
+
+ if (l1 == 0 && l3 == 0)
{
- var multiWildcardTopic = string.Format("{0}/+/level{1}/+", publisherName, l2 + 1);
+ var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
}
@@ -75,7 +77,8 @@ namespace MQTTnet.Benchmarks
topicList = new List();
topicsByPublisher.Add(publisherName, topicList);
}
+
topicList.Add(topic);
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs b/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs
index 6b4be7e1..e18a71df 100644
--- a/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/UnsubscribeBenchmark.cs
@@ -1,3 +1,7 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
using BenchmarkDotNet.Attributes;
using MQTTnet.Client;
using MQTTnet.Server;
@@ -7,7 +11,7 @@ using System.Linq;
namespace MQTTnet.Benchmarks
{
[MemoryDiagnoser]
- public class UnsubscribeBenchmark
+ public class UnsubscribeBenchmark : BaseBenchmark
{
MqttServer _mqttServer;
IMqttClient _mqttClient;
diff --git a/Source/MQTTnet.Benchmarks/packages.config b/Source/MQTTnet.Benchmarks/packages.config
deleted file mode 100644
index bb930145..00000000
--- a/Source/MQTTnet.Benchmarks/packages.config
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
From 27f247487631f0e788c62d57dbe2e8aa7781f048 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=80=81=E4=B9=9D?= <366193849@qq.com>
Date: Sat, 3 Dec 2022 20:14:07 +0800
Subject: [PATCH 15/48] fix MqttBufferReader #1597 (#1598)
---
Source/MQTTnet/Formatter/MqttBufferReader.cs | 83 +++++++++++---------
1 file changed, 46 insertions(+), 37 deletions(-)
diff --git a/Source/MQTTnet/Formatter/MqttBufferReader.cs b/Source/MQTTnet/Formatter/MqttBufferReader.cs
index 51c8225f..3aa820a6 100644
--- a/Source/MQTTnet/Formatter/MqttBufferReader.cs
+++ b/Source/MQTTnet/Formatter/MqttBufferReader.cs
@@ -2,25 +2,27 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System;
-using System.Runtime.CompilerServices;
-using System.Text;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
namespace MQTTnet.Formatter
{
public sealed class MqttBufferReader
{
byte[] _buffer = EmptyBuffer.Array;
- int _maxPosition;
int _offset;
+ int _position;
+ int _maxPosition;
- public int BytesLeft => _maxPosition - Position;
+ public int BytesLeft => _maxPosition - _position;
public bool EndOfStream => BytesLeft == 0;
- public int Position { get; private set; }
+ public int Position => _position - _offset;
public byte[] ReadBinaryData()
{
@@ -34,8 +36,8 @@ namespace MQTTnet.Formatter
ValidateReceiveBuffer(length);
var result = new byte[length];
- Array.Copy(_buffer, Position + _offset, result, 0, length);
- Position += length;
+ Array.Copy(_buffer, _position, result, 0, length);
+ _position += length;
return result;
}
@@ -43,8 +45,7 @@ namespace MQTTnet.Formatter
public byte ReadByte()
{
ValidateReceiveBuffer(1);
-
- return _buffer[Position++];
+ return _buffer[_position++];
}
public uint ReadFourByteInteger()
@@ -52,34 +53,36 @@ namespace MQTTnet.Formatter
ValidateReceiveBuffer(4);
#if NETCOREAPP3_0_OR_GREATER
- var value = System.Runtime.InteropServices.MemoryMarshal.Read(_buffer.AsSpan().Slice(Position));
- value = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value);
+ var value = MemoryMarshal.Read(_buffer.AsSpan(_position));
+ if (BitConverter.IsLittleEndian)
+ {
+ value = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value);
+ }
#else
- var byte0 = _buffer[_offset + Position];
- var byte1 = _buffer[_offset + Position + 1];
- var byte2 = _buffer[_offset + Position + 2];
- var byte3 = _buffer[_offset + Position + 3];
+ var byte0 = _buffer[_position];
+ var byte1 = _buffer[_position + 1];
+ var byte2 = _buffer[_position + 2];
+ var byte3 = _buffer[_position + 3];
var value = (uint)((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
#endif
- Position += 4;
+ _position += 4;
return value;
}
public byte[] ReadRemainingData()
{
- var bufferLength = _maxPosition - Position - _offset;
-
+ var bufferLength = BytesLeft;
if (bufferLength == 0)
{
return EmptyBuffer.Array;
}
var buffer = new byte[bufferLength];
- Array.Copy(_buffer, Position + _offset, buffer, 0, bufferLength);
+ Array.Copy(_buffer, _position, buffer, 0, bufferLength);
- Position += bufferLength;
+ _position += bufferLength;
return buffer;
}
@@ -97,12 +100,12 @@ namespace MQTTnet.Formatter
#if NETCOREAPP3_0_OR_GREATER
// AsSpan() version is slightly faster. Not much but at least a little bit.
- var result = Encoding.UTF8.GetString(_buffer.AsSpan(_offset + Position, length));
+ var result = Encoding.UTF8.GetString(_buffer.AsSpan(_position, length));
#else
- var result = Encoding.UTF8.GetString(_buffer, _offset + Position, length);
+ var result = Encoding.UTF8.GetString(_buffer, _position, length);
#endif
- Position += length;
+ _position += length;
return result;
}
@@ -111,16 +114,19 @@ namespace MQTTnet.Formatter
ValidateReceiveBuffer(2);
#if NETCOREAPP3_0_OR_GREATER
- var value = System.Runtime.InteropServices.MemoryMarshal.Read(_buffer.AsSpan().Slice(Position));
- value = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value);
+ var value = MemoryMarshal.Read(_buffer.AsSpan(_position));
+ if (BitConverter.IsLittleEndian)
+ {
+ value = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value);
+ }
#else
- var msb = _buffer[_offset + Position];
- var lsb = _buffer[_offset + Position + 1];
+ var msb = _buffer[_position];
+ var lsb = _buffer[_position + 1];
var value = (ushort)((msb << 8) | lsb);
#endif
-
- Position += 2;
+
+ _position += 2;
return value;
}
@@ -148,26 +154,29 @@ namespace MQTTnet.Formatter
public void Seek(int position)
{
- Position = position;
+ _position = _offset + position;
+ }
+
+ public void SetBuffer(ArraySegment buffer)
+ {
+ SetBuffer(buffer.Array, buffer.Offset, buffer.Count);
}
public void SetBuffer(byte[] buffer, int offset, int length)
{
_buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
_offset = offset;
-
- Position = 0;
- _maxPosition = length;
+ _position = offset;
+ _maxPosition = offset + length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void ValidateReceiveBuffer(int length)
{
- var newPosition = Position + _offset + length;
-
+ var newPosition = _position + length;
if (_maxPosition < newPosition)
{
- throw new MqttProtocolViolationException($"Expected at least {Position + length} bytes but there are only {_maxPosition} bytes");
+ throw new MqttProtocolViolationException($"Expected at least {newPosition} bytes but there are only {_maxPosition} bytes");
}
}
}
From 862d76471b748b13f1da1981bcbc653693378aad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=80=81=E4=B9=9D?= <366193849@qq.com>
Date: Sat, 3 Dec 2022 20:17:02 +0800
Subject: [PATCH 16/48] Implement buffer copy using different policies (#1599)
use Memory-Copy
Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
---
.../MQTTnet.Benchmarks.csproj | 3 +-
.../MQTTnet.Benchmarks/MemoryCopyBenchmark.cs | 40 +++++++++++++++++++
Source/MQTTnet/Formatter/MqttBufferReader.cs | 5 +--
Source/MQTTnet/Formatter/MqttBufferWriter.cs | 5 ++-
Source/MQTTnet/Formatter/MqttPacketBuffer.cs | 8 ++--
Source/MQTTnet/Internal/Memory.cs | 29 ++++++++++++++
Source/MQTTnet/MQTTnet.csproj | 1 +
7 files changed, 81 insertions(+), 10 deletions(-)
create mode 100644 Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs
create mode 100644 Source/MQTTnet/Internal/Memory.cs
diff --git a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
index 9cac34d0..899b655f 100644
--- a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
+++ b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
@@ -1,6 +1,7 @@
-
+
+ true
Exe
Full
net6.0
diff --git a/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs b/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs
new file mode 100644
index 00000000..39fa4f38
--- /dev/null
+++ b/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs
@@ -0,0 +1,40 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using System;
+
+
+namespace MQTTnet.Benchmarks
+{
+ [SimpleJob(RuntimeMoniker.Net60)]
+ [RPlotExporter, RankColumn]
+ [MemoryDiagnoser]
+ public class MemoryCopyBenchmark
+ {
+ const int max_length = 1024 * 8;
+ private byte[] source;
+ private byte[] target;
+
+ [Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
+ public int Length { get; set; }
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ source = new byte[max_length];
+ target = new byte[max_length];
+ }
+
+ [Benchmark(Baseline = true)]
+ public void Array_Copy()
+ {
+ Array.Copy(source, 0, target, 0, Length);
+ }
+
+ [Benchmark]
+ public void Memory_Copy()
+ {
+ MQTTnet.Internal.Memory.Copy(source, 0, target, 0, Length);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Formatter/MqttBufferReader.cs b/Source/MQTTnet/Formatter/MqttBufferReader.cs
index 3aa820a6..4b3ef3c4 100644
--- a/Source/MQTTnet/Formatter/MqttBufferReader.cs
+++ b/Source/MQTTnet/Formatter/MqttBufferReader.cs
@@ -36,7 +36,7 @@ namespace MQTTnet.Formatter
ValidateReceiveBuffer(length);
var result = new byte[length];
- Array.Copy(_buffer, _position, result, 0, length);
+ Memory.Copy(_buffer, _position, result, 0, length);
_position += length;
return result;
@@ -80,8 +80,7 @@ namespace MQTTnet.Formatter
}
var buffer = new byte[bufferLength];
- Array.Copy(_buffer, _position, buffer, 0, bufferLength);
-
+ Memory.Copy(_buffer, _position, buffer, 0, bufferLength);
_position += bufferLength;
return buffer;
diff --git a/Source/MQTTnet/Formatter/MqttBufferWriter.cs b/Source/MQTTnet/Formatter/MqttBufferWriter.cs
index 7e740630..8e572d22 100644
--- a/Source/MQTTnet/Formatter/MqttBufferWriter.cs
+++ b/Source/MQTTnet/Formatter/MqttBufferWriter.cs
@@ -6,6 +6,7 @@ using System;
using System.Runtime.CompilerServices;
using System.Text;
using MQTTnet.Exceptions;
+using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Formatter
@@ -125,7 +126,7 @@ namespace MQTTnet.Formatter
EnsureAdditionalCapacity(count);
- Array.Copy(buffer, offset, _buffer, _position, count);
+ Memory.Copy(buffer, offset, _buffer, _position, count);
IncreasePosition(count);
}
@@ -149,7 +150,7 @@ namespace MQTTnet.Formatter
_buffer[_position] = (byte)(valueLength >> 8);
_buffer[_position + 1] = (byte)valueLength;
- Array.Copy(value, 0, _buffer, _position + 2, valueLength);
+ Memory.Copy(value, 0, _buffer, _position + 2, valueLength);
IncreasePosition(valueLength + 2);
}
}
diff --git a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs
index 8bdd51c8..7a3afea1 100644
--- a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs
+++ b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs
@@ -43,8 +43,8 @@ namespace MQTTnet.Formatter
}
var buffer = new byte[Length];
- Array.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
- Array.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
+ Memory.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
+ Memory.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
return buffer;
}
@@ -57,8 +57,8 @@ namespace MQTTnet.Formatter
}
var buffer = new byte[Length];
- Array.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
- Array.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
+ Memory.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
+ Memory.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
return new ArraySegment(buffer);
}
diff --git a/Source/MQTTnet/Internal/Memory.cs b/Source/MQTTnet/Internal/Memory.cs
new file mode 100644
index 00000000..0d53f6aa
--- /dev/null
+++ b/Source/MQTTnet/Internal/Memory.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace MQTTnet.Internal
+{
+ public static class Memory
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Copy(byte[] source, int sourceIndex, byte[] destination, int destinationIndex, int length)
+ {
+#if NETCOREAPP3_1_OR_GREATER || NETSTANDARD2_1
+ source.AsSpan(sourceIndex, length).CopyTo(destination.AsSpan(destinationIndex, length));
+#elif NET461_OR_GREATER || NETSTANDARD1_3_OR_GREATER
+ unsafe
+ {
+ fixed (byte* pSoure = &source[sourceIndex])
+ {
+ fixed (byte* pDestination = &destination[destinationIndex])
+ {
+ System.Buffer.MemoryCopy(pSoure, pDestination, length, length);
+ }
+ }
+ }
+#else
+ Array.Copy(source, sourceIndex, destination, destinationIndex, length);
+#endif
+ }
+ }
+}
diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj
index c8bf7f0a..6153cce0 100644
--- a/Source/MQTTnet/MQTTnet.csproj
+++ b/Source/MQTTnet/MQTTnet.csproj
@@ -42,6 +42,7 @@
MIT
true
1591
+ true
From 3d32faa8f988e2293379b6bb6366985aa104a076 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=80=81=E4=B9=9D?= <366193849@qq.com>
Date: Sat, 3 Dec 2022 20:18:30 +0800
Subject: [PATCH 17/48] Avoid copying buffer from ReadOnlySequence as
ReceivedMqttPacket body (#1596)
* Avoid copying buffer from ReadOnlySequence as ReceivedMqttPacket body
* Use ReadOnlySequence.FirstSpan if possible.
* add ReaderExtensionsBenchmark
* update benchmark
* fix fixedHeader incorrect read
Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
---
Source/MQTTnet.AspnetCore/ReaderExtensions.cs | 73 ++++--
.../ReaderExtensionsBenchmark.cs | 241 ++++++++++++++++++
2 files changed, 291 insertions(+), 23 deletions(-)
create mode 100644 Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs
diff --git a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs
index d3b00ab6..c7d289fd 100644
--- a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs
+++ b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs
@@ -2,21 +2,23 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System;
-using System.Buffers;
using MQTTnet.Adapter;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
+using System;
+using System.Buffers;
+using System.Linq;
+using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore
{
public static class ReaderExtensions
{
- public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
- in ReadOnlySequence input,
- out MqttPacket packet,
- out SequencePosition consumed,
+ public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
+ in ReadOnlySequence input,
+ out MqttPacket packet,
+ out SequencePosition consumed,
out SequencePosition observed,
out int bytesRead)
{
@@ -33,22 +35,22 @@ namespace MQTTnet.AspNetCore
return false;
}
- var fixedHeader = copy.First.Span[0];
- if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
+ if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
{
return false;
}
+ var fixedHeader = copy.First.Span[0];
+ copy = copy.Slice(headerLength);
if (copy.Length < bodyLength)
{
return false;
}
var bodySlice = copy.Slice(0, bodyLength);
- var buffer = bodySlice.GetMemory().ToArray();
-
- var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment(buffer, 0, buffer.Length), buffer.Length + 2);
-
+ var bodySegment = GetArraySegment(ref bodySlice);
+
+ var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{
formatter.DetectProtocolVersion(receivedMqttPacket);
@@ -61,18 +63,41 @@ namespace MQTTnet.AspNetCore
return true;
}
- static ReadOnlyMemory GetMemory(this in ReadOnlySequence input)
+ static ArraySegment GetArraySegment(ref ReadOnlySequence input)
{
- if (input.IsSingleSegment)
+ if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
{
- return input.First;
+ return segment;
}
// Should be rare
- return input.ToArray();
+ var array = input.ToArray();
+ return new ArraySegment(array);
}
static bool TryReadBodyLength(ref ReadOnlySequence input, out int headerLength, out int bodyLength)
+ {
+ var valueSequence = input.Slice(0, Math.Min(5, input.Length));
+ if (valueSequence.IsSingleSegment)
+ {
+ var valueSpan =
+#if NET5_0_OR_GREATER
+ valueSequence.FirstSpan;
+#else
+ valueSequence.First.Span;
+#endif
+ return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
+ }
+ else
+ {
+ Span valueSpan = stackalloc byte[8];
+ valueSequence.CopyTo(valueSpan);
+ valueSpan = valueSpan.Slice(0, (int)valueSequence.Length);
+ return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
+ }
+ }
+
+ static bool TryReadBodyLength(ReadOnlySpan span, out int headerLength, out int bodyLength)
{
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
var multiplier = 1;
@@ -81,33 +106,35 @@ namespace MQTTnet.AspNetCore
var index = 1;
headerLength = 0;
bodyLength = 0;
-
- var temp = input.Slice(0, Math.Min(5, input.Length)).GetMemory().Span;
do
{
- if (index == temp.Length)
+ if (index == span.Length)
{
return false;
}
- encodedByte = temp[index];
+ encodedByte = span[index];
index++;
value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128)
{
- throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
+ ThrowProtocolViolationException(span, index);
}
multiplier *= 128;
} while ((encodedByte & 128) != 0);
- input = input.Slice(index);
-
headerLength = index;
bodyLength = value;
return true;
}
+
+
+ static void ThrowProtocolViolationException(ReadOnlySpan valueSpan, int index)
+ {
+ throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", valueSpan.Slice(1, index).ToArray())}).");
+ }
}
}
diff --git a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs
new file mode 100644
index 00000000..1d50b904
--- /dev/null
+++ b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs
@@ -0,0 +1,241 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using MQTTnet.Adapter;
+using MQTTnet.AspNetCore;
+using MQTTnet.Exceptions;
+using MQTTnet.Formatter;
+using MQTTnet.Packets;
+using System;
+using System.Buffers;
+using System.IO;
+using System.IO.Pipelines;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MQTTnet.Benchmarks
+{
+ [SimpleJob(RuntimeMoniker.Net60)]
+ [RPlotExporter, RankColumn]
+ [MemoryDiagnoser]
+ public class ReaderExtensionsBenchmark
+ {
+ private MqttPacketFormatterAdapter mqttPacketFormatter;
+ private MemoryStream stream;
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
+ var mqttMessage = new MqttApplicationMessageBuilder()
+ .WithTopic("topic")
+ .WithPayload(new byte[10 * 1024])
+ .Build();
+
+ var packetFactories = new MqttPacketFactories();
+ var packet = packetFactories.Publish.Create(mqttMessage);
+
+ var buffer = mqttPacketFormatter.Encode(packet);
+ stream = new MemoryStream();
+ stream.Write(buffer.Packet);
+ stream.Write(buffer.Payload);
+ mqttPacketFormatter.Cleanup();
+ }
+
+ [Benchmark(Baseline = true)]
+ public async Task Before()
+ {
+ stream.Position = 0;
+ var input = PipeReader.Create(stream);
+
+ while (true)
+ {
+ ReadResult readResult;
+ var readTask = input.ReadAsync();
+ if (readTask.IsCompleted)
+ {
+ readResult = readTask.Result;
+ }
+ else
+ {
+ readResult = await readTask.ConfigureAwait(false);
+ }
+
+ var buffer = readResult.Buffer;
+
+ var consumed = buffer.Start;
+ var observed = buffer.Start;
+
+ try
+ {
+ if (!buffer.IsEmpty)
+ {
+ if (ReaderExtensions_Before.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
+ {
+ break;
+ }
+ }
+ else if (readResult.IsCompleted)
+ {
+ throw new MqttCommunicationException("Connection Aborted");
+ }
+ }
+ finally
+ {
+ // The buffer was sliced up to where it was consumed, so we can just advance to the start.
+ // We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
+ // before yielding the read again.
+ input.AdvanceTo(consumed, observed);
+ }
+ }
+ }
+
+ [Benchmark]
+ public async Task After()
+ {
+ stream.Position = 0;
+ var input = PipeReader.Create(stream);
+
+ while (true)
+ {
+ ReadResult readResult;
+ var readTask = input.ReadAsync();
+ if (readTask.IsCompleted)
+ {
+ readResult = readTask.Result;
+ }
+ else
+ {
+ readResult = await readTask.ConfigureAwait(false);
+ }
+
+ var buffer = readResult.Buffer;
+
+ var consumed = buffer.Start;
+ var observed = buffer.Start;
+
+ try
+ {
+ if (!buffer.IsEmpty)
+ {
+ if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
+ {
+ break;
+ }
+ }
+ else if (readResult.IsCompleted)
+ {
+ throw new MqttCommunicationException("Connection Aborted");
+ }
+ }
+ finally
+ {
+ // The buffer was sliced up to where it was consumed, so we can just advance to the start.
+ // We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
+ // before yielding the read again.
+ input.AdvanceTo(consumed, observed);
+ }
+ }
+ }
+
+ public static class ReaderExtensions_Before
+ {
+ public static bool TryDecode(MqttPacketFormatterAdapter formatter,
+ in ReadOnlySequence input,
+ out MqttPacket packet,
+ out SequencePosition consumed,
+ out SequencePosition observed,
+ out int bytesRead)
+ {
+ if (formatter == null) throw new ArgumentNullException(nameof(formatter));
+
+ packet = null;
+ consumed = input.Start;
+ observed = input.End;
+ bytesRead = 0;
+ var copy = input;
+
+ if (copy.Length < 2)
+ {
+ return false;
+ }
+
+ var fixedHeader = copy.First.Span[0];
+ if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
+ {
+ return false;
+ }
+
+ if (copy.Length < bodyLength)
+ {
+ return false;
+ }
+
+ var bodySlice = copy.Slice(0, bodyLength);
+ var buffer = GetMemory(bodySlice).ToArray();
+
+ var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment(buffer, 0, buffer.Length), buffer.Length + 2);
+
+ if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
+ {
+ formatter.DetectProtocolVersion(receivedMqttPacket);
+ }
+
+ packet = formatter.Decode(receivedMqttPacket);
+ consumed = bodySlice.End;
+ observed = bodySlice.End;
+ bytesRead = headerLength + bodyLength;
+ return true;
+ }
+
+ static ReadOnlyMemory GetMemory(in ReadOnlySequence input)
+ {
+ if (input.IsSingleSegment)
+ {
+ return input.First;
+ }
+
+ // Should be rare
+ return input.ToArray();
+ }
+
+ static bool TryReadBodyLength(ref ReadOnlySequence input, out int headerLength, out int bodyLength)
+ {
+ // Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
+ var multiplier = 1;
+ var value = 0;
+ byte encodedByte;
+ var index = 1;
+ headerLength = 0;
+ bodyLength = 0;
+
+ var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
+
+ do
+ {
+ if (index == temp.Length)
+ {
+ return false;
+ }
+
+ encodedByte = temp[index];
+ index++;
+
+ value += (byte)(encodedByte & 127) * multiplier;
+ if (multiplier > 128 * 128 * 128)
+ {
+ throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
+ }
+
+ multiplier *= 128;
+ } while ((encodedByte & 128) != 0);
+
+ input = input.Slice(index);
+
+ headerLength = index;
+ bodyLength = value;
+ return true;
+ }
+ }
+
+ }
+}
From e400bfb79ea58130eaa5220ad0ab57bb81e81fad Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Sat, 3 Dec 2022 13:19:37 +0100
Subject: [PATCH 18/48] Update ci.yml
---
.github/workflows/ci.yml | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 86873f2e..28979bf3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -56,12 +56,8 @@ jobs:
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net6.0\MQTTnet.Tests.dll
- name: ASP.NET Tests
- run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
+ run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\net6.0\MQTTnet.AspNetCore.Tests.dll
- name: Publish MyGet nugets
if: ${{ github.event_name == 'push' }}
- run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
-
-# - name: Publish nugets
-# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
-# run: dotnet nuget push **/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate
\ No newline at end of file
+ run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
\ No newline at end of file
From d9da7cc92eb4eb58b602a52f3b1dd7cba66e191f Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Sat, 3 Dec 2022 14:09:27 +0100
Subject: [PATCH 19/48] Refactor code
---
.github/workflows/ReleaseNotes.md | 4 +-
.../MQTTnet.Benchmarks/MemoryCopyBenchmark.cs | 2 +-
.../Formatter/MqttBufferReader_Tests.cs | 200 ++++++++++++++++++
Source/MQTTnet/Formatter/MqttBufferReader.cs | 28 +--
Source/MQTTnet/Formatter/MqttBufferWriter.cs | 4 +-
Source/MQTTnet/Formatter/MqttPacketBuffer.cs | 8 +-
.../{Memory.cs => MqttMemoryHelper.cs} | 8 +-
7 files changed, 225 insertions(+), 29 deletions(-)
rename Source/MQTTnet/Internal/{Memory.cs => MqttMemoryHelper.cs} (71%)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index 4401f369..0b38d44b 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -1,5 +1,7 @@
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593).
+* [Core] Performance improvements and reduced memory usage due to improved buffer copies (#1599, thanks to @xljiulang).
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
-* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
\ No newline at end of file
+* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
+* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang).
\ No newline at end of file
diff --git a/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs b/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs
index 39fa4f38..0733e2bb 100644
--- a/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs
@@ -33,7 +33,7 @@ namespace MQTTnet.Benchmarks
[Benchmark]
public void Memory_Copy()
{
- MQTTnet.Internal.Memory.Copy(source, 0, target, 0, Length);
+ MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length);
}
}
diff --git a/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs
index 0c7bf994..cf709af8 100644
--- a/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs
+++ b/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs
@@ -2,6 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
+using System.Collections.Generic;
+using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
@@ -67,6 +70,172 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(5, remainingData.Length);
}
+ [TestMethod]
+ public void Read_Various_Positions_and_Offsets()
+ {
+ const int NumBufferElements = 1000;
+ const int NumTestSequences = 10000;
+
+ const string TestString = "The quick brown fox jumps over the lazy dog.";
+
+ var rndSeed = Environment.TickCount;
+ // rndSeed = ...; // assign random seed here if there is an error so that it can be repeated
+ var rnd = new Random(rndSeed);
+ // rndSeed is printed when an assert fails
+
+ var bufferElements = new List();
+
+ byte[] elementBuffer;
+
+ // Fill a buffer with strings or numbers in big endian format
+ using (var ms = new MemoryStream())
+ {
+ using (var bw = new BinaryWriter(ms))
+ {
+ var bufferOffset = 0;
+
+ for (var i = 0; i < NumBufferElements; i++)
+ {
+ var nextElementType = (ElementReference.BufferElementType)rnd.Next(ElementReference.NumBufferElementTypes);
+
+ byte[] elementBytes = null;
+ uint elementNumberValue = 0; // all element number values fit into uint
+ string elementStringValue = null;
+ var elementSize = 0;
+
+ var alreadyBigEndian = false;
+ switch (nextElementType)
+ {
+ case ElementReference.BufferElementType.Byte:
+ var byteValue = (byte)i;
+ elementNumberValue = byteValue;
+ elementSize = sizeof(byte);
+ elementBytes = new byte[1] { byteValue };
+ alreadyBigEndian = true; // nothing to swap
+ break;
+ case ElementReference.BufferElementType.TwoByteInt:
+ var ushortValue = (ushort)i;
+ elementNumberValue = ushortValue;
+ elementSize = sizeof(ushort);
+ elementBytes = BitConverter.GetBytes(ushortValue);
+ break;
+ case ElementReference.BufferElementType.FourByteInt:
+ var uintValue = (uint)i;
+ elementNumberValue = uintValue;
+ elementSize = sizeof(uint);
+ elementBytes = BitConverter.GetBytes(uintValue);
+ break;
+ case ElementReference.BufferElementType.VariableSizeInt:
+ {
+ elementNumberValue = (uint)i;
+ var writer = new MqttBufferWriter(4, 4);
+ writer.WriteVariableByteInteger(elementNumberValue);
+ elementSize = writer.Length;
+ elementBytes = new byte[elementSize];
+ var buffer = writer.GetBuffer();
+ Array.Copy(buffer, elementBytes, elementSize);
+ alreadyBigEndian = true; // nothing to swap
+ }
+ break;
+ case ElementReference.BufferElementType.String:
+ {
+ var stringLen = rnd.Next(TestString.Length);
+ elementStringValue = TestString.Substring(0, stringLen); // could be empty
+ var writer = new MqttBufferWriter(stringLen + 1, stringLen + 1);
+ writer.WriteString(elementStringValue);
+ elementSize = writer.Length;
+ elementBytes = new byte[elementSize];
+ var buffer = writer.GetBuffer();
+ Array.Copy(buffer, elementBytes, elementSize);
+ alreadyBigEndian = true; // nothing to swap
+ }
+ break;
+ }
+
+ if (BitConverter.IsLittleEndian && !alreadyBigEndian)
+ {
+ Array.Reverse(elementBytes);
+ }
+
+ bw.Write(elementBytes);
+ bufferElements.Add(new ElementReference(nextElementType, elementSize, elementNumberValue, elementStringValue, bufferOffset));
+ bufferOffset += elementSize;
+ }
+
+ elementBuffer = ms.ToArray();
+ }
+ }
+
+ for (var i = 0; i < NumTestSequences; i++)
+ {
+ var firstElementIndex = rnd.Next(NumBufferElements - 1);
+ // segmentElementIndex will be < NumBufferElements - 1, which means there is at least one byte left for segment element count
+ var elementCount = rnd.Next(NumBufferElements - firstElementIndex) + 1;
+ var lastElementIndex = firstElementIndex + elementCount - 1;
+ // Use element index to get buffer offsets
+ var lastElement = bufferElements[lastElementIndex];
+ var segmentStartPosition = bufferElements[firstElementIndex].BufferOffset;
+ var segmentEndPosition = lastElement.BufferOffset + lastElement.Size;
+ var segmentLength = segmentEndPosition - segmentStartPosition;
+
+ var reader = new MqttBufferReader();
+ reader.SetBuffer(elementBuffer, segmentStartPosition, segmentLength);
+
+ // read all elements in the buffer segment; values should be as expected
+ for (var n = 0; n < elementCount; n++)
+ {
+ var element = bufferElements[firstElementIndex + n];
+ uint elementNumberValue = 0;
+ string elementStringValue = null;
+
+ var expectedPosition = element.BufferOffset - segmentStartPosition;
+ Assert.AreEqual(expectedPosition, reader.Position);
+
+ switch (element.Type)
+ {
+ case ElementReference.BufferElementType.Byte:
+ {
+ elementNumberValue = reader.ReadByte();
+ }
+ break;
+ case ElementReference.BufferElementType.TwoByteInt:
+ {
+ elementNumberValue = reader.ReadTwoByteInteger();
+ }
+ break;
+ case ElementReference.BufferElementType.FourByteInt:
+ {
+ elementNumberValue = reader.ReadFourByteInteger();
+ }
+ break;
+ case ElementReference.BufferElementType.VariableSizeInt:
+ {
+ elementNumberValue = reader.ReadVariableByteInteger();
+ }
+ break;
+ case ElementReference.BufferElementType.String:
+ {
+ elementStringValue = reader.ReadString();
+ }
+ break;
+ }
+
+ if (elementStringValue != null)
+ {
+ Assert.AreEqual(elementStringValue, element.StringValue, $"'{elementStringValue}' not equal '{element.StringValue}' with random seed {rndSeed}.");
+ }
+ else
+ {
+ Assert.AreEqual(elementNumberValue, element.NumberValue, $"{elementNumberValue} not equal {element.NumberValue} with random seed {rndSeed}.");
+ }
+ }
+
+ // confirm end of stream
+ Assert.AreEqual(0, reader.BytesLeft, "BytesLeft not zero as expected with random seed " + rndSeed);
+ Assert.IsTrue(reader.EndOfStream, "Not end of stream as expected with random seed " + rndSeed);
+ }
+ }
+
[TestMethod]
public void Report_Correct_Length_For_Full_Buffer()
{
@@ -106,5 +275,36 @@ namespace MQTTnet.Tests.Formatter
Assert.IsFalse(reader.EndOfStream);
Assert.AreEqual(5, reader.BytesLeft);
}
+
+
+ // Helper class to build up a reference to elements of various types in a buffer
+ class ElementReference
+ {
+ public enum BufferElementType
+ {
+ Byte,
+ TwoByteInt,
+ FourByteInt,
+ VariableSizeInt,
+ String
+ }
+
+ public const int NumBufferElementTypes = 5;
+
+ public ElementReference(BufferElementType type, int size, uint numberValue, string stringValue, int bufferOffset)
+ {
+ Type = type;
+ Size = size;
+ NumberValue = numberValue;
+ StringValue = stringValue;
+ BufferOffset = bufferOffset;
+ }
+
+ public int BufferOffset { get; }
+ public uint NumberValue { get; }
+ public int Size { get; }
+ public string StringValue { get; }
+ public BufferElementType Type { get; }
+ }
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Formatter/MqttBufferReader.cs b/Source/MQTTnet/Formatter/MqttBufferReader.cs
index 4b3ef3c4..a1e462fb 100644
--- a/Source/MQTTnet/Formatter/MqttBufferReader.cs
+++ b/Source/MQTTnet/Formatter/MqttBufferReader.cs
@@ -2,21 +2,23 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using MQTTnet.Exceptions;
-using MQTTnet.Internal;
using System;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
using System.Text;
+using MQTTnet.Exceptions;
+using MQTTnet.Internal;
+#if NETCOREAPP3_0_OR_GREATER
+using System.Buffers.Binary;
+#endif
namespace MQTTnet.Formatter
{
public sealed class MqttBufferReader
{
byte[] _buffer = EmptyBuffer.Array;
+ int _maxPosition;
int _offset;
int _position;
- int _maxPosition;
public int BytesLeft => _maxPosition - _position;
@@ -36,7 +38,7 @@ namespace MQTTnet.Formatter
ValidateReceiveBuffer(length);
var result = new byte[length];
- Memory.Copy(_buffer, _position, result, 0, length);
+ MqttMemoryHelper.Copy(_buffer, _position, result, 0, length);
_position += length;
return result;
@@ -53,11 +55,7 @@ namespace MQTTnet.Formatter
ValidateReceiveBuffer(4);
#if NETCOREAPP3_0_OR_GREATER
- var value = MemoryMarshal.Read(_buffer.AsSpan(_position));
- if (BitConverter.IsLittleEndian)
- {
- value = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value);
- }
+ var value = BinaryPrimitives.ReadUInt32BigEndian(_buffer.AsSpan(_position));
#else
var byte0 = _buffer[_position];
var byte1 = _buffer[_position + 1];
@@ -80,7 +78,7 @@ namespace MQTTnet.Formatter
}
var buffer = new byte[bufferLength];
- Memory.Copy(_buffer, _position, buffer, 0, bufferLength);
+ MqttMemoryHelper.Copy(_buffer, _position, buffer, 0, bufferLength);
_position += bufferLength;
return buffer;
@@ -102,7 +100,7 @@ namespace MQTTnet.Formatter
var result = Encoding.UTF8.GetString(_buffer.AsSpan(_position, length));
#else
var result = Encoding.UTF8.GetString(_buffer, _position, length);
-#endif
+#endif
_position += length;
return result;
@@ -113,11 +111,7 @@ namespace MQTTnet.Formatter
ValidateReceiveBuffer(2);
#if NETCOREAPP3_0_OR_GREATER
- var value = MemoryMarshal.Read(_buffer.AsSpan(_position));
- if (BitConverter.IsLittleEndian)
- {
- value = System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(value);
- }
+ var value = BinaryPrimitives.ReadUInt16BigEndian(_buffer.AsSpan(_position));
#else
var msb = _buffer[_position];
var lsb = _buffer[_position + 1];
diff --git a/Source/MQTTnet/Formatter/MqttBufferWriter.cs b/Source/MQTTnet/Formatter/MqttBufferWriter.cs
index 8e572d22..0849f5b3 100644
--- a/Source/MQTTnet/Formatter/MqttBufferWriter.cs
+++ b/Source/MQTTnet/Formatter/MqttBufferWriter.cs
@@ -126,7 +126,7 @@ namespace MQTTnet.Formatter
EnsureAdditionalCapacity(count);
- Memory.Copy(buffer, offset, _buffer, _position, count);
+ MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count);
IncreasePosition(count);
}
@@ -150,7 +150,7 @@ namespace MQTTnet.Formatter
_buffer[_position] = (byte)(valueLength >> 8);
_buffer[_position + 1] = (byte)valueLength;
- Memory.Copy(value, 0, _buffer, _position + 2, valueLength);
+ MqttMemoryHelper.Copy(value, 0, _buffer, _position + 2, valueLength);
IncreasePosition(valueLength + 2);
}
}
diff --git a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs
index 7a3afea1..4df92b36 100644
--- a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs
+++ b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs
@@ -43,8 +43,8 @@ namespace MQTTnet.Formatter
}
var buffer = new byte[Length];
- Memory.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
- Memory.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
+ MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
+ MqttMemoryHelper.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
return buffer;
}
@@ -57,8 +57,8 @@ namespace MQTTnet.Formatter
}
var buffer = new byte[Length];
- Memory.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
- Memory.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
+ MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
+ MqttMemoryHelper.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
return new ArraySegment(buffer);
}
diff --git a/Source/MQTTnet/Internal/Memory.cs b/Source/MQTTnet/Internal/MqttMemoryHelper.cs
similarity index 71%
rename from Source/MQTTnet/Internal/Memory.cs
rename to Source/MQTTnet/Internal/MqttMemoryHelper.cs
index 0d53f6aa..c4a1302a 100644
--- a/Source/MQTTnet/Internal/Memory.cs
+++ b/Source/MQTTnet/Internal/MqttMemoryHelper.cs
@@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
namespace MQTTnet.Internal
{
- public static class Memory
+ public static class MqttMemoryHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy(byte[] source, int sourceIndex, byte[] destination, int destinationIndex, int length)
@@ -13,11 +13,11 @@ namespace MQTTnet.Internal
#elif NET461_OR_GREATER || NETSTANDARD1_3_OR_GREATER
unsafe
{
- fixed (byte* pSoure = &source[sourceIndex])
+ fixed (byte* sourceHandle = &source[sourceIndex])
{
- fixed (byte* pDestination = &destination[destinationIndex])
+ fixed (byte* destinationHandle = &destination[destinationIndex])
{
- System.Buffer.MemoryCopy(pSoure, pDestination, length, length);
+ System.Buffer.MemoryCopy(sourceHandle, destinationHandle, length, length);
}
}
}
From 180e7f79d455a8d7cb98bc6c3c7c9f070cfe0863 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Sat, 3 Dec 2022 14:50:30 +0100
Subject: [PATCH 20/48] Update ci.yml
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 28979bf3..4e2d0287 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -56,7 +56,7 @@ jobs:
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net6.0\MQTTnet.Tests.dll
- name: ASP.NET Tests
- run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\net6.0\MQTTnet.AspNetCore.Tests.dll
+ run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
- name: Publish MyGet nugets
if: ${{ github.event_name == 'push' }}
From d572490a677b2cb941a03cd02926db15c987ec92 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Sun, 4 Dec 2022 13:39:50 +0100
Subject: [PATCH 21/48] Use the net452 supporting async task completion source
(#1607)
---
.../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 81 +++++++++----------
1 file changed, 39 insertions(+), 42 deletions(-)
diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
index 205e1591..e3ac567a 100644
--- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
+++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
@@ -2,23 +2,23 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using MQTTnet.Client;
-using MQTTnet.Exceptions;
-using MQTTnet.Protocol;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
+using MQTTnet.Client;
+using MQTTnet.Exceptions;
using MQTTnet.Internal;
+using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc
{
public sealed class MqttRpcClient : IMqttRpcClient
{
- readonly ConcurrentDictionary> _waitingCalls = new ConcurrentDictionary>();
readonly IMqttClient _mqttClient;
readonly MqttRpcClientOptions _options;
-
+ readonly ConcurrentDictionary> _waitingCalls = new ConcurrentDictionary>();
+
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
@@ -27,6 +27,18 @@ namespace MQTTnet.Extensions.Rpc
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
}
+ public void Dispose()
+ {
+ _mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
+
+ foreach (var tcs in _waitingCalls)
+ {
+ tcs.Value.TrySetCanceled();
+ }
+
+ _waitingCalls.Clear();
+ }
+
public async Task ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
using (var timeoutToken = new CancellationTokenSource(timeout))
@@ -49,15 +61,19 @@ namespace MQTTnet.Extensions.Rpc
public async Task ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
{
- if (methodName == null) throw new ArgumentNullException(nameof(methodName));
-
- var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(new TopicGenerationContext
+ if (methodName == null)
{
- MethodName = methodName,
- QualityOfServiceLevel = qualityOfServiceLevel,
- MqttClient = _mqttClient,
- Options = _options
- });
+ throw new ArgumentNullException(nameof(methodName));
+ }
+
+ var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(
+ new TopicGenerationContext
+ {
+ MethodName = methodName,
+ QualityOfServiceLevel = qualityOfServiceLevel,
+ MqttClient = _mqttClient,
+ Options = _options
+ });
var requestTopic = topicNames.RequestTopic;
var responseTopic = topicNames.ResponseTopic;
@@ -72,8 +88,7 @@ namespace MQTTnet.Extensions.Rpc
throw new MqttProtocolViolationException("RPC response topic is empty.");
}
- var requestMessage = new MqttApplicationMessageBuilder()
- .WithTopic(requestTopic)
+ var requestMessage = new MqttApplicationMessageBuilder().WithTopic(requestTopic)
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel)
.WithResponseTopic(responseTopic)
@@ -81,25 +96,23 @@ namespace MQTTnet.Extensions.Rpc
try
{
-#if NET452
- var awaitable = new TaskCompletionSource();
-#else
- var awaitable = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
-#endif
+ var awaitable = new AsyncTaskCompletionSource();
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
{
throw new InvalidOperationException();
}
- var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
- .WithTopicFilter(responseTopic, qualityOfServiceLevel)
- .Build();
+ var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
- using (cancellationToken.Register(() => { awaitable.TrySetCanceled(); }))
+ using (cancellationToken.Register(
+ () =>
+ {
+ awaitable.TrySetCanceled();
+ }))
{
return await awaitable.Task.ConfigureAwait(false);
}
@@ -107,8 +120,8 @@ namespace MQTTnet.Extensions.Rpc
finally
{
_waitingCalls.TryRemove(responseTopic, out _);
-
- await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
+
+ await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
}
}
@@ -119,28 +132,12 @@ namespace MQTTnet.Extensions.Rpc
return CompletedTask.Instance;
}
-#if NET452
- Task.Run(() => awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload));
-#else
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
-#endif
// Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true;
return CompletedTask.Instance;
}
-
- public void Dispose()
- {
- _mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
-
- foreach (var tcs in _waitingCalls)
- {
- tcs.Value.TrySetCanceled();
- }
-
- _waitingCalls.Clear();
- }
}
}
\ No newline at end of file
From 099209fff2e45e8b33a07baffdb4ee7f62645f71 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Sun, 4 Dec 2022 14:10:54 +0100
Subject: [PATCH 22/48] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3d551d53..0aadf450 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[](https://www.nuget.org/packages/MQTTnet)
-[](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
+[](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
[](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)

[](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
From 09e47fb5eb52799c7599dfed947c54b480b0a8fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=80=81=E4=B9=9D?= <366193849@qq.com>
Date: Tue, 6 Dec 2022 02:19:31 +0800
Subject: [PATCH 23/48] Avoid a split MqttPacketBuffer transfer caused by
triggering PipeWriter.WriteAsync twice. (#1609)
Avoid a split MqttPacketBuffer transfer caused by triggering PipeWriter.FlushAsync twice.
---
.../MqttConnectionContext.cs | 50 +++++++++----------
1 file changed, 24 insertions(+), 26 deletions(-)
diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
index 8dd380f8..54c37a7b 100644
--- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
+++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
@@ -2,11 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System;
-using System.IO.Pipelines;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Features;
using MQTTnet.Adapter;
@@ -15,12 +10,16 @@ using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
+using System;
+using System.IO.Pipelines;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
namespace MQTTnet.AspNetCore
{
public sealed class MqttConnectionContext : IMqttChannelAdapter
{
- readonly bool? _isOverWebSocket;
readonly AsyncLock _writerLock = new AsyncLock();
PipeReader _input;
PipeWriter _output;
@@ -30,12 +29,6 @@ namespace MQTTnet.AspNetCore
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
- var feature = connection.Features.Get();
- if (feature?.HttpContext != null)
- {
- _isOverWebSocket = feature.HttpContext.WebSockets.IsWebSocketRequest;
- }
-
_input = Connection.Transport.Input;
_output = Connection.Transport.Output;
}
@@ -181,20 +174,9 @@ namespace MQTTnet.AspNetCore
try
{
var buffer = PacketFormatterAdapter.Encode(packet);
-
- if (_isOverWebSocket == false)
- {
- await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
- if (buffer.Payload.Count > 0)
- {
- await _output.WriteAsync(buffer.Payload, cancellationToken).ConfigureAwait(false);
- }
- }
- else
- {
- var bufferSegment = buffer.Join();
- await _output.WriteAsync(bufferSegment, cancellationToken).ConfigureAwait(false);
- }
+
+ WritePacketBuffer(_output, buffer);
+ await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
BytesSent += buffer.Length;
}
@@ -204,5 +186,21 @@ namespace MQTTnet.AspNetCore
}
}
}
+
+ ///
+ /// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
+ /// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
+ ///
+ ///
+ ///
+ private static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
+ {
+ var span = output.GetSpan(buffer.Length);
+
+ buffer.Packet.AsSpan().CopyTo(span);
+ buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
+
+ output.Advance(buffer.Length);
+ }
}
}
\ No newline at end of file
From bb106f8c6e288ba633f24a8e082147e5c6c729cb Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Mon, 5 Dec 2022 19:21:08 +0100
Subject: [PATCH 24/48] Refactor code
---
.../MqttConnectionContext.cs | 21 ++++++++-----------
1 file changed, 9 insertions(+), 12 deletions(-)
diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
index 54c37a7b..a57df2d9 100644
--- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
+++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
@@ -2,6 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
+using System.IO.Pipelines;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Features;
using MQTTnet.Adapter;
@@ -10,11 +15,6 @@ using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
-using System;
-using System.IO.Pipelines;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading;
-using System.Threading.Tasks;
namespace MQTTnet.AspNetCore
{
@@ -187,14 +187,11 @@ namespace MQTTnet.AspNetCore
}
}
- ///
- /// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
- /// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
- ///
- ///
- ///
- private static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
+ static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
{
+ // copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
+ // MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
+
var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span);
From c17a3507963b0b54a02447da255dbd5fd62a0dc4 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Mon, 5 Dec 2022 19:22:18 +0100
Subject: [PATCH 25/48] 1595 object disposed exception in unit test handle
parallel connection attempts (#1610)
---
MQTTnet.sln.DotSettings | 7 +-
.../MqttConnectionContextTest.cs | 4 +-
.../ReaderExtensionsBenchmark.cs | 7 +-
.../MQTTnet.Extensions.Rpc.csproj | 1 +
.../MqttFactoryExtensions.cs | 10 +-
.../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 13 +-
.../Options/MqttRpcClientOptionsBuilder.cs | 16 +-
...ultMqttRpcClientTopicGenerationStrategy.cs | 5 +
.../TopicGeneration/TopicGenerationContext.cs | 36 +--
.../MQTTnet.Extensions.WebSocket4Net.csproj | 1 +
.../MqttFactoryExtensions.cs | 7 +-
.../WebSocket4NetMqttChannel.cs | 124 ++++++-----
.../WebSocket4NetMqttClientAdapterFactory.cs | 23 +-
Source/MQTTnet.TestApp/PublicBrokerTest.cs | 177 +++++++++------
.../{RPC_Tests.cs => Extensions/Rpc_Tests.cs} | 175 ++++++++-------
.../Extensions/WebSocket4Net_Tests.cs | 51 +++++
...MqttPacketSerialization_V3_Binary_Tests.cs | 2 +-
.../MqttPacketWriterExtensions.cs | 2 +-
.../Helpers/ReflectionExtensions.cs | 28 +++
Source/MQTTnet.Tests/MQTTnet.Tests.csproj | 1 +
Source/MQTTnet.Tests/Server/QoS_Tests.cs | 1 +
Source/MQTTnet.Tests/Server/Session_Tests.cs | 10 +-
Source/MQTTnet/Client/MqttClient.cs | 24 +-
.../Logger/MqttNetSourceLoggerExtensions.cs | 207 ++++++++++--------
.../MqttApplicationMessageFactory.cs | 4 +-
.../MQTTnet/Formatter/MqttPacketFactories.cs | 26 +--
.../Formatter/V3/MqttV3PacketFormatter.cs | 4 +-
.../Formatter/V5/MqttV5PacketDecoder.cs | 2 +-
.../Formatter/V5/MqttV5PacketEncoder.cs | 2 +-
.../CancellationTokenSourceExtensions.cs | 37 ++++
.../MQTTnet/Packets/MqttPacketExtensions.cs | 102 +++++++++
.../MQTTnet/Protocol/MqttControlPacketType.cs | 2 +-
Source/MQTTnet/Server/Internal/MqttClient.cs | 168 +++++++-------
.../Internal/MqttClientSessionsManager.cs | 7 +-
34 files changed, 802 insertions(+), 484 deletions(-)
rename Source/MQTTnet.Tests/{RPC_Tests.cs => Extensions/Rpc_Tests.cs} (95%)
create mode 100644 Source/MQTTnet.Tests/Extensions/WebSocket4Net_Tests.cs
rename Source/MQTTnet.Tests/{Extensions => Helpers}/MqttPacketWriterExtensions.cs (95%)
create mode 100644 Source/MQTTnet.Tests/Helpers/ReflectionExtensions.cs
create mode 100644 Source/MQTTnet/Internal/CancellationTokenSourceExtensions.cs
create mode 100644 Source/MQTTnet/Packets/MqttPacketExtensions.cs
diff --git a/MQTTnet.sln.DotSettings b/MQTTnet.sln.DotSettings
index 2dc673c8..49d8306b 100644
--- a/MQTTnet.sln.DotSettings
+++ b/MQTTnet.sln.DotSettings
@@ -243,6 +243,11 @@ See the LICENSE file in the project root for more information.
True
True
True
+ True
True
+ True
+ True
+ True
True
- True
\ No newline at end of file
+ True
+ True
\ No newline at end of file
diff --git a/Source/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs b/Source/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs
index a9b08a87..865a49ce 100644
--- a/Source/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs
+++ b/Source/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs
@@ -18,7 +18,7 @@ using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
-using MQTTnet.Tests.Extensions;
+using MQTTnet.Tests.Helpers;
namespace MQTTnet.AspNetCore.Tests
{
@@ -35,7 +35,7 @@ namespace MQTTnet.AspNetCore.Tests
connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection);
- await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
+ await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, Array.Empty()));
await Assert.ThrowsExceptionAsync(() => ctx.ReceivePacketAsync(CancellationToken.None));
diff --git a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs
index 1d50b904..4ac0d4f6 100644
--- a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs
+++ b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs
@@ -19,8 +19,8 @@ namespace MQTTnet.Benchmarks
[MemoryDiagnoser]
public class ReaderExtensionsBenchmark
{
- private MqttPacketFormatterAdapter mqttPacketFormatter;
- private MemoryStream stream;
+ MqttPacketFormatterAdapter mqttPacketFormatter;
+ MemoryStream stream;
[GlobalSetup]
public void GlobalSetup()
@@ -31,8 +31,7 @@ namespace MQTTnet.Benchmarks
.WithPayload(new byte[10 * 1024])
.Build();
- var packetFactories = new MqttPacketFactories();
- var packet = packetFactories.Publish.Create(mqttMessage);
+ var packet = MqttPacketFactories.Publish.Create(mqttMessage);
var buffer = mqttPacketFormatter.Encode(packet);
stream = new MemoryStream();
diff --git a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
index d1d76589..8100c0e3 100644
--- a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
+++ b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
@@ -34,6 +34,7 @@
true
true
1591
+ 7.3
diff --git a/Source/MQTTnet.Extensions.Rpc/MqttFactoryExtensions.cs b/Source/MQTTnet.Extensions.Rpc/MqttFactoryExtensions.cs
index 3d4f5170..a8818461 100644
--- a/Source/MQTTnet.Extensions.Rpc/MqttFactoryExtensions.cs
+++ b/Source/MQTTnet.Extensions.Rpc/MqttFactoryExtensions.cs
@@ -11,10 +11,12 @@ namespace MQTTnet.Extensions.Rpc
{
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient)
{
- return factory.CreateMqttRpcClient(mqttClient, new MqttRpcClientOptions
- {
- TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
- });
+ return factory.CreateMqttRpcClient(
+ mqttClient,
+ new MqttRpcClientOptions
+ {
+ TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
+ });
}
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
index e3ac567a..5784b814 100644
--- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
+++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
@@ -17,8 +17,9 @@ namespace MQTTnet.Extensions.Rpc
{
readonly IMqttClient _mqttClient;
readonly MqttRpcClientOptions _options;
+
readonly ConcurrentDictionary> _waitingCalls = new ConcurrentDictionary>();
-
+
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
@@ -66,14 +67,8 @@ namespace MQTTnet.Extensions.Rpc
throw new ArgumentNullException(nameof(methodName));
}
- var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(
- new TopicGenerationContext
- {
- MethodName = methodName,
- QualityOfServiceLevel = qualityOfServiceLevel,
- MqttClient = _mqttClient,
- Options = _options
- });
+ var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
+ var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
var requestTopic = topicNames.RequestTopic;
var responseTopic = topicNames.ResponseTopic;
diff --git a/Source/MQTTnet.Extensions.Rpc/Options/MqttRpcClientOptionsBuilder.cs b/Source/MQTTnet.Extensions.Rpc/Options/MqttRpcClientOptionsBuilder.cs
index dc53ddc0..5fb0ea2e 100644
--- a/Source/MQTTnet.Extensions.Rpc/Options/MqttRpcClientOptionsBuilder.cs
+++ b/Source/MQTTnet.Extensions.Rpc/Options/MqttRpcClientOptionsBuilder.cs
@@ -10,13 +10,6 @@ namespace MQTTnet.Extensions.Rpc
{
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
- public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
- {
- _topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
-
- return this;
- }
-
public MqttRpcClientOptions Build()
{
return new MqttRpcClientOptions
@@ -24,5 +17,12 @@ namespace MQTTnet.Extensions.Rpc
TopicGenerationStrategy = _topicGenerationStrategy
};
}
+
+ public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
+ {
+ _topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
+
+ return this;
+ }
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Extensions.Rpc/Options/TopicGeneration/DefaultMqttRpcClientTopicGenerationStrategy.cs b/Source/MQTTnet.Extensions.Rpc/Options/TopicGeneration/DefaultMqttRpcClientTopicGenerationStrategy.cs
index 11c087d8..e1af7473 100644
--- a/Source/MQTTnet.Extensions.Rpc/Options/TopicGeneration/DefaultMqttRpcClientTopicGenerationStrategy.cs
+++ b/Source/MQTTnet.Extensions.Rpc/Options/TopicGeneration/DefaultMqttRpcClientTopicGenerationStrategy.cs
@@ -10,6 +10,11 @@ namespace MQTTnet.Extensions.Rpc
{
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
{
throw new ArgumentException("The method name cannot contain /, + or #.");
diff --git a/Source/MQTTnet.Extensions.Rpc/Options/TopicGeneration/TopicGenerationContext.cs b/Source/MQTTnet.Extensions.Rpc/Options/TopicGeneration/TopicGenerationContext.cs
index 7185c1ba..03898bb8 100644
--- a/Source/MQTTnet.Extensions.Rpc/Options/TopicGeneration/TopicGenerationContext.cs
+++ b/Source/MQTTnet.Extensions.Rpc/Options/TopicGeneration/TopicGenerationContext.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using MQTTnet.Client;
using MQTTnet.Protocol;
@@ -9,20 +10,29 @@ namespace MQTTnet.Extensions.Rpc
{
public sealed class TopicGenerationContext
{
- public string MethodName { get; set; }
+ public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel)
+ {
+ MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
+ QualityOfServiceLevel = qualityOfServiceLevel;
+ MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
+ Options = options ?? throw new ArgumentNullException(nameof(options));
+ }
+
+ public string MethodName { get; }
+
+ public IMqttClient MqttClient { get; }
+
+ public MqttRpcClientOptions Options { get; }
///
- /// Gets or sets the quality of service level.
- /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message.
- /// There are 3 QoS levels in MQTT:
- /// - At most once (0): Message gets delivered no time, once or multiple times.
- /// - At least once (1): Message gets delivered at least once (one time or more often).
- /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
+ /// Gets or sets the quality of service level.
+ /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
+ /// that defines the guarantee of delivery for a specific message.
+ /// There are 3 QoS levels in MQTT:
+ /// - At most once (0): Message gets delivered no time, once or multiple times.
+ /// - At least once (1): Message gets delivered at least once (one time or more often).
+ /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
///
- public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
-
- public IMqttClient MqttClient { get; set; }
-
- public MqttRpcClientOptions Options { get; set; }
+ public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj b/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj
index 04581cd0..ba3433ee 100644
--- a/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj
+++ b/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj
@@ -34,6 +34,7 @@
true
true
1591
+ 7.3
diff --git a/Source/MQTTnet.Extensions.WebSocket4Net/MqttFactoryExtensions.cs b/Source/MQTTnet.Extensions.WebSocket4Net/MqttFactoryExtensions.cs
index d23286f2..1b056f1b 100644
--- a/Source/MQTTnet.Extensions.WebSocket4Net/MqttFactoryExtensions.cs
+++ b/Source/MQTTnet.Extensions.WebSocket4Net/MqttFactoryExtensions.cs
@@ -10,9 +10,12 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory)
{
- if (mqttFactory == null) throw new ArgumentNullException(nameof(mqttFactory));
+ if (mqttFactory == null)
+ {
+ throw new ArgumentNullException(nameof(mqttFactory));
+ }
return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory());
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs
index d656a92f..859bd2a3 100644
--- a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs
+++ b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttChannel.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Security.Authentication;
@@ -17,14 +18,14 @@ using MQTTnet.Exceptions;
using MQTTnet.Internal;
using SuperSocket.ClientEngine;
using WebSocket4Net;
+using DataReceivedEventArgs = WebSocket4Net.DataReceivedEventArgs;
namespace MQTTnet.Extensions.WebSocket4Net
{
public sealed class WebSocket4NetMqttChannel : IMqttChannel
{
- readonly BlockingCollection _receiveBuffer = new BlockingCollection();
-
readonly MqttClientOptions _clientOptions;
+ readonly BlockingCollection _receiveBuffer = new BlockingCollection();
readonly MqttClientWebSocketOptions _webSocketOptions;
WebSocket _webSocket;
@@ -35,13 +36,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
_webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions));
}
+ public X509Certificate2 ClientCertificate { get; }
+
public string Endpoint => _webSocketOptions.Uri;
public bool IsSecureConnection { get; private set; }
- public X509Certificate2 ClientCertificate { get; }
-
- public async Task ConnectAsync(CancellationToken cancellationToken)
+ public Task ConnectAsync(CancellationToken cancellationToken)
{
var uri = _webSocketOptions.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
@@ -55,11 +56,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
uri = "wss://" + uri;
}
}
+
+ IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
-#if NET48 || NETCOREAPP3_1 || NET5 || NET6
- var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
+#if NET48 || NETCOREAPP3_0_OR_GREATER
+ var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
#else
- var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
+ var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
#endif
var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
@@ -90,7 +93,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
var receiveBufferSize = 0;
var certificates = new X509CertificateCollection();
- if (_webSocketOptions?.TlsOptions?.Certificates != null)
+ if (_webSocketOptions.TlsOptions?.Certificates != null)
{
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
{
@@ -107,15 +110,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
NoDelay = true,
Security =
{
- AllowUnstrustedCertificate = _webSocketOptions?.TlsOptions?.AllowUntrustedCertificates == true,
- AllowCertificateChainErrors = _webSocketOptions?.TlsOptions?.IgnoreCertificateChainErrors == true,
+ AllowUnstrustedCertificate = _webSocketOptions.TlsOptions?.AllowUntrustedCertificates == true,
+ AllowCertificateChainErrors = _webSocketOptions.TlsOptions?.IgnoreCertificateChainErrors == true,
Certificates = certificates
}
};
- await ConnectInternalAsync(cancellationToken).ConfigureAwait(false);
-
- IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
+ return ConnectInternalAsync(cancellationToken);
}
public Task DisconnectAsync(CancellationToken cancellationToken)
@@ -124,10 +125,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
_webSocket.Close();
}
-
+
return CompletedTask.Instance;
}
+ public void Dispose()
+ {
+ if (_webSocket == null)
+ {
+ return;
+ }
+
+ _webSocket.DataReceived -= OnDataReceived;
+ _webSocket.Error -= OnError;
+ _webSocket.Dispose();
+ _webSocket = null;
+ }
+
public Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var readBytes = 0;
@@ -161,75 +175,55 @@ namespace MQTTnet.Extensions.WebSocket4Net
return CompletedTask.Instance;
}
- public void Dispose()
- {
- if (_webSocket == null)
- {
- return;
- }
-
- _webSocket.DataReceived -= OnDataReceived;
- _webSocket.Error -= OnError;
- _webSocket.Dispose();
- _webSocket = null;
- }
-
- static void OnError(object sender, ErrorEventArgs e)
- {
- System.Diagnostics.Debug.Write(e.Exception.ToString());
- }
-
- void OnDataReceived(object sender, DataReceivedEventArgs e)
- {
- foreach (var @byte in e.Data)
- {
- _receiveBuffer.Add(@byte);
- }
- }
-
async Task ConnectInternalAsync(CancellationToken cancellationToken)
{
_webSocket.Error += OnError;
_webSocket.DataReceived += OnDataReceived;
- var taskCompletionSource = new TaskCompletionSource();
+ var promise = new AsyncTaskCompletionSource();
void ErrorHandler(object sender, ErrorEventArgs e)
{
- taskCompletionSource.TrySetResult(e.Exception);
+ promise.TrySetException(e.Exception);
}
- void SuccessHandler(object sender, EventArgs e)
+ void OpenedHandler(object sender, EventArgs e)
{
- taskCompletionSource.TrySetResult(null);
+ promise.TrySetResult(true);
+ }
+
+ void ClosedHandler(object sender, EventArgs e)
+ {
+ promise.TrySetException(new MqttCommunicationException("Connection closed."));
}
try
{
- _webSocket.Opened += SuccessHandler;
+ _webSocket.Opened += OpenedHandler;
+ _webSocket.Closed += ClosedHandler;
_webSocket.Error += ErrorHandler;
-#pragma warning disable AsyncFixer02 // Long-running or blocking operations inside an async method
- _webSocket.Open();
-#pragma warning restore AsyncFixer02 // Long-running or blocking operations inside an async method
-
- using (var timeoutCts = new CancellationTokenSource(_clientOptions.Timeout))
- using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
+ using (var timeout = new CancellationTokenSource(_clientOptions.Timeout))
+ using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken))
{
- using (linkedCts.Token.Register(() => taskCompletionSource.TrySetCanceled()))
+ using (linkedCts.Token.Register(() => promise.TrySetCanceled()))
{
try
{
- await taskCompletionSource.Task.ConfigureAwait(false);
+ // OpenAsync does basically the same but without support for a cancellation token.
+ // So we do it on our own!
+ _webSocket.Open();
+
+ await promise.Task.ConfigureAwait(false);
}
catch (Exception exception)
{
- var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
+ var timeoutReached = timeout.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
if (timeoutReached)
{
throw new MqttCommunicationTimedOutException(exception);
}
-
+
if (exception is AuthenticationException authenticationException)
{
throw new MqttCommunicationException(authenticationException.InnerException);
@@ -251,9 +245,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
}
finally
{
- _webSocket.Opened -= SuccessHandler;
+ _webSocket.Opened -= OpenedHandler;
+ _webSocket.Closed -= ClosedHandler;
_webSocket.Error -= ErrorHandler;
}
}
+
+ void OnDataReceived(object sender, DataReceivedEventArgs e)
+ {
+ foreach (var @byte in e.Data)
+ {
+ _receiveBuffer.Add(@byte);
+ }
+ }
+
+ static void OnError(object sender, ErrorEventArgs e)
+ {
+ Debug.Write(e.Exception.ToString());
+ }
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttClientAdapterFactory.cs b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttClientAdapterFactory.cs
index 69ece623..27359a1a 100644
--- a/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttClientAdapterFactory.cs
+++ b/Source/MQTTnet.Extensions.WebSocket4Net/WebSocket4NetMqttClientAdapterFactory.cs
@@ -2,12 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using MQTTnet.Adapter;
-using MQTTnet.Formatter;
-using MQTTnet.Implementations;
using System;
+using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
+using MQTTnet.Formatter;
+using MQTTnet.Implementations;
namespace MQTTnet.Extensions.WebSocket4Net
{
@@ -15,7 +15,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
{
- if (options == null) throw new ArgumentNullException(nameof(options));
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
switch (options.ChannelOptions)
{
@@ -23,7 +26,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
return new MqttChannelAdapter(
new MqttTcpChannel(options),
- new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)),
+ new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
packetInspector,
logger);
}
@@ -32,16 +35,16 @@ namespace MQTTnet.Extensions.WebSocket4Net
{
return new MqttChannelAdapter(
new WebSocket4NetMqttChannel(options, webSocketOptions),
- new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4068, 65535)),
+ new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
packetInspector,
logger);
}
default:
- {
- throw new NotSupportedException();
- }
+ {
+ throw new NotSupportedException();
+ }
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.TestApp/PublicBrokerTest.cs b/Source/MQTTnet.TestApp/PublicBrokerTest.cs
index 768ba874..f95c332e 100644
--- a/Source/MQTTnet.TestApp/PublicBrokerTest.cs
+++ b/Source/MQTTnet.TestApp/PublicBrokerTest.cs
@@ -2,11 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using MQTTnet.Client;
using System;
using System.Security.Authentication;
using System.Threading;
using System.Threading.Tasks;
+using MQTTnet.Client;
+using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
@@ -17,10 +18,6 @@ namespace MQTTnet.TestApp
{
public static async Task RunAsync()
{
- // MqttNetConsoleLogger.ForwardToConsole();
-
- // For most of these connections to work, set output target to Net5.0.
-
#if NET5_0_OR_GREATER
// TLS13 is only available in Net5.0
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
@@ -31,7 +28,6 @@ namespace MQTTnet.TestApp
CertificateValidationHandler = w => true
};
#endif
-
// Also defining TLS12 for servers that don't seem no to support TLS13.
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
{
@@ -42,33 +38,37 @@ namespace MQTTnet.TestApp
};
// mqtt.eclipseprojects.io
- await ExecuteTestAsync("mqtt.eclipseprojects.io TCP",
- new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883)
- .WithProtocolVersion(MqttProtocolVersion.V311).Build());
+ await ExecuteTestAsync(
+ "mqtt.eclipseprojects.io TCP",
+ new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
- await ExecuteTestAsync("mqtt.eclipseprojects.io WS",
- new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt")
- .WithProtocolVersion(MqttProtocolVersion.V311).Build());
+ await ExecuteTestAsync(
+ "mqtt.eclipseprojects.io WS",
+ new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
+
+ await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
+ new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
+ .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build(),
+ true);
#endif
// test.mosquitto.org
- await ExecuteTestAsync("test.mosquitto.org TCP",
- new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883)
- .WithProtocolVersion(MqttProtocolVersion.V311).Build());
+ await ExecuteTestAsync(
+ "test.mosquitto.org TCP",
+ new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
- await ExecuteTestAsync("test.mosquitto.org TCP - Authenticated",
- new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884)
- .WithCredentials("rw", "readwrite")
- .WithProtocolVersion(MqttProtocolVersion.V311).Build());
+ await ExecuteTestAsync(
+ "test.mosquitto.org TCP - Authenticated",
+ new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build());
- await ExecuteTestAsync("test.mosquitto.org TCP TLS12",
- new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
- .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
+ await ExecuteTestAsync(
+ "test.mosquitto.org TCP TLS12",
+ new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
@@ -76,27 +76,40 @@ namespace MQTTnet.TestApp
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif
- await ExecuteTestAsync("test.mosquitto.org TCP TLS12 - Authenticated",
+ await ExecuteTestAsync(
+ "test.mosquitto.org TCP TLS12 - Authenticated",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
.WithCredentials("rw", "readwrite")
- .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
+ .WithProtocolVersion(MqttProtocolVersion.V311)
+ .WithTls(unsafeTls12)
+ .Build());
- await ExecuteTestAsync("test.mosquitto.org WS",
- new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt")
- .WithProtocolVersion(MqttProtocolVersion.V311).Build());
+ await ExecuteTestAsync(
+ "test.mosquitto.org WS",
+ new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
- await ExecuteTestAsync("test.mosquitto.org WS TLS12",
- new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt")
- .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
+ await ExecuteTestAsync(
+ "test.mosquitto.org WS (WebSocket4Net)",
+ new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
+ true);
+
+ await ExecuteTestAsync(
+ "test.mosquitto.org WS TLS12",
+ new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
+
+ // await ExecuteTestAsync(
+ // "test.mosquitto.org WS TLS12 (WebSocket4Net)",
+ // new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
+ // true);
// broker.emqx.io
- await ExecuteTestAsync("broker.emqx.io TCP",
- new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883)
- .WithProtocolVersion(MqttProtocolVersion.V311).Build());
+ await ExecuteTestAsync(
+ "broker.emqx.io TCP",
+ new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
- await ExecuteTestAsync("broker.emqx.io TCP TLS12",
- new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
- .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
+ await ExecuteTestAsync(
+ "broker.emqx.io TCP TLS12",
+ new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
@@ -104,23 +117,37 @@ namespace MQTTnet.TestApp
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif
- await ExecuteTestAsync("broker.emqx.io WS",
- new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt")
- .WithProtocolVersion(MqttProtocolVersion.V311).Build());
+ await ExecuteTestAsync(
+ "broker.emqx.io WS",
+ new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
- await ExecuteTestAsync("broker.emqx.io WS TLS12",
- new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt")
- .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
+ await ExecuteTestAsync(
+ "broker.emqx.io WS (WebSocket4Net)",
+ new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
+ true);
+ await ExecuteTestAsync(
+ "broker.emqx.io WS TLS12",
+ new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
+
+ await ExecuteTestAsync(
+ "broker.emqx.io WS TLS12 (WebSocket4Net)",
+ new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
+ true);
// broker.hivemq.com
- await ExecuteTestAsync("broker.hivemq.com TCP",
- new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883)
- .WithProtocolVersion(MqttProtocolVersion.V311).Build());
+ await ExecuteTestAsync(
+ "broker.hivemq.com TCP",
+ new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
- await ExecuteTestAsync("broker.hivemq.com WS",
- new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt")
- .WithProtocolVersion(MqttProtocolVersion.V311).Build());
+ await ExecuteTestAsync(
+ "broker.hivemq.com WS",
+ new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
+
+ await ExecuteTestAsync(
+ "broker.hivemq.com WS (WebSocket4Net)",
+ new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
+ true);
// mqtt.swifitch.cz: Does not seem to operate any more
@@ -130,36 +157,43 @@ namespace MQTTnet.TestApp
Console.ReadLine();
}
- static async Task ExecuteTestAsync(string name, MqttClientOptions options)
+ static async Task ExecuteTestAsync(string name, MqttClientOptions options, bool useWebSocket4Net = false)
{
try
{
Write("Testing '" + name + "'... ", ConsoleColor.Gray);
+
var factory = new MqttFactory();
- //factory.UseWebSocket4Net();
- var client = factory.CreateMqttClient();
- var topic = Guid.NewGuid().ToString();
-
- MqttApplicationMessage receivedMessage = null;
- client.ApplicationMessageReceivedAsync += e =>
+ if (useWebSocket4Net)
{
- receivedMessage = e.ApplicationMessage;
- return CompletedTask.Instance;
- };
-
- await client.ConnectAsync(options);
- await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce);
- await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce);
-
- SpinWait.SpinUntil(() => receivedMessage != null, 5000);
-
- if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
- {
- throw new Exception("Message invalid.");
+ factory.UseWebSocket4Net();
}
- await client.UnsubscribeAsync(topic);
- await client.DisconnectAsync();
+ using (var client = factory.CreateMqttClient())
+ {
+ MqttApplicationMessage receivedMessage = null;
+ client.ApplicationMessageReceivedAsync += e =>
+ {
+ receivedMessage = e.ApplicationMessage;
+ return CompletedTask.Instance;
+ };
+
+ await client.ConnectAsync(options).ConfigureAwait(false);
+
+ var topic = Guid.NewGuid().ToString();
+ await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
+ await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
+
+ SpinWait.SpinUntil(() => receivedMessage != null, 5000);
+
+ if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
+ {
+ throw new Exception("Message invalid.");
+ }
+
+ await client.UnsubscribeAsync(topic).ConfigureAwait(false);
+ await client.DisconnectAsync().ConfigureAwait(false);
+ }
Write("[OK]\n", ConsoleColor.Green);
}
@@ -174,6 +208,5 @@ namespace MQTTnet.TestApp
Console.ForegroundColor = color;
Console.Write(message);
}
-
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/RPC_Tests.cs b/Source/MQTTnet.Tests/Extensions/Rpc_Tests.cs
similarity index 95%
rename from Source/MQTTnet.Tests/RPC_Tests.cs
rename to Source/MQTTnet.Tests/Extensions/Rpc_Tests.cs
index f266f5a6..b48d5053 100644
--- a/Source/MQTTnet.Tests/RPC_Tests.cs
+++ b/Source/MQTTnet.Tests/Extensions/Rpc_Tests.cs
@@ -6,68 +6,93 @@ using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using MQTTnet.Tests.Mockups;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Extensions.Rpc;
-using MQTTnet.Protocol;
using MQTTnet.Formatter;
-using MQTTnet.Implementations;
using MQTTnet.Internal;
+using MQTTnet.Protocol;
+using MQTTnet.Tests.Mockups;
-namespace MQTTnet.Tests
+namespace MQTTnet.Tests.Extensions
{
[TestClass]
- public sealed class RPC_Tests : BaseTestClass
+ public sealed class Rpc_Tests : BaseTestClass
{
+ [TestMethod]
+ public async Task Execute_Success_MQTT_V5_Mixed_Clients()
+ {
+ using (var testEnvironment = CreateTestEnvironment())
+ {
+ await testEnvironment.StartServer();
+ var responseSender = await testEnvironment.ConnectClient();
+ await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
+
+ responseSender.ApplicationMessageReceivedAsync += async e =>
+ {
+ Assert.IsNull(e.ApplicationMessage.ResponseTopic);
+ await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
+ };
+
+ var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
+
+ using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
+ {
+ var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
+
+ Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
+ }
+ }
+ }
+
[TestMethod]
public Task Execute_Success_With_QoS_0()
{
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311);
}
- [TestMethod]
- public Task Execute_Success_With_QoS_1()
- {
- return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
- }
-
- [TestMethod]
- public Task Execute_Success_With_QoS_2()
- {
- return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
- }
-
[TestMethod]
public Task Execute_Success_With_QoS_0_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500);
}
- [TestMethod]
- public Task Execute_Success_With_QoS_1_MQTT_V5()
- {
- return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
- }
-
- [TestMethod]
- public Task Execute_Success_With_QoS_2_MQTT_V5()
- {
- return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
- }
-
[TestMethod]
public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce);
}
+ [TestMethod]
+ public Task Execute_Success_With_QoS_1()
+ {
+ return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
+ }
+
+ [TestMethod]
+ public Task Execute_Success_With_QoS_1_MQTT_V5()
+ {
+ return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
+ }
+
[TestMethod]
public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce);
}
+ [TestMethod]
+ public Task Execute_Success_With_QoS_2()
+ {
+ return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
+ }
+
+ [TestMethod]
+ public Task Execute_Success_With_QoS_2_MQTT_V5()
+ {
+ return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
+ }
+
[TestMethod]
public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic()
{
@@ -90,6 +115,31 @@ namespace MQTTnet.Tests
}
}
+ [TestMethod]
+ [ExpectedException(typeof(MqttCommunicationTimedOutException))]
+ public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
+ {
+ using (var testEnvironment = new TestEnvironment(TestContext))
+ {
+ await testEnvironment.StartServer();
+ var responseSender = await testEnvironment.ConnectClient();
+ await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
+
+ responseSender.ApplicationMessageReceivedAsync += async e =>
+ {
+ Assert.IsNull(e.ApplicationMessage.ResponseTopic);
+ await CompletedTask.Instance;
+ };
+
+ var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
+
+ using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
+ {
+ var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
+ }
+ }
+ }
+
[TestMethod]
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
public async Task Execute_With_Custom_Topic_Names()
@@ -98,14 +148,24 @@ namespace MQTTnet.Tests
{
await testEnvironment.StartServer();
- var requestSender = await testEnvironment.ConnectClient();
-
var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build());
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
}
+ [TestMethod]
+ public void Use_Factory()
+ {
+ var factory = new MqttFactory();
+ using (var client = factory.CreateMqttClient())
+ {
+ var rpcClient = factory.CreateMqttRpcClient(client);
+
+ Assert.IsNotNull(rpcClient);
+ }
+ }
+
async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion)
{
using (var testEnvironment = CreateTestEnvironment())
@@ -151,57 +211,6 @@ namespace MQTTnet.Tests
}
}
- [TestMethod]
- public async Task Execute_Success_MQTT_V5_Mixed_Clients()
- {
- using (var testEnvironment = CreateTestEnvironment())
- {
- await testEnvironment.StartServer();
- var responseSender = await testEnvironment.ConnectClient();
- await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
-
- responseSender.ApplicationMessageReceivedAsync += async e =>
- {
- Assert.IsNull(e.ApplicationMessage.ResponseTopic);
- await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
- };
-
- var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
-
- using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
- {
- var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
-
- Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
- }
- }
- }
-
- [TestMethod]
- [ExpectedException(typeof(MqttCommunicationTimedOutException))]
- public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
- {
- using (var testEnvironment = new TestEnvironment(TestContext))
- {
- await testEnvironment.StartServer();
- var responseSender = await testEnvironment.ConnectClient();
- await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
-
- responseSender.ApplicationMessageReceivedAsync += async e =>
- {
- Assert.IsNull(e.ApplicationMessage.ResponseTopic);
- await CompletedTask.Instance;
- };
-
- var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
-
- using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
- {
- var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
- }
- }
- }
-
class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy
{
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
@@ -214,4 +223,4 @@ namespace MQTTnet.Tests
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/Extensions/WebSocket4Net_Tests.cs b/Source/MQTTnet.Tests/Extensions/WebSocket4Net_Tests.cs
new file mode 100644
index 00000000..8c70fef0
--- /dev/null
+++ b/Source/MQTTnet.Tests/Extensions/WebSocket4Net_Tests.cs
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MQTTnet.Client;
+using MQTTnet.Exceptions;
+using MQTTnet.Extensions.WebSocket4Net;
+using MQTTnet.Tests.Helpers;
+
+namespace MQTTnet.Tests.Extensions
+{
+ [TestClass]
+ public sealed class WebSocket4Net_Tests
+ {
+ [TestMethod]
+ [ExpectedException(typeof(MqttCommunicationException))]
+ public async Task Connect_Failed_With_Invalid_Server()
+ {
+ var factory = new MqttFactory().UseWebSocket4Net();
+
+ using (var client = factory.CreateMqttClient())
+ {
+ var options = new MqttClientOptionsBuilder().WithWebSocketServer("ws://a.b/mqtt").WithTimeout(TimeSpan.FromSeconds(2)).Build();
+ await client.ConnectAsync(options).ConfigureAwait(false);
+ }
+ }
+
+ [TestMethod]
+ public void Use_Correct_Adapter()
+ {
+ // Should not throw exception
+ var factory = new MqttFactory().UseWebSocket4Net();
+ using (var client = factory.CreateMqttClient())
+ {
+ var adapterFactory = client.GetFieldValue("_adapterFactory");
+
+ Assert.IsInstanceOfType(adapterFactory, typeof(WebSocket4NetMqttClientAdapterFactory));
+ }
+ }
+
+ [TestMethod]
+ public void Use_WebSocket4Net()
+ {
+ // Should not throw exception
+ new MqttFactory().UseWebSocket4Net();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs
index 673e46b3..6c32b24a 100644
--- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs
+++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs
@@ -15,7 +15,7 @@ using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
-using MQTTnet.Tests.Extensions;
+using MQTTnet.Tests.Helpers;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Formatter
diff --git a/Source/MQTTnet.Tests/Extensions/MqttPacketWriterExtensions.cs b/Source/MQTTnet.Tests/Helpers/MqttPacketWriterExtensions.cs
similarity index 95%
rename from Source/MQTTnet.Tests/Extensions/MqttPacketWriterExtensions.cs
rename to Source/MQTTnet.Tests/Helpers/MqttPacketWriterExtensions.cs
index e254354f..1c6e1bf1 100644
--- a/Source/MQTTnet.Tests/Extensions/MqttPacketWriterExtensions.cs
+++ b/Source/MQTTnet.Tests/Helpers/MqttPacketWriterExtensions.cs
@@ -5,7 +5,7 @@
using MQTTnet.Formatter;
using MQTTnet.Protocol;
-namespace MQTTnet.Tests.Extensions
+namespace MQTTnet.Tests.Helpers
{
public static class MqttPacketWriterExtensions
{
diff --git a/Source/MQTTnet.Tests/Helpers/ReflectionExtensions.cs b/Source/MQTTnet.Tests/Helpers/ReflectionExtensions.cs
new file mode 100644
index 00000000..2639153f
--- /dev/null
+++ b/Source/MQTTnet.Tests/Helpers/ReflectionExtensions.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Reflection;
+
+namespace MQTTnet.Tests.Helpers
+{
+ public static class ReflectionExtensions
+ {
+ public static object GetFieldValue(this object source, string fieldName)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ var field = source.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
+ if (field == null)
+ {
+ throw new ArgumentException($"Field {fieldName} not found.");
+ }
+
+ return field.GetValue(source);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj
index d9a7ce1e..daf55189 100644
--- a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj
+++ b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj
@@ -22,6 +22,7 @@
+
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/Server/QoS_Tests.cs b/Source/MQTTnet.Tests/Server/QoS_Tests.cs
index d053cac5..2b5e59d2 100644
--- a/Source/MQTTnet.Tests/Server/QoS_Tests.cs
+++ b/Source/MQTTnet.Tests/Server/QoS_Tests.cs
@@ -27,6 +27,7 @@ namespace MQTTnet.Tests.Server
await dummyClient.SubscribeAsync("#", MqttQualityOfServiceLevel.AtLeastOnce);
dummyClient.Dispose();
+ await LongTestDelay();
await LongTestDelay();
// Now inject messages which are appended to the queue of the client.
diff --git a/Source/MQTTnet.Tests/Server/Session_Tests.cs b/Source/MQTTnet.Tests/Server/Session_Tests.cs
index 6b9af994..b8c5527f 100644
--- a/Source/MQTTnet.Tests/Server/Session_Tests.cs
+++ b/Source/MQTTnet.Tests/Server/Session_Tests.cs
@@ -121,10 +121,9 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
}
}
-
- #if !NET452
[TestMethod]
+ [DataRow(MqttProtocolVersion.V310)]
[DataRow(MqttProtocolVersion.V311)]
[DataRow(MqttProtocolVersion.V500)]
public async Task Handle_Parallel_Connection_Attempts(MqttProtocolVersion protocolVersion)
@@ -150,8 +149,6 @@ namespace MQTTnet.Tests.Server
// Try to connect 50 clients at the same time.
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
- await Task.Delay(TimeSpan.FromSeconds(10));
-
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
await LongTestDelay();
@@ -162,13 +159,12 @@ namespace MQTTnet.Tests.Server
var sendClient = await testEnvironment.ConnectClient(option2);
await sendClient.PublishStringAsync("aaa", "1");
- await Task.Delay(TimeSpan.FromSeconds(5));
+ await LongTestDelay();
Assert.AreEqual(true, hasReceive);
}
}
-#endif
-
+
[DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs
index 50e9223a..47ee9b6b 100644
--- a/Source/MQTTnet/Client/MqttClient.cs
+++ b/Source/MQTTnet/Client/MqttClient.cs
@@ -19,7 +19,6 @@ namespace MQTTnet.Client
public sealed class MqttClient : Disposable, IMqttClient
{
readonly IMqttClientAdapterFactory _adapterFactory;
- readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
readonly AsyncEvent _applicationMessageReceivedEvent = new AsyncEvent();
readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory();
@@ -32,7 +31,6 @@ namespace MQTTnet.Client
readonly object _disconnectLock = new object();
readonly AsyncEvent _inspectPacketEvent = new AsyncEvent();
readonly MqttNetSourceLogger _logger;
- readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
readonly IMqttNetLogger _rootLogger;
@@ -207,7 +205,7 @@ namespace MQTTnet.Client
if (clientWasConnected)
{
- var disconnectPacket = _packetFactories.Disconnect.Create(options);
+ var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
}
}
@@ -241,7 +239,7 @@ namespace MQTTnet.Client
ThrowIfDisposed();
ThrowIfNotConnected();
- var publishPacket = _packetFactories.Publish.Create(applicationMessage);
+ var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
switch (applicationMessage.QualityOfServiceLevel)
{
@@ -301,7 +299,7 @@ namespace MQTTnet.Client
ThrowIfDisposed();
ThrowIfNotConnected();
- var subscribePacket = _packetFactories.Subscribe.Create(options);
+ var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var subAckPacket = await SendAndReceiveAsync(subscribePacket, cancellationToken).ConfigureAwait(false);
@@ -319,7 +317,7 @@ namespace MQTTnet.Client
ThrowIfDisposed();
ThrowIfNotConnected();
- var unsubscribePacket = _packetFactories.Unsubscribe.Create(options);
+ var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var unsubAckPacket = await SendAndReceiveAsync(unsubscribePacket, cancellationToken).ConfigureAwait(false);
@@ -347,7 +345,7 @@ namespace MQTTnet.Client
{
if (!eventArgs.ProcessingFailed)
{
- var pubAckPacket = _packetFactories.PubAck.Create(eventArgs);
+ var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs);
return SendAsync(pubAckPacket, cancellationToken);
}
}
@@ -355,7 +353,7 @@ namespace MQTTnet.Client
{
if (!eventArgs.ProcessingFailed)
{
- var pubRecPacket = _packetFactories.PubRec.Create(eventArgs);
+ var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs);
return SendAsync(pubRecPacket, cancellationToken);
}
}
@@ -373,7 +371,7 @@ namespace MQTTnet.Client
try
{
- var connectPacket = _packetFactories.Connect.Create(options);
+ var connectPacket = MqttPacketFactories.Connect.Create(options);
var connAckPacket = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false);
@@ -535,7 +533,7 @@ namespace MQTTnet.Client
async Task HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
{
- var applicationMessage = _applicationMessageFactory.Create(publishPacket);
+ var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
@@ -601,7 +599,7 @@ namespace MQTTnet.Client
{
// The packet is unknown. Probably due to a restart of the client.
// So wen send this to the server to trigger a full resend of the message.
- var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
+ var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
return SendAsync(pubRelPacket, cancellationToken);
}
@@ -610,7 +608,7 @@ namespace MQTTnet.Client
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{
- var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
+ var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendAsync(pubCompPacket, cancellationToken);
}
@@ -637,7 +635,7 @@ namespace MQTTnet.Client
var pubRecPacket = await SendAndReceiveAsync(publishPacket, cancellationToken).ConfigureAwait(false);
- var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
+ var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
var pubCompPacket = await SendAndReceiveAsync(pubRelPacket, cancellationToken).ConfigureAwait(false);
diff --git a/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLoggerExtensions.cs b/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLoggerExtensions.cs
index 18d70a5f..47874aa0 100644
--- a/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLoggerExtensions.cs
+++ b/Source/MQTTnet/Diagnostics/Logger/MqttNetSourceLoggerExtensions.cs
@@ -6,17 +6,100 @@ using System;
namespace MQTTnet.Diagnostics
{
+ /*
+ * The logger uses generic parameters in order to avoid boxing of parameter values like integers etc.
+ */
+
public static class MqttNetSourceLoggerExtensions
{
- /*
- * The logger uses generic parameters in order to avoid boxing of parameter values like integers etc.
- */
-
- public static MqttNetSourceLogger WithSource(this IMqttNetLogger logger, string source)
+ public static void Error(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
{
- if (logger == null) throw new ArgumentNullException(nameof(logger));
+ if (!logger.IsEnabled)
+ {
+ return;
+ }
- return new MqttNetSourceLogger(logger, source);
+ logger.Publish(MqttNetLogLevel.Error, message, new object[] { parameter1 }, exception);
+ }
+
+ public static void Error(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
+ {
+ if (!logger.IsEnabled)
+ {
+ return;
+ }
+
+ logger.Publish(MqttNetLogLevel.Error, message, new object[] { parameter1, parameter2 }, exception);
+ }
+
+ public static void Error(this MqttNetSourceLogger logger, Exception exception, string message)
+ {
+ if (!logger.IsEnabled)
+ {
+ return;
+ }
+
+ logger.Publish(MqttNetLogLevel.Error, message, null, exception);
+ }
+
+ public static void Error(this MqttNetSourceLogger logger, string message)
+ {
+ if (!logger.IsEnabled)
+ {
+ return;
+ }
+
+ logger.Publish(MqttNetLogLevel.Error, message, null, null);
+ }
+
+ public static void Info(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
+ {
+ if (!logger.IsEnabled)
+ {
+ return;
+ }
+
+ logger.Publish(MqttNetLogLevel.Info, message, new object[] { parameter1 }, null);
+ }
+
+ public static void Info(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
+ {
+ if (!logger.IsEnabled)
+ {
+ return;
+ }
+
+ logger.Publish(MqttNetLogLevel.Info, message, new object[] { parameter1, parameter2 }, null);
+ }
+
+ public static void Info(this MqttNetSourceLogger logger, string message)
+ {
+ if (!logger.IsEnabled)
+ {
+ return;
+ }
+
+ logger.Publish(MqttNetLogLevel.Info, message, null, null);
+ }
+
+ public static void Publish(this MqttNetSourceLogger logger, MqttNetLogLevel logLevel, Exception exception, string message, TParameter1 parameter1)
+ {
+ if (!logger.IsEnabled)
+ {
+ return;
+ }
+
+ logger.Publish(logLevel, message, new object[] { parameter1 }, exception);
+ }
+
+ public static void Publish(this MqttNetSourceLogger logger, MqttNetLogLevel logLevel, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
+ {
+ if (!logger.IsEnabled)
+ {
+ return;
+ }
+
+ logger.Publish(logLevel, message, new object[] { parameter1, parameter2 }, exception);
}
public static void Verbose(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
@@ -26,7 +109,7 @@ namespace MQTTnet.Diagnostics
return;
}
- logger.Publish(MqttNetLogLevel.Verbose, message, new object[] {parameter1}, null);
+ logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1 }, null);
}
public static void Verbose(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
@@ -36,10 +119,14 @@ namespace MQTTnet.Diagnostics
return;
}
- logger.Publish(MqttNetLogLevel.Verbose, message, new object[] {parameter1, parameter2}, null);
+ logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1, parameter2 }, null);
}
- public static void Verbose(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2,
+ public static void Verbose(
+ this MqttNetSourceLogger logger,
+ string message,
+ TParameter1 parameter1,
+ TParameter2 parameter2,
TParameter3 parameter3)
{
if (!logger.IsEnabled)
@@ -47,7 +134,7 @@ namespace MQTTnet.Diagnostics
return;
}
- logger.Publish(MqttNetLogLevel.Verbose, message, new object[] {parameter1, parameter2, parameter3}, null);
+ logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1, parameter2, parameter3 }, null);
}
public static void Verbose(this MqttNetSourceLogger logger, string message)
@@ -60,134 +147,74 @@ namespace MQTTnet.Diagnostics
logger.Publish(MqttNetLogLevel.Verbose, message, null, null);
}
- public static void Info(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
- {
- if (!logger.IsEnabled)
- {
- return;
- }
-
- logger.Publish(MqttNetLogLevel.Info, message, new object[] {parameter1}, null);
- }
-
- public static void Info(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
- {
- if (!logger.IsEnabled)
- {
- return;
- }
-
- logger.Publish(MqttNetLogLevel.Info, message, new object[] {parameter1, parameter2}, null);
- }
-
- public static void Info(this MqttNetSourceLogger logger, string message)
- {
- if (!logger.IsEnabled)
- {
- return;
- }
-
- logger.Publish(MqttNetLogLevel.Info, message, null, null);
- }
-
public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
{
if (!logger.IsEnabled)
{
return;
}
-
- logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1}, exception);
+
+ logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1 }, exception);
}
-
+
public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
{
if (!logger.IsEnabled)
{
return;
}
-
- logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1, parameter2}, exception);
+
+ logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1, parameter2 }, exception);
}
-
+
public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message)
{
if (!logger.IsEnabled)
{
return;
}
-
+
logger.Publish(MqttNetLogLevel.Warning, message, null, exception);
}
-
+
public static void Warning(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
{
if (!logger.IsEnabled)
{
return;
}
-
- logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1}, null);
+
+ logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1 }, null);
}
-
+
public static void Warning(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
{
if (!logger.IsEnabled)
{
return;
}
-
- logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1, parameter2}, null);
+
+ logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1, parameter2 }, null);
}
-
+
public static void Warning(this MqttNetSourceLogger logger, string message)
{
if (!logger.IsEnabled)
{
return;
}
-
+
logger.Publish(MqttNetLogLevel.Warning, message, null, null);
}
- public static void Error(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
+ public static MqttNetSourceLogger WithSource(this IMqttNetLogger logger, string source)
{
- if (!logger.IsEnabled)
+ if (logger == null)
{
- return;
+ throw new ArgumentNullException(nameof(logger));
}
-
- logger.Publish(MqttNetLogLevel.Error, message, new object[] {parameter1}, exception);
- }
-
- public static void Error(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
- {
- if (!logger.IsEnabled)
- {
- return;
- }
-
- logger.Publish(MqttNetLogLevel.Error, message, new object[] {parameter1, parameter2}, exception);
- }
- public static void Error(this MqttNetSourceLogger logger, Exception exception, string message)
- {
- if (!logger.IsEnabled)
- {
- return;
- }
-
- logger.Publish(MqttNetLogLevel.Error, message, null, exception);
- }
-
- public static void Error(this MqttNetSourceLogger logger, string message)
- {
- if (!logger.IsEnabled)
- {
- return;
- }
-
- logger.Publish(MqttNetLogLevel.Error, message, null, null);
+ return new MqttNetSourceLogger(logger, source);
}
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs
index 0e80f1df..1fd23bf7 100644
--- a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs
+++ b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs
@@ -7,9 +7,9 @@ using MQTTnet.Packets;
namespace MQTTnet.Formatter
{
- public sealed class MqttApplicationMessageFactory
+ public static class MqttApplicationMessageFactory
{
- public MqttApplicationMessage Create(MqttPublishPacket publishPacket)
+ public static MqttApplicationMessage Create(MqttPublishPacket publishPacket)
{
if (publishPacket == null)
{
diff --git a/Source/MQTTnet/Formatter/MqttPacketFactories.cs b/Source/MQTTnet/Formatter/MqttPacketFactories.cs
index 70de69d2..19264230 100644
--- a/Source/MQTTnet/Formatter/MqttPacketFactories.cs
+++ b/Source/MQTTnet/Formatter/MqttPacketFactories.cs
@@ -4,30 +4,30 @@
namespace MQTTnet.Formatter
{
- public sealed class MqttPacketFactories
+ public static class MqttPacketFactories
{
- public MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
+ public static MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
- public MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
+ public static MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
- public MqttDisconnectPacketFactory Disconnect { get; } = new MqttDisconnectPacketFactory();
+ public static MqttDisconnectPacketFactory Disconnect { get; } = new MqttDisconnectPacketFactory();
- public MqttPubAckPacketFactory PubAck { get; } = new MqttPubAckPacketFactory();
+ public static MqttPubAckPacketFactory PubAck { get; } = new MqttPubAckPacketFactory();
- public MqttPubCompPacketFactory PubComp { get; } = new MqttPubCompPacketFactory();
+ public static MqttPubCompPacketFactory PubComp { get; } = new MqttPubCompPacketFactory();
- public MqttPublishPacketFactory Publish { get; } = new MqttPublishPacketFactory();
+ public static MqttPublishPacketFactory Publish { get; } = new MqttPublishPacketFactory();
- public MqttPubRecPacketFactory PubRec { get; } = new MqttPubRecPacketFactory();
+ public static MqttPubRecPacketFactory PubRec { get; } = new MqttPubRecPacketFactory();
- public MqttPubRelPacketFactory PubRel { get; } = new MqttPubRelPacketFactory();
+ public static MqttPubRelPacketFactory PubRel { get; } = new MqttPubRelPacketFactory();
- public MqttSubAckPacketFactory SubAck { get; } = new MqttSubAckPacketFactory();
+ public static MqttSubAckPacketFactory SubAck { get; } = new MqttSubAckPacketFactory();
- public MqttSubscribePacketFactory Subscribe { get; } = new MqttSubscribePacketFactory();
+ public static MqttSubscribePacketFactory Subscribe { get; } = new MqttSubscribePacketFactory();
- public MqttUnsubAckPacketFactory UnsubAck { get; } = new MqttUnsubAckPacketFactory();
+ public static MqttUnsubAckPacketFactory UnsubAck { get; } = new MqttUnsubAckPacketFactory();
- public MqttUnsubscribePacketFactory Unsubscribe { get; } = new MqttUnsubscribePacketFactory();
+ public static MqttUnsubscribePacketFactory Unsubscribe { get; } = new MqttUnsubscribePacketFactory();
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs
index f9651d81..2924d86e 100644
--- a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs
+++ b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs
@@ -78,7 +78,7 @@ namespace MQTTnet.Formatter.V3
return DecodeSubscribePacket(receivedMqttPacket.Body);
case MqttControlPacketType.SubAck:
return DecodeSubAckPacket(receivedMqttPacket.Body);
- case MqttControlPacketType.Unsubscibe:
+ case MqttControlPacketType.Unsubscribe:
return DecodeUnsubscribePacket(receivedMqttPacket.Body);
case MqttControlPacketType.UnsubAck:
return DecodeUnsubAckPacket(receivedMqttPacket.Body);
@@ -783,7 +783,7 @@ namespace MQTTnet.Formatter.V3
}
}
- return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
+ return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscribe, 0x02);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs
index 5f875851..9d460411 100644
--- a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs
+++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs
@@ -54,7 +54,7 @@ namespace MQTTnet.Formatter.V5
return DecodeSubscribePacket(receivedMqttPacket.Body);
case MqttControlPacketType.SubAck:
return DecodeSubAckPacket(receivedMqttPacket.Body);
- case MqttControlPacketType.Unsubscibe:
+ case MqttControlPacketType.Unsubscribe:
return DecodeUnsubscribePacket(receivedMqttPacket.Body);
case MqttControlPacketType.UnsubAck:
return DecodeUnsubAckPacket(receivedMqttPacket.Body);
diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs
index 31092fa1..ed94c0e4 100644
--- a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs
+++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs
@@ -524,7 +524,7 @@ namespace MQTTnet.Formatter.V5
_bufferWriter.WriteString(topicFilter);
}
- return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
+ return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscribe, 0x02);
}
static void ThrowIfPacketIdentifierIsInvalid(ushort packetIdentifier, MqttPacket packet)
diff --git a/Source/MQTTnet/Internal/CancellationTokenSourceExtensions.cs b/Source/MQTTnet/Internal/CancellationTokenSourceExtensions.cs
new file mode 100644
index 00000000..94c3febc
--- /dev/null
+++ b/Source/MQTTnet/Internal/CancellationTokenSourceExtensions.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+
+namespace MQTTnet.Internal
+{
+ public static class CancellationTokenSourceExtensions
+ {
+ public static bool TryCancel(this CancellationTokenSource cancellationTokenSource, bool throwOnFirstException = false)
+ {
+ if (cancellationTokenSource == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ // Checking the _IsCancellationRequested_ here will not throw an
+ // "ObjectDisposedException" as the getter of the property "Token" will do!
+ if (cancellationTokenSource.IsCancellationRequested)
+ {
+ return false;
+ }
+
+ cancellationTokenSource.Cancel(throwOnFirstException);
+ return true;
+ }
+ catch (ObjectDisposedException)
+ {
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Packets/MqttPacketExtensions.cs b/Source/MQTTnet/Packets/MqttPacketExtensions.cs
new file mode 100644
index 00000000..42d2288d
--- /dev/null
+++ b/Source/MQTTnet/Packets/MqttPacketExtensions.cs
@@ -0,0 +1,102 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+namespace MQTTnet.Packets
+{
+ public static class MqttPacketExtensions
+ {
+ public static string GetRfcName(this MqttPacket packet)
+ {
+ if (packet == null)
+ {
+ throw new ArgumentNullException(nameof(packet));
+ }
+
+ switch (packet)
+ {
+ case MqttConnectPacket _:
+ {
+ return "CONNECT";
+ }
+
+ case MqttConnAckPacket _:
+ {
+ return "CONNACK";
+ }
+
+ case MqttAuthPacket _:
+ {
+ return "AUTH";
+ }
+
+ case MqttDisconnectPacket _:
+ {
+ return "DISCONNECT";
+ }
+
+ case MqttPingReqPacket _:
+ {
+ return "PINGREQ";
+ }
+
+ case MqttPingRespPacket _:
+ {
+ return "PINGRESP";
+ }
+
+ case MqttSubscribePacket _:
+ {
+ return "SUBSCRIBE";
+ }
+
+ case MqttSubAckPacket _:
+ {
+ return "SUBACK";
+ }
+
+ case MqttUnsubscribePacket _:
+ {
+ return "UNSUBSCRIBE";
+ }
+
+ case MqttUnsubAckPacket _:
+ {
+ return "UNSUBACK";
+ }
+
+ case MqttPublishPacket _:
+ {
+ return "PUBLISH";
+ }
+
+ case MqttPubAckPacket _:
+ {
+ return "PUBACK";
+ }
+
+ case MqttPubRelPacket _:
+ {
+ return "PUBREL";
+ }
+
+ case MqttPubRecPacket _:
+ {
+ return "PUBREC";
+ }
+
+ case MqttPubCompPacket _:
+ {
+ return "PUBCOMP";
+ }
+
+ default:
+ {
+ return packet.GetType().Name;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Protocol/MqttControlPacketType.cs b/Source/MQTTnet/Protocol/MqttControlPacketType.cs
index 22b48e03..37a3fc5d 100644
--- a/Source/MQTTnet/Protocol/MqttControlPacketType.cs
+++ b/Source/MQTTnet/Protocol/MqttControlPacketType.cs
@@ -15,7 +15,7 @@ namespace MQTTnet.Protocol
PubComp = 7,
Subscribe = 8,
SubAck = 9,
- Unsubscibe = 10,
+ Unsubscribe = 10,
UnsubAck = 11,
PingReq = 12,
PingResp = 13,
diff --git a/Source/MQTTnet/Server/Internal/MqttClient.cs b/Source/MQTTnet/Server/Internal/MqttClient.cs
index 463ca2ec..6c4b9bc8 100644
--- a/Source/MQTTnet/Server/Internal/MqttClient.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClient.cs
@@ -12,27 +12,21 @@ using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
-using MQTTnet.PacketDispatcher;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
- public sealed class MqttClient
+ public sealed class MqttClient : IDisposable
{
- readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
readonly MqttConnectPacket _connectPacket;
readonly MqttServerEventContainer _eventContainer;
readonly MqttNetSourceLogger _logger;
- readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
- readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
-
readonly MqttServerOptions _serverOptions;
-
readonly MqttClientSessionsManager _sessionsManager;
readonly Dictionary _topicAlias = new Dictionary();
- CancellationTokenSource _cancellationToken;
+ CancellationTokenSource _cancellationToken = new CancellationTokenSource();
bool _disconnectPacketSent;
public MqttClient(
@@ -45,14 +39,13 @@ namespace MQTTnet.Server
IMqttNetLogger logger)
{
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
- _eventContainer = eventContainer;
+ _eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
+ _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
ChannelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
Endpoint = channelAdapter.Endpoint;
-
Session = session ?? throw new ArgumentNullException(nameof(session));
- _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
if (logger == null)
{
@@ -80,6 +73,11 @@ namespace MQTTnet.Server
public MqttClientStatistics Statistics { get; } = new MqttClientStatistics();
+ public void Dispose()
+ {
+ _cancellationToken?.Dispose();
+ }
+
public void ResetStatistics()
{
ChannelAdapter.ResetStatistics();
@@ -88,46 +86,43 @@ namespace MQTTnet.Server
public async Task RunAsync()
{
- _logger.Info("Client '{0}': Session started.", Id);
+ _logger.Info("Client '{0}': Session started", Id);
Session.LatestConnectPacket = _connectPacket;
Session.WillMessageSent = false;
- using (_cancellationToken = new CancellationTokenSource())
+ try
{
var cancellationToken = _cancellationToken.Token;
- try
- {
- _ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false);
+ _ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default)
+ .ConfigureAwait(false);
- IsRunning = true;
+ IsRunning = true;
- await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
- }
- finally
- {
- IsRunning = false;
-
- _cancellationToken?.Cancel();
- _cancellationToken = null;
- }
+ await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
}
+ finally
+ {
+ IsRunning = false;
- _packetDispatcher.CancelAll();
+ _cancellationToken?.TryCancel();
+ _cancellationToken?.Dispose();
+ _cancellationToken = null;
+ }
if (!IsTakenOver && !IsCleanDisconnect && Session.LatestConnectPacket.WillFlag && !Session.WillMessageSent)
{
- var willPublishPacket = _packetFactories.Publish.Create(Session.LatestConnectPacket);
- var willApplicationMessage = _applicationMessageFactory.Create(willPublishPacket);
+ var willPublishPacket = MqttPacketFactories.Publish.Create(Session.LatestConnectPacket);
+ var willApplicationMessage = MqttApplicationMessageFactory.Create(willPublishPacket);
_ = _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
Session.WillMessageSent = true;
- _logger.Info("Client '{0}': Published will message.", Id);
+ _logger.Info("Client '{0}': Published will message", Id);
}
- _logger.Info("Client '{0}': Connection stopped.", Id);
+ _logger.Info("Client '{0}': Connection stopped", Id);
}
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
@@ -181,6 +176,12 @@ namespace MQTTnet.Server
return CompletedTask.Instance;
}
+ void HandleIncomingPingReqPacket()
+ {
+ // See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
+ Session.EnqueueHealthPacket(new MqttPacketBusItem(MqttPingRespPacket.Instance));
+ }
+
Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket)
{
var acknowledgedPublishPacket = Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier);
@@ -210,7 +211,7 @@ namespace MQTTnet.Server
HandleTopicAlias(publishPacket);
InterceptingPublishEventArgs interceptingPublishEventArgs = null;
- var applicationMessage = _applicationMessageFactory.Create(publishPacket);
+ var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
var closeConnection = false;
var processPublish = true;
@@ -253,19 +254,19 @@ namespace MQTTnet.Server
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
- var pubAckPacket = _packetFactories.PubAck.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
+ var pubAckPacket = MqttPacketFactories.PubAck.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket));
break;
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
- var pubRecPacket = _packetFactories.PubRec.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
+ var pubRecPacket = MqttPacketFactories.PubRec.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRecPacket));
break;
}
default:
{
- throw new MqttCommunicationException("Received a not supported QoS level.");
+ throw new MqttCommunicationException("Received a not supported QoS level");
}
}
}
@@ -274,7 +275,7 @@ namespace MQTTnet.Server
{
// Do not fire the event _ClientAcknowledgedPublishPacket_ here because the QoS 2 process is only finished
// properly when the client has sent the PUBCOMP packet.
- var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
+ var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket));
return CompletedTask.Instance;
@@ -282,7 +283,7 @@ namespace MQTTnet.Server
void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket)
{
- var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
+ var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
Session.EnqueueControlPacket(new MqttPacketBusItem(pubCompPacket));
}
@@ -290,7 +291,7 @@ namespace MQTTnet.Server
{
var subscribeResult = await Session.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false);
- var subAckPacket = _packetFactories.SubAck.Create(subscribePacket, subscribeResult);
+ var subAckPacket = MqttPacketFactories.SubAck.Create(subscribePacket, subscribeResult);
Session.EnqueueControlPacket(new MqttPacketBusItem(subAckPacket));
@@ -304,7 +305,7 @@ namespace MQTTnet.Server
{
foreach (var retainedMessageMatch in subscribeResult.RetainedMessages)
{
- var publishPacket = _packetFactories.Publish.Create(retainedMessageMatch);
+ var publishPacket = MqttPacketFactories.Publish.Create(retainedMessageMatch);
Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
}
}
@@ -314,7 +315,7 @@ namespace MQTTnet.Server
{
var unsubscribeResult = await Session.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false);
- var unsubAckPacket = _packetFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult);
+ var unsubAckPacket = MqttPacketFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult);
Session.EnqueueControlPacket(new MqttPacketBusItem(unsubAckPacket));
@@ -345,7 +346,7 @@ namespace MQTTnet.Server
}
else
{
- _logger.Warning("Client '{0}': Received invalid topic alias ({1}).", Id, publishPacket.TopicAlias);
+ _logger.Warning("Client '{0}': Received invalid topic alias ({1})", Id, publishPacket.TopicAlias);
}
}
}
@@ -371,6 +372,7 @@ namespace MQTTnet.Server
async Task ReceivePackagesLoop(CancellationToken cancellationToken)
{
+ MqttPacket currentPacket = null;
try
{
// We do not listen for the cancellation token here because the internal buffer might still
@@ -380,12 +382,12 @@ namespace MQTTnet.Server
{
await Task.Yield();
- var packet = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
- if (packet == null)
+ currentPacket = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
+ if (currentPacket == null)
{
return;
}
-
+
// Check for cancellation again because receive packet might block some time.
if (cancellationToken.IsCancellationRequested)
{
@@ -398,73 +400,69 @@ namespace MQTTnet.Server
{
return;
}
-
+
var processPacket = true;
if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
{
- var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items);
+ var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, currentPacket, Session.Items);
await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
- packet = interceptingPacketEventArgs.Packet;
+ currentPacket = interceptingPacketEventArgs.Packet;
processPacket = interceptingPacketEventArgs.ProcessPacket;
}
- if (!processPacket || packet == null)
+ if (!processPacket || currentPacket == null)
{
// Restart the receiving process to get the next packet ignoring the current one..
continue;
}
- Statistics.HandleReceivedPacket(packet);
+ Statistics.HandleReceivedPacket(currentPacket);
- if (packet is MqttPublishPacket publishPacket)
+ if (currentPacket is MqttPublishPacket publishPacket)
{
await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false);
}
- else if (packet is MqttPubAckPacket pubAckPacket)
+ else if (currentPacket is MqttPubAckPacket pubAckPacket)
{
await HandleIncomingPubAckPacket(pubAckPacket).ConfigureAwait(false);
}
- else if (packet is MqttPubCompPacket pubCompPacket)
+ else if (currentPacket is MqttPubCompPacket pubCompPacket)
{
await HandleIncomingPubCompPacket(pubCompPacket).ConfigureAwait(false);
}
- else if (packet is MqttPubRecPacket pubRecPacket)
+ else if (currentPacket is MqttPubRecPacket pubRecPacket)
{
await HandleIncomingPubRecPacket(pubRecPacket).ConfigureAwait(false);
}
- else if (packet is MqttPubRelPacket pubRelPacket)
+ else if (currentPacket is MqttPubRelPacket pubRelPacket)
{
HandleIncomingPubRelPacket(pubRelPacket);
}
- else if (packet is MqttSubscribePacket subscribePacket)
+ else if (currentPacket is MqttSubscribePacket subscribePacket)
{
await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false);
}
- else if (packet is MqttUnsubscribePacket unsubscribePacket)
+ else if (currentPacket is MqttUnsubscribePacket unsubscribePacket)
{
await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false);
}
- else if (packet is MqttPingReqPacket)
+ else if (currentPacket is MqttPingReqPacket)
{
- // See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
- Session.EnqueueHealthPacket(new MqttPacketBusItem(MqttPingRespPacket.Instance));
+ HandleIncomingPingReqPacket();
}
- else if (packet is MqttPingRespPacket)
+ else if (currentPacket is MqttPingRespPacket)
{
throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only.");
}
- else if (packet is MqttDisconnectPacket)
+ else if (currentPacket is MqttDisconnectPacket)
{
IsCleanDisconnect = true;
return;
}
else
{
- if (!_packetDispatcher.TryDispatch(packet))
- {
- throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
- }
+ throw new MqttProtocolViolationException("Packet not allowed");
}
}
}
@@ -475,11 +473,25 @@ namespace MQTTnet.Server
{
if (exception is MqttCommunicationException)
{
- _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", Id);
+ _logger.Warning(exception, "Client '{0}': Communication exception while receiving packets", Id);
+ return;
+ }
+
+ var logLevel = MqttNetLogLevel.Error;
+
+ if (!IsRunning)
+ {
+ // There was an exception but the connection is already closed. So there is no chance to send a response to the client etc.
+ logLevel = MqttNetLogLevel.Warning;
+ }
+
+ if (currentPacket == null)
+ {
+ _logger.Publish(logLevel, exception, "Client '{0}': Error while receiving packets", Id);
}
else
{
- _logger.Error(exception, "Client '{0}': Error while receiving client packets.", Id);
+ _logger.Publish(logLevel, exception, "Client '{0}': Error while processing {1} packet", Id, currentPacket.GetRfcName());
}
}
}
@@ -531,15 +543,15 @@ namespace MQTTnet.Server
{
if (exception is MqttCommunicationTimedOutException)
{
- _logger.Warning(exception, "Client '{0}': Sending publish packet failed: Timeout.", Id);
+ _logger.Warning(exception, "Client '{0}': Sending PUBLISH packet failed due to timeout", Id);
}
else if (exception is MqttCommunicationException)
{
- _logger.Warning(exception, "Client '{0}': Sending publish packet failed: Communication exception.", Id);
+ _logger.Warning(exception, "Client '{0}': Sending PUBLISH packet failed due to communication exception", Id);
}
else
{
- _logger.Error(exception, "Client '{0}': Sending publish packet failed.", Id);
+ _logger.Error(exception, "Client '{0}': Sending PUBLISH packet failed", Id);
}
if (packetBusItem?.Packet is MqttPublishPacket publishPacket)
@@ -557,15 +569,7 @@ namespace MQTTnet.Server
void StopInternal()
{
- try
- {
- _cancellationToken?.Cancel();
- }
- catch (ObjectDisposedException)
- {
- // This can happen when connections are created and dropped very quickly.
- // It is not an issue if the cancellation token cannot be canceled multiple times.
- }
+ _cancellationToken?.TryCancel();
}
async Task TrySendDisconnectPacket(MqttDisconnectReasonCode reasonCode)
@@ -574,8 +578,8 @@ namespace MQTTnet.Server
{
// This also indicates that it was tried at least!
_disconnectPacketSent = true;
-
- var disconnectPacket = _packetFactories.Disconnect.Create(reasonCode);
+
+ var disconnectPacket = MqttPacketFactories.Disconnect.Create(reasonCode);
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
{
@@ -584,7 +588,7 @@ namespace MQTTnet.Server
}
catch (Exception exception)
{
- _logger.Warning(exception, "Client '{0}': Error while sending DISCONNECT packet (ReasonCode = {1}).", Id, reasonCode);
+ _logger.Warning(exception, "Client '{0}': Error while sending DISCONNECT packet (ReasonCode = {1})", Id, reasonCode);
}
}
}
diff --git a/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
index 9fb9ca18..75dc3f9f 100644
--- a/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
@@ -28,7 +28,6 @@ namespace MQTTnet.Server
readonly MqttServerEventContainer _eventContainer;
readonly MqttNetSourceLogger _logger;
readonly MqttServerOptions _options;
- readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
readonly MqttRetainedMessagesManager _retainedMessagesManager;
readonly IMqttNetLogger _rootLogger;
@@ -166,7 +165,7 @@ namespace MQTTnet.Server
continue;
}
- var newPublishPacket = _packetFactories.Publish.Create(applicationMessage);
+ var newPublishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
newPublishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel;
newPublishPacket.SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers;
@@ -288,7 +287,7 @@ namespace MQTTnet.Server
}
var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
- var connAckPacket = _packetFactories.ConnAck.Create(validatingConnectionEventArgs);
+ var connAckPacket = MqttPacketFactories.ConnAck.Create(validatingConnectionEventArgs);
if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success)
{
@@ -430,7 +429,7 @@ namespace MQTTnet.Server
{
foreach (var retainedMessageMatch in subscribeResult.RetainedMessages)
{
- var publishPacket = _packetFactories.Publish.Create(retainedMessageMatch);
+ var publishPacket = MqttPacketFactories.Publish.Create(retainedMessageMatch);
clientSession.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
}
}
From d7eb4bbe6e30e1fb5cdacf8aa1b47f0c5868a415 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Tue, 6 Dec 2022 20:28:01 +0100
Subject: [PATCH 26/48] Allow interception of will messages (#1613)
---
.github/workflows/ReleaseNotes.md | 3 +-
.../MQTTnet.Tests/Server/Injection_Tests.cs | 24 +++
Source/MQTTnet.Tests/Server/Will_Tests.cs | 74 ++++---
.../Formatter/MqttPubAckPacketFactory.cs | 30 +--
.../Formatter/MqttPubRecPacketFactory.cs | 21 +-
.../DispatchApplicationMessageResult.cs | 18 +-
Source/MQTTnet/Server/Internal/MqttClient.cs | 42 +---
.../Internal/MqttClientSessionsManager.cs | 191 ++++++++++--------
Source/MQTTnet/Server/MqttServer.cs | 40 +---
Source/MQTTnet/Server/PublishResponse.cs | 2 +-
10 files changed, 230 insertions(+), 215 deletions(-)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index 0b38d44b..8c4732c3 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -4,4 +4,5 @@
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
-* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang).
\ No newline at end of file
+* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang).
+* [Server] Add support for interception of will messages (#1613).
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/Server/Injection_Tests.cs b/Source/MQTTnet.Tests/Server/Injection_Tests.cs
index 7ce3687d..14d30026 100644
--- a/Source/MQTTnet.Tests/Server/Injection_Tests.cs
+++ b/Source/MQTTnet.Tests/Server/Injection_Tests.cs
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
+using MQTTnet.Internal;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server
@@ -60,5 +61,28 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic);
}
}
+
+ [TestMethod]
+ public async Task Intercept_Injected_Application_Message()
+ {
+ using (var testEnvironment = CreateTestEnvironment())
+ {
+ var server = await testEnvironment.StartServer();
+
+ MqttApplicationMessage interceptedMessage = null;
+ server.InterceptingPublishAsync += eventArgs =>
+ {
+ interceptedMessage = eventArgs.ApplicationMessage;
+ return CompletedTask.Instance;
+ };
+
+ var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build();
+ await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage));
+
+ await LongTestDelay();
+
+ Assert.IsNotNull(interceptedMessage);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/Server/Will_Tests.cs b/Source/MQTTnet.Tests/Server/Will_Tests.cs
index 12b24f63..e9c364ca 100644
--- a/Source/MQTTnet.Tests/Server/Will_Tests.cs
+++ b/Source/MQTTnet.Tests/Server/Will_Tests.cs
@@ -1,4 +1,3 @@
-using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
@@ -10,33 +9,51 @@ namespace MQTTnet.Tests.Server
[TestClass]
public sealed class Will_Tests : BaseTestClass
{
+ [TestMethod]
+ public async Task Intercept_Will_Message()
+ {
+ using (var testEnvironment = CreateTestEnvironment())
+ {
+ var server = await testEnvironment.StartServer().ConfigureAwait(false);
+
+ MqttApplicationMessage willMessage = null;
+ server.InterceptingPublishAsync += eventArgs =>
+ {
+ willMessage = eventArgs.ApplicationMessage;
+ return CompletedTask.Instance;
+ };
+
+ await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false);
+ var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
+ var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
+ takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
+
+ await LongTestDelay().ConfigureAwait(false);
+
+ Assert.IsNotNull(willMessage);
+ }
+ }
+
[TestMethod]
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
{
using (var testEnvironment = CreateTestEnvironment())
{
- var receivedMessagesCount = 0;
-
await testEnvironment.StartServer();
+ var receiver = await testEnvironment.ConnectClient().ConfigureAwait(false);
+
+ var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
+
+ await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
+
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
+ var sender = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
+ await sender.DisconnectAsync().ConfigureAwait(false);
- var c1 = await testEnvironment.ConnectClient();
+ await LongTestDelay().ConfigureAwait(false);
- c1.ApplicationMessageReceivedAsync += e =>
- {
- Interlocked.Increment(ref receivedMessagesCount);
- return CompletedTask.Instance;
- };
-
- await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
-
- var c2 = await testEnvironment.ConnectClient(clientOptions);
- await c2.DisconnectAsync().ConfigureAwait(false);
-
- await Task.Delay(1000);
-
- Assert.AreEqual(0, receivedMessagesCount);
+ Assert.AreEqual(0, receivedMessages.ReceivedEventArgs.Count);
}
}
@@ -47,24 +64,17 @@ namespace MQTTnet.Tests.Server
{
await testEnvironment.StartServer();
- var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
-
- var receivedMessagesCount = 0;
- c1.ApplicationMessageReceivedAsync += e =>
- {
- Interlocked.Increment(ref receivedMessagesCount);
- return CompletedTask.Instance;
- };
-
- await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
+ var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false);
+ var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
+ await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
- var c2 = await testEnvironment.ConnectClient(clientOptions);
- c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
+ var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
+ takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
- await Task.Delay(1000);
+ await LongTestDelay().ConfigureAwait(false);
- Assert.AreEqual(1, receivedMessagesCount);
+ Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count);
}
}
}
diff --git a/Source/MQTTnet/Formatter/MqttPubAckPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPubAckPacketFactory.cs
index 536ec011..c79bed16 100644
--- a/Source/MQTTnet/Formatter/MqttPubAckPacketFactory.cs
+++ b/Source/MQTTnet/Formatter/MqttPubAckPacketFactory.cs
@@ -14,7 +14,6 @@ namespace MQTTnet.Formatter
{
public MqttPubAckPacket Create(
MqttPublishPacket publishPacket,
- InterceptingPublishEventArgs interceptingPublishEventArgs,
DispatchApplicationMessageResult dispatchApplicationMessageResult)
{
if (publishPacket == null)
@@ -22,28 +21,19 @@ namespace MQTTnet.Formatter
throw new ArgumentNullException(nameof(publishPacket));
}
+ if (dispatchApplicationMessageResult == null)
+ {
+ throw new ArgumentNullException(nameof(dispatchApplicationMessageResult));
+ }
+
var pubAckPacket = new MqttPubAckPacket
{
- PacketIdentifier = publishPacket.PacketIdentifier
+ PacketIdentifier = publishPacket.PacketIdentifier,
+ ReasonCode = (MqttPubAckReasonCode)dispatchApplicationMessageResult.ReasonCode,
+ ReasonString = dispatchApplicationMessageResult.ReasonString,
+ UserProperties = dispatchApplicationMessageResult.UserProperties
};
-
- if (dispatchApplicationMessageResult.MatchingSubscribersCount == 0)
- {
- // NoMatchingSubscribers is ONLY sent by the server!
- pubAckPacket.ReasonCode = MqttPubAckReasonCode.NoMatchingSubscribers;
- }
- else
- {
- pubAckPacket.ReasonCode = MqttPubAckReasonCode.Success;
- }
-
- if (interceptingPublishEventArgs != null)
- {
- pubAckPacket.ReasonCode = (MqttPubAckReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
- pubAckPacket.ReasonString = interceptingPublishEventArgs.Response.ReasonString;
- pubAckPacket.UserProperties = interceptingPublishEventArgs.Response.UserProperties;
- }
-
+
return pubAckPacket;
}
diff --git a/Source/MQTTnet/Formatter/MqttPubRecPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPubRecPacketFactory.cs
index 0a6bba24..9e825ab9 100644
--- a/Source/MQTTnet/Formatter/MqttPubRecPacketFactory.cs
+++ b/Source/MQTTnet/Formatter/MqttPubRecPacketFactory.cs
@@ -25,10 +25,7 @@ namespace MQTTnet.Formatter
return pubRecPacket;
}
- public MqttPacket Create(
- MqttPublishPacket publishPacket,
- InterceptingPublishEventArgs interceptingPublishEventArgs,
- DispatchApplicationMessageResult dispatchApplicationMessageResult)
+ public MqttPacket Create(MqttPublishPacket publishPacket, DispatchApplicationMessageResult dispatchApplicationMessageResult)
{
if (publishPacket == null)
{
@@ -38,21 +35,11 @@ namespace MQTTnet.Formatter
var pubRecPacket = new MqttPubRecPacket
{
PacketIdentifier = publishPacket.PacketIdentifier,
- ReasonCode = MqttPubRecReasonCode.Success
+ ReasonCode = (MqttPubRecReasonCode)dispatchApplicationMessageResult.ReasonCode,
+ ReasonString = dispatchApplicationMessageResult.ReasonString,
+ UserProperties = dispatchApplicationMessageResult.UserProperties
};
- if (dispatchApplicationMessageResult.MatchingSubscribersCount == 0)
- {
- pubRecPacket.ReasonCode = MqttPubRecReasonCode.NoMatchingSubscribers;
- }
-
- if (interceptingPublishEventArgs != null)
- {
- pubRecPacket.ReasonCode = (MqttPubRecReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
- pubRecPacket.ReasonString = interceptingPublishEventArgs.Response.ReasonString;
- pubRecPacket.UserProperties = interceptingPublishEventArgs.Response.UserProperties;
- }
-
return pubRecPacket;
}
diff --git a/Source/MQTTnet/Server/Internal/DispatchApplicationMessageResult.cs b/Source/MQTTnet/Server/Internal/DispatchApplicationMessageResult.cs
index bc4637a1..12527f4d 100644
--- a/Source/MQTTnet/Server/Internal/DispatchApplicationMessageResult.cs
+++ b/Source/MQTTnet/Server/Internal/DispatchApplicationMessageResult.cs
@@ -2,15 +2,27 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
+using MQTTnet.Packets;
+
namespace MQTTnet.Server
{
public sealed class DispatchApplicationMessageResult
{
- public DispatchApplicationMessageResult(int matchingSubscribersCount)
+ public DispatchApplicationMessageResult(int reasonCode, bool closeConnection, string reasonString, List userProperties)
{
- MatchingSubscribersCount = matchingSubscribersCount;
+ ReasonCode = reasonCode;
+ CloseConnection = closeConnection;
+ ReasonString = reasonString;
+ UserProperties = userProperties;
}
- public int MatchingSubscribersCount { get; }
+ public bool CloseConnection { get; }
+
+ public int ReasonCode { get; }
+
+ public string ReasonString { get; }
+
+ public List UserProperties { get; }
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Server/Internal/MqttClient.cs b/Source/MQTTnet/Server/Internal/MqttClient.cs
index 6c4b9bc8..4a09cd90 100644
--- a/Source/MQTTnet/Server/Internal/MqttClient.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClient.cs
@@ -116,7 +116,7 @@ namespace MQTTnet.Server
var willPublishPacket = MqttPacketFactories.Publish.Create(Session.LatestConnectPacket);
var willApplicationMessage = MqttApplicationMessageFactory.Create(willPublishPacket);
- _ = _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
+ _ = _sessionsManager.DispatchApplicationMessage(Id, Session.Items, willApplicationMessage, CancellationToken.None);
Session.WillMessageSent = true;
_logger.Info("Client '{0}': Published will message", Id);
@@ -210,41 +210,17 @@ namespace MQTTnet.Server
{
HandleTopicAlias(publishPacket);
- InterceptingPublishEventArgs interceptingPublishEventArgs = null;
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
- var closeConnection = false;
- var processPublish = true;
- if (_eventContainer.InterceptingPublishEvent.HasHandlers)
- {
- interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, Id, Session.Items);
- if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
- {
- // This can happen if a topic alias us used but the topic is
- // unknown to the server.
- interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
- interceptingPublishEventArgs.ProcessPublish = false;
- }
+ var dispatchApplicationMessageResult =
+ await _sessionsManager.DispatchApplicationMessage(Id, Session.Items, applicationMessage, cancellationToken).ConfigureAwait(false);
- await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
-
- applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
- closeConnection = interceptingPublishEventArgs.CloseConnection;
- processPublish = interceptingPublishEventArgs.ProcessPublish;
- }
-
- if (closeConnection)
+ if (dispatchApplicationMessageResult.CloseConnection)
{
await StopAsync(MqttDisconnectReasonCode.UnspecifiedError);
return;
}
- DispatchApplicationMessageResult dispatchResult = null;
- if (processPublish && applicationMessage != null)
- {
- dispatchResult = await _sessionsManager.DispatchApplicationMessage(Id, applicationMessage).ConfigureAwait(false);
- }
-
switch (publishPacket.QualityOfServiceLevel)
{
case MqttQualityOfServiceLevel.AtMostOnce:
@@ -254,13 +230,13 @@ namespace MQTTnet.Server
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
- var pubAckPacket = MqttPacketFactories.PubAck.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
+ var pubAckPacket = MqttPacketFactories.PubAck.Create(publishPacket, dispatchApplicationMessageResult);
Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket));
break;
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
- var pubRecPacket = MqttPacketFactories.PubRec.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
+ var pubRecPacket = MqttPacketFactories.PubRec.Create(publishPacket, dispatchApplicationMessageResult);
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRecPacket));
break;
}
@@ -478,16 +454,16 @@ namespace MQTTnet.Server
}
var logLevel = MqttNetLogLevel.Error;
-
+
if (!IsRunning)
{
// There was an exception but the connection is already closed. So there is no chance to send a response to the client etc.
logLevel = MqttNetLogLevel.Warning;
}
-
+
if (currentPacket == null)
{
- _logger.Publish(logLevel, exception, "Client '{0}': Error while receiving packets", Id);
+ _logger.Publish(logLevel, exception, "Client '{0}': Error while receiving packets", Id);
}
else
{
diff --git a/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
index 75dc3f9f..a21b6926 100644
--- a/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
@@ -126,81 +126,118 @@ namespace MQTTnet.Server
_logger.Verbose("Session for client '{0}' deleted.", clientId);
}
-
- public async Task DispatchApplicationMessage(string senderId, MqttApplicationMessage applicationMessage)
+
+ public async Task DispatchApplicationMessage(
+ string senderId,
+ IDictionary senderSessionItems,
+ MqttApplicationMessage applicationMessage,
+ CancellationToken cancellationToken)
{
- var matchingSubscribersCount = 0;
- try
+ var processPublish = true;
+ var closeConnection = false;
+ string reasonString = null;
+ List userProperties = null;
+ var reasonCode = 0; // The reason code is later converted into several different but compatible enums!
+
+ // Allow the user to intercept application message...
+ if (_eventContainer.InterceptingPublishEvent.HasHandlers)
{
- if (applicationMessage.Retain)
+ var interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, senderId, senderSessionItems);
+ if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
{
- await _retainedMessagesManager.UpdateMessage(senderId, applicationMessage).ConfigureAwait(false);
- }
-
- List subscriberSessions;
- lock (_sessionsManagementLock)
- {
- subscriberSessions = _subscriberSessions.ToList();
+ // This can happen if a topic alias us used but the topic is
+ // unknown to the server.
+ interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
+ interceptingPublishEventArgs.ProcessPublish = false;
}
- // Calculate application message topic hash once for subscription checks
- MqttSubscription.CalculateTopicHash(applicationMessage.Topic, out var topicHash, out _, out _);
+ await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
- foreach (var session in subscriberSessions)
- {
- if(!session.TryCheckSubscriptions(
- applicationMessage.Topic,
- topicHash,
- applicationMessage.QualityOfServiceLevel,
- senderId,
- out var checkSubscriptionsResult))
- {
- // Checking the subscriptions has failed for the session. The session
- // will be ignored.
- continue;
- }
-
- if (!checkSubscriptionsResult.IsSubscribed)
- {
- continue;
- }
-
- var newPublishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
- newPublishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel;
- newPublishPacket.SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers;
-
- if (newPublishPacket.QualityOfServiceLevel > 0)
- {
- newPublishPacket.PacketIdentifier = session.PacketIdentifierProvider.GetNextPacketIdentifier();
- }
-
- if (checkSubscriptionsResult.RetainAsPublished)
- {
- // Transfer the original retain state from the publisher. This is a MQTTv5 feature.
- newPublishPacket.Retain = applicationMessage.Retain;
- }
- else
- {
- newPublishPacket.Retain = false;
- }
-
- session.EnqueueDataPacket(new MqttPacketBusItem(newPublishPacket));
- matchingSubscribersCount++;
-
- _logger.Verbose("Client '{0}': Queued PUBLISH packet with topic '{1}'.", session.Id, applicationMessage.Topic);
- }
-
- if (matchingSubscribersCount == 0)
- {
- await FireApplicationMessageNotConsumedEvent(applicationMessage, senderId).ConfigureAwait(false);
- }
- }
- catch (Exception exception)
- {
- _logger.Error(exception, "Unhandled exception while processing next queued application message.");
+ applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
+ closeConnection = interceptingPublishEventArgs.CloseConnection;
+ processPublish = interceptingPublishEventArgs.ProcessPublish;
+ reasonString = interceptingPublishEventArgs.Response.ReasonString;
+ userProperties = interceptingPublishEventArgs.Response.UserProperties;
+ reasonCode = (int)interceptingPublishEventArgs.Response.ReasonCode;
}
- return new DispatchApplicationMessageResult(matchingSubscribersCount);
+ // Process the application message...
+ if (processPublish && applicationMessage != null)
+ {
+ var matchingSubscribersCount = 0;
+ try
+ {
+ if (applicationMessage.Retain)
+ {
+ await _retainedMessagesManager.UpdateMessage(senderId, applicationMessage).ConfigureAwait(false);
+ }
+
+ List subscriberSessions;
+ lock (_sessionsManagementLock)
+ {
+ subscriberSessions = _subscriberSessions.ToList();
+ }
+
+ // Calculate application message topic hash once for subscription checks
+ MqttSubscription.CalculateTopicHash(applicationMessage.Topic, out var topicHash, out _, out _);
+
+ foreach (var session in subscriberSessions)
+ {
+ if (!session.TryCheckSubscriptions(
+ applicationMessage.Topic,
+ topicHash,
+ applicationMessage.QualityOfServiceLevel,
+ senderId,
+ out var checkSubscriptionsResult))
+ {
+ // Checking the subscriptions has failed for the session. The session
+ // will be ignored.
+ continue;
+ }
+
+ if (!checkSubscriptionsResult.IsSubscribed)
+ {
+ continue;
+ }
+
+ var publishPacketCopy = MqttPacketFactories.Publish.Create(applicationMessage);
+ publishPacketCopy.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel;
+ publishPacketCopy.SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers;
+
+ if (publishPacketCopy.QualityOfServiceLevel > 0)
+ {
+ publishPacketCopy.PacketIdentifier = session.PacketIdentifierProvider.GetNextPacketIdentifier();
+ }
+
+ if (checkSubscriptionsResult.RetainAsPublished)
+ {
+ // Transfer the original retain state from the publisher. This is a MQTTv5 feature.
+ publishPacketCopy.Retain = applicationMessage.Retain;
+ }
+ else
+ {
+ publishPacketCopy.Retain = false;
+ }
+
+ session.EnqueueDataPacket(new MqttPacketBusItem(publishPacketCopy));
+ matchingSubscribersCount++;
+
+ _logger.Verbose("Client '{0}': Queued PUBLISH packet with topic '{1}'.", session.Id, applicationMessage.Topic);
+ }
+
+ if (matchingSubscribersCount == 0)
+ {
+ reasonCode = (int)MqttPubAckReasonCode.NoMatchingSubscribers;
+ await FireApplicationMessageNotConsumedEvent(applicationMessage, senderId).ConfigureAwait(false);
+ }
+ }
+ catch (Exception exception)
+ {
+ _logger.Error(exception, "Unhandled exception while processing next queued application message.");
+ }
+ }
+
+ return new DispatchApplicationMessageResult(reasonCode, closeConnection, reasonString, userProperties);
}
public void Dispose()
@@ -495,11 +532,11 @@ namespace MQTTnet.Server
{
MqttSession oldSession;
MqttClient oldClient;
-
+
lock (_sessionsManagementLock)
{
MqttSession session;
-
+
// Create a new session (if required).
if (!_sessions.TryGetValue(connectPacket.ClientId, out oldSession))
{
@@ -518,7 +555,7 @@ namespace MQTTnet.Server
_logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
session = oldSession;
oldSession = null;
-
+
// Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval
session.IsPersistent = sessionShouldPersist;
connAckPacket.IsSessionPresent = true;
@@ -527,7 +564,7 @@ namespace MQTTnet.Server
}
_sessions[connectPacket.ClientId] = session;
-
+
// Create a new client (always required).
_clients.TryGetValue(connectPacket.ClientId, out oldClient);
if (oldClient != null)
@@ -536,7 +573,7 @@ namespace MQTTnet.Server
// for a later DISCONNECT packet.
oldClient.IsTakenOver = true;
}
-
+
client = CreateClient(connectPacket, channelAdapter, session);
_clients[connectPacket.ClientId] = client;
@@ -548,19 +585,15 @@ namespace MQTTnet.Server
var preparingSessionEventArgs = new PreparingSessionEventArgs();
await _eventContainer.PreparingSessionEvent.TryInvokeAsync(preparingSessionEventArgs, _logger).ConfigureAwait(false);
}
-
+
if (oldClient != null)
{
await oldClient.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false);
if (_eventContainer.ClientDisconnectedEvent.HasHandlers)
{
- var eventArgs = new ClientDisconnectedEventArgs(
- oldClient.Id,
- MqttClientDisconnectType.Takeover,
- oldClient.Endpoint,
- oldClient.Session.Items);
-
+ var eventArgs = new ClientDisconnectedEventArgs(oldClient.Id, MqttClientDisconnectType.Takeover, oldClient.Endpoint, oldClient.Session.Items);
+
await _eventContainer.ClientDisconnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
}
}
diff --git a/Source/MQTTnet/Server/MqttServer.cs b/Source/MQTTnet/Server/MqttServer.cs
index 2c5969e7..6568f0b5 100644
--- a/Source/MQTTnet/Server/MqttServer.cs
+++ b/Source/MQTTnet/Server/MqttServer.cs
@@ -19,17 +19,17 @@ namespace MQTTnet.Server
{
public class MqttServer : Disposable
{
- readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer();
-
- readonly IDictionary _sessionItems = new ConcurrentDictionary
-
-
diff --git a/Source/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj b/Source/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj
index dfcc0c72..63f972c2 100644
--- a/Source/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj
+++ b/Source/MQTTnet.AspNetCore.Tests/MQTTnet.AspNetCore.Tests.csproj
@@ -12,8 +12,6 @@
-
-
diff --git a/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj b/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj
index 973f5d7d..e7fb5432 100644
--- a/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj
+++ b/Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj
@@ -8,17 +8,15 @@
true
false
false
+ 1591;NETSDK1138
-
-
-
-
+
-
+
diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
index 7fff4ab4..1745b38d 100644
--- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
+++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
@@ -32,7 +32,7 @@
true
true
true
- 1591
+ 1591;NETSDK1138
7.3
diff --git a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
index 899b655f..ad9a1f03 100644
--- a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
+++ b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
@@ -10,6 +10,7 @@
false
false
true
+ 1591;NETSDK1138
@@ -27,9 +28,6 @@
-
-
-
diff --git a/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj b/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj
index 2ef5c783..45f5464f 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj
+++ b/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj
@@ -33,7 +33,7 @@
For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).
true
true
- 1591
+ 1591;NETSDK1138
diff --git a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
index 8100c0e3..d2098e10 100644
--- a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
+++ b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
@@ -33,7 +33,7 @@
For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).
true
true
- 1591
+ 1591;NETSDK1138
7.3
diff --git a/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj b/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj
index ba3433ee..d78358aa 100644
--- a/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj
+++ b/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj
@@ -33,7 +33,7 @@
For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).
true
true
- 1591
+ 1591;NETSDK1138
7.3
diff --git a/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj b/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj
index de649457..66fd1002 100644
--- a/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj
+++ b/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj
@@ -9,12 +9,11 @@
false
false
true
+ 1591;NETSDK1138
-
-
diff --git a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj
index daf55189..8b56b6cb 100644
--- a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj
+++ b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj
@@ -7,15 +7,13 @@
false
false
true
+ 1591;NETSDK1138
-
-
-
diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj
index 6153cce0..945a8693 100644
--- a/Source/MQTTnet/MQTTnet.csproj
+++ b/Source/MQTTnet/MQTTnet.csproj
@@ -41,7 +41,7 @@
true
MIT
true
- 1591
+ 1591;NETSDK1138
true
From 5649328bc52e2ef5cace53f6f32cb8d80f03f20a Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Wed, 7 Dec 2022 20:44:30 +0100
Subject: [PATCH 31/48] Update release notes
---
.github/workflows/ReleaseNotes.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index 8c4732c3..84b8e1b5 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -5,4 +5,5 @@
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang).
-* [Server] Add support for interception of will messages (#1613).
\ No newline at end of file
+* [Server] Add support for interception of will messages (#1613).
+* [Server] Fixed wrong topic filter detections (#1615, thanks to @logicaloud).
\ No newline at end of file
From 0bc47cf853a2ad78994e8ef0ada9d0072792551b Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Wed, 7 Dec 2022 20:50:34 +0100
Subject: [PATCH 32/48] Improve unit tests
---
.../Helpers/MqttClientExtensions.cs | 23 +++++++++++++++++++
.../TestApplicationMessageReceivedHandler.cs | 11 +++++++++
2 files changed, 34 insertions(+)
create mode 100644 Source/MQTTnet.Tests/Helpers/MqttClientExtensions.cs
diff --git a/Source/MQTTnet.Tests/Helpers/MqttClientExtensions.cs b/Source/MQTTnet.Tests/Helpers/MqttClientExtensions.cs
new file mode 100644
index 00000000..314244c4
--- /dev/null
+++ b/Source/MQTTnet.Tests/Helpers/MqttClientExtensions.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using MQTTnet.Client;
+using MQTTnet.Tests.Mockups;
+
+namespace MQTTnet.Tests.Helpers
+{
+ public static class MqttClientExtensions
+ {
+ public static TestApplicationMessageReceivedHandler TrackReceivedMessages(this IMqttClient client)
+ {
+ if (client == null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+
+ return new TestApplicationMessageReceivedHandler(client);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs b/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs
index 7556709a..4d5b7761 100644
--- a/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs
+++ b/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs
@@ -27,6 +27,17 @@ namespace MQTTnet.Tests.Mockups
mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync;
}
+ public int Count
+ {
+ get
+ {
+ lock (_receivedEventArgs)
+ {
+ return _receivedEventArgs.Count;
+ }
+ }
+ }
+
public List ReceivedEventArgs
{
get
From c9c27b9f01313ee5d537578cbc39e3fea77c63b7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 7 Dec 2022 20:57:29 +0100
Subject: [PATCH 33/48] Bump Newtonsoft.Json from 13.0.1 to 13.0.2 in
/Source/MQTTnet.TestApp (#1621)
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 13.0.1 to 13.0.2.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/13.0.1...13.0.2)
---
updated-dependencies:
- dependency-name: Newtonsoft.Json
dependency-type: direct:production
...
Signed-off-by: dependabot[bot]
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj b/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj
index 66fd1002..6272084b 100644
--- a/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj
+++ b/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj
@@ -13,7 +13,7 @@
-
+
From f79649d71b6544952986bac7f645482617a613ba Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Wed, 7 Dec 2022 20:59:52 +0100
Subject: [PATCH 34/48] 1612 mqttserver subscription to similar base and
subtopic with wildcards fails (#1622)
---
.../Server/MqttSubscriptionsManager_Tests.cs | 98 +++++++++++++++++++
.../MQTTnet.Tests/Server/Subscribe_Tests.cs | 44 +++++++++
.../MqttClientSubscriptionsManager.cs | 27 ++---
.../Internal/TopicHashMaskSubscriptions.cs | 27 +++--
4 files changed, 178 insertions(+), 18 deletions(-)
diff --git a/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs b/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs
index 19a4f427..ff81c1c6 100644
--- a/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs
+++ b/Source/MQTTnet.Tests/Server/MqttSubscriptionsManager_Tests.cs
@@ -114,6 +114,104 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce);
}
+ [TestMethod]
+ public async Task MqttSubscriptionsManager_SubscribeWildcard1()
+ {
+ await SubscribeToTopic("house/+/room");
+ await SubscribeToTopic("house/+/room/+");
+
+ CheckIsSubscribed("house/1/room");
+ CheckIsSubscribed("house/1/room/bed");
+ CheckIsSubscribed("house/1/room/chair");
+ CheckIsSubscribed("house/2/room/bed");
+ CheckIsSubscribed("house/2/room/chair");
+
+ CheckIsNotSubscribed("house/1/room/bed/cover");
+ CheckIsNotSubscribed("house/1/study/bed");
+ }
+
+ [TestMethod]
+ public async Task MqttSubscriptionsManager_SubscribeWildcard2()
+ {
+ await SubscribeToTopic("house/+/room");
+ await SubscribeToTopic("house/+/room/#");
+
+ CheckIsSubscribed("house/1/room");
+ CheckIsSubscribed("house/1/room/bed");
+ CheckIsSubscribed("house/2/room");
+ CheckIsSubscribed("house/2/room/bed");
+ CheckIsSubscribed("house/2/room/bed/cover");
+
+ CheckIsNotSubscribed("house/1/level");
+ CheckIsNotSubscribed("house/1/level/door");
+ }
+
+ [TestMethod]
+ public async Task MqttSubscriptionsManager_SubscribeWildcard3()
+ {
+ await SubscribeToTopic("house/1/room");
+ await SubscribeToTopic("house/1/room/+");
+
+ CheckIsSubscribed("house/1/room");
+ CheckIsSubscribed("house/1/room/bed");
+ CheckIsSubscribed("house/1/room/chair");
+
+ CheckIsNotSubscribed("house/2/room");
+ CheckIsNotSubscribed("house/2/room/bed/cover");
+ }
+
+ [TestMethod]
+ public async Task MqttSubscriptionsManager_SubscribeWildcard4()
+ {
+ await SubscribeToTopic("house/1/+/+");
+
+ CheckIsSubscribed("house/1/room/bed");
+ CheckIsSubscribed("house/1/room/chair");
+
+ CheckIsNotSubscribed("house/1/room");
+ CheckIsNotSubscribed("house/1/room/bed/cover");
+ }
+
+ [TestMethod]
+ public async Task MqttSubscriptionsManager_SubscribeWildcard5()
+ {
+ await SubscribeToTopic("house/1/+/#");
+
+ CheckIsSubscribed("house/1/room/bed");
+ CheckIsSubscribed("house/1/room/chair");
+ CheckIsSubscribed("house/1/room/chair/leg");
+ CheckIsSubscribed("house/1/level/window");
+ CheckIsSubscribed("house/1/level/door");
+
+ CheckIsNotSubscribed("house/2/room/bed");
+ }
+
+ async Task SubscribeToTopic(string topic)
+ {
+ var sp = new MqttSubscribePacket
+ {
+ TopicFilters = new List
+ {
+ new MqttTopicFilter { Topic = topic, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce },
+ }
+ };
+
+ await _subscriptionsManager.Subscribe(sp, CancellationToken.None);
+ }
+
+ void CheckIsSubscribed(string topic)
+ {
+ var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, "");
+ Assert.IsTrue(result.IsSubscribed);
+ Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
+ }
+
+ void CheckIsNotSubscribed(string topic)
+ {
+ var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, "");
+ Assert.IsFalse(result.IsSubscribed);
+ }
+
[TestInitialize]
public void TestInitialize()
{
diff --git a/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs b/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs
index d4631bc2..a4ce93b7 100644
--- a/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs
+++ b/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs
@@ -13,12 +13,56 @@ using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
+using MQTTnet.Tests.Helpers;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Subscribe_Tests : BaseTestClass
{
+ [DataTestMethod]
+ [DataRow("A", "A", true)]
+ [DataRow("A", "B", false)]
+ [DataRow("A", "#", true)]
+ [DataRow("A", "+", true)]
+ [DataRow("A/B", "A/B", true)]
+ [DataRow("A/B", "A/+", true)]
+ [DataRow("A/B", "A/#", true)]
+ [DataRow("A/B/C", "A/B/C", true)]
+ [DataRow("A/B/C", "A/+/C", true)]
+ [DataRow("A/B/C", "A/+/+", true)]
+ [DataRow("A/B/C", "A/+/#", true)]
+ [DataRow("A/B/C/D", "A/B/C/D", true)]
+ [DataRow("A/B/C/D", "A/+/C/+", true)]
+ [DataRow("A/B/C/D", "A/+/C/#", true)]
+ [DataRow("A/B/C", "A/B/+", true)]
+ [DataRow("A/B1/B2/C", "A/+/C", false)]
+ public async Task Subscription_Roundtrip(string topic, string filter, bool shouldWork)
+ {
+ using (var testEnvironment = CreateTestEnvironment())
+ {
+ await testEnvironment.StartServer().ConfigureAwait(false);
+
+ var receiver = await testEnvironment.ConnectClient();
+ await receiver.SubscribeAsync(filter).ConfigureAwait(false);
+ var receivedMessages = receiver.TrackReceivedMessages();
+
+ var sender = await testEnvironment.ConnectClient();
+ await sender.PublishStringAsync(topic, "PAYLOAD").ConfigureAwait(false);
+
+ await LongTestDelay().ConfigureAwait(false);
+
+ if (shouldWork)
+ {
+ Assert.AreEqual(1, receivedMessages.Count, message: "The filter should work!");
+ }
+ else
+ {
+ Assert.AreEqual(0, receivedMessages.Count, message: "The filter should not work!");
+ }
+ }
+ }
+
[TestMethod]
public async Task Deny_Invalid_Topic()
{
diff --git a/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs
index 6fb45371..4155331d 100644
--- a/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs
@@ -55,18 +55,21 @@ namespace MQTTnet.Server
{
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions))
{
- possibleSubscriptions.AddRange(noWildcardSubscriptions.ToList());
+ possibleSubscriptions.AddRange(noWildcardSubscriptions);
}
foreach (var wcs in _wildcardSubscriptionsByTopicHash)
{
- var wildcardSubscriptions = wcs.Value;
var subscriptionHash = wcs.Key;
- var subscriptionHashMask = wildcardSubscriptions.HashMask;
-
- if ((topicHash & subscriptionHashMask) == subscriptionHash)
+ var subscriptionsByHashMask = wcs.Value.SubscriptionsByHashMask;
+ foreach (var shm in subscriptionsByHashMask)
{
- possibleSubscriptions.AddRange(wildcardSubscriptions.Subscriptions.ToList());
+ var subscriptionHashMask = shm.Key;
+ if ((topicHash & subscriptionHashMask) == subscriptionHash)
+ {
+ var subscriptions = shm.Value;
+ possibleSubscriptions.AddRange(subscriptions);
+ }
}
}
}
@@ -265,8 +268,8 @@ namespace MQTTnet.Server
{
if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
{
- subscriptions.Subscriptions.Remove(existingSubscription);
- if (subscriptions.Subscriptions.Count == 0)
+ subscriptions.RemoveSubscription(existingSubscription);
+ if (subscriptions.SubscriptionsByHashMask.Count == 0)
{
_wildcardSubscriptionsByTopicHash.Remove(topicHash);
}
@@ -346,9 +349,9 @@ namespace MQTTnet.Server
// must remove object from topic hash dictionary first
if (hasWildcard)
{
- if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subs))
+ if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
{
- subs.Subscriptions.Remove(existingSubscription);
+ subscriptions.RemoveSubscription(existingSubscription);
// no need to remove empty entry because we'll be adding subscription again below
}
}
@@ -370,11 +373,11 @@ namespace MQTTnet.Server
{
if (!_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
{
- subscriptions = new TopicHashMaskSubscriptions(topicHashMask);
+ subscriptions = new TopicHashMaskSubscriptions();
_wildcardSubscriptionsByTopicHash.Add(topicHash, subscriptions);
}
- subscriptions.Subscriptions.Add(subscription);
+ subscriptions.AddSubscription(subscription);
}
else
{
diff --git a/Source/MQTTnet/Server/Internal/TopicHashMaskSubscriptions.cs b/Source/MQTTnet/Server/Internal/TopicHashMaskSubscriptions.cs
index e9a35e83..fa074995 100644
--- a/Source/MQTTnet/Server/Internal/TopicHashMaskSubscriptions.cs
+++ b/Source/MQTTnet/Server/Internal/TopicHashMaskSubscriptions.cs
@@ -3,17 +3,32 @@ using System.Collections.Generic;
namespace MQTTnet.Server
{
///
- /// Helper class that stores the topic hash mask common to all contained Subscriptions for direct access.
+ /// Helper class that stores subscriptions by their topic hash mask.
///
public sealed class TopicHashMaskSubscriptions
{
- public TopicHashMaskSubscriptions(ulong hashMask)
+ public Dictionary> SubscriptionsByHashMask { get; } = new Dictionary>();
+
+ public void AddSubscription(MqttSubscription subscription)
{
- HashMask = hashMask;
+ if (!SubscriptionsByHashMask.TryGetValue(subscription.TopicHashMask, out var subscriptions))
+ {
+ subscriptions = new HashSet();
+ SubscriptionsByHashMask.Add(subscription.TopicHashMask, subscriptions);
+ }
+ subscriptions.Add(subscription);
}
- public ulong HashMask { get; }
-
- public HashSet Subscriptions { get; } = new HashSet();
+ public void RemoveSubscription(MqttSubscription subscription)
+ {
+ if (SubscriptionsByHashMask.TryGetValue(subscription.TopicHashMask, out var subscriptions))
+ {
+ subscriptions.Remove(subscription);
+ if (subscriptions.Count == 0)
+ {
+ SubscriptionsByHashMask.Remove(subscription.TopicHashMask);
+ }
+ }
+ }
}
}
\ No newline at end of file
From 555c3eac7abf61d9b58bb191b9c875ac7b9e3031 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Tue, 13 Dec 2022 17:15:43 +0100
Subject: [PATCH 35/48] Reset release notes
---
.github/workflows/ReleaseNotes.md | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index 84b8e1b5..f59ec20a 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -1,9 +1 @@
-* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
-* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593).
-* [Core] Performance improvements and reduced memory usage due to improved buffer copies (#1599, thanks to @xljiulang).
-* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
-* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
-* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
-* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang).
-* [Server] Add support for interception of will messages (#1613).
-* [Server] Fixed wrong topic filter detections (#1615, thanks to @logicaloud).
\ No newline at end of file
+*
\ No newline at end of file
From 7d5e49b44c912048b319d2dba4a636ec22d33324 Mon Sep 17 00:00:00 2001
From: soyoo
Date: Sat, 17 Dec 2022 00:12:46 +0800
Subject: [PATCH 36/48] A typo in comment. (#1628)
---
Samples/Client/Client_Connection_Samples.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Samples/Client/Client_Connection_Samples.cs b/Samples/Client/Client_Connection_Samples.cs
index 13f5c0f4..f42667b7 100644
--- a/Samples/Client/Client_Connection_Samples.cs
+++ b/Samples/Client/Client_Connection_Samples.cs
@@ -297,7 +297,7 @@ public static class Client_Connection_Samples
/*
* This sample sends a PINGREQ packet to the server and waits for a reply.
*
- * This is only supported in METTv5.0.0+.
+ * This is only supported in MQTTv5.0.0+.
*/
var mqttFactory = new MqttFactory();
From 8a926a3db38bb9c888d0371df0ad8786e376864d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=80=81=E4=B9=9D?= <366193849@qq.com>
Date: Sat, 17 Dec 2022 00:13:11 +0800
Subject: [PATCH 37/48] Performance optimization of SendPacketAsync (#1624)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* performance optimization of SendPacketAsync
* Update Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
Co-authored-by: Günther Foidl
Co-authored-by: Günther Foidl
---
.../MqttConnectionContext.cs | 13 +++-
.../SendPacketAsyncBenchmark.cs | 68 +++++++++++++++++++
2 files changed, 79 insertions(+), 2 deletions(-)
create mode 100644 Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs
diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
index a809107f..5804d856 100644
--- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
+++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs
@@ -211,8 +211,17 @@ namespace MQTTnet.AspNetCore
{
var buffer = PacketFormatterAdapter.Encode(packet);
- WritePacketBuffer(_output, buffer);
- await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
+ if (buffer.Payload.Count == 0)
+ {
+ // zero copy
+ // https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
+ await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ WritePacketBuffer(_output, buffer);
+ await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
BytesSent += buffer.Length;
}
diff --git a/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs
new file mode 100644
index 00000000..aa5cd038
--- /dev/null
+++ b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs
@@ -0,0 +1,68 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Jobs;
+using MQTTnet.Formatter;
+using System;
+using System.IO;
+using System.IO.Pipelines;
+using System.Threading.Tasks;
+
+namespace MQTTnet.Benchmarks
+{
+ [SimpleJob(RuntimeMoniker.Net60)]
+ [RPlotExporter, RankColumn]
+ [MemoryDiagnoser]
+ public class SendPacketAsyncBenchmark : BaseBenchmark
+ {
+ MemoryStream stream;
+ MqttPacketBuffer buffer;
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ stream = new MemoryStream(1024);
+ var packet = new ArraySegment(new byte[10]);
+ buffer = new MqttPacketBuffer(packet);
+ }
+
+ [Benchmark(Baseline = true)]
+ public async ValueTask Before()
+ {
+ stream.Position = 0;
+ var output = PipeWriter.Create(stream);
+
+ WritePacketBuffer(output, buffer);
+ await output.FlushAsync();
+ }
+
+ [Benchmark]
+ public async ValueTask After()
+ {
+ stream.Position = 0;
+ var output = PipeWriter.Create(stream);
+
+ if (buffer.Payload.Count == 0)
+ {
+ await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
+ }
+ else
+ {
+ WritePacketBuffer(output, buffer);
+ await output.FlushAsync().ConfigureAwait(false);
+ }
+ }
+
+
+ static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
+ {
+ // copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
+ // MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
+
+ var span = output.GetSpan(buffer.Length);
+
+ buffer.Packet.AsSpan().CopyTo(span);
+ buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
+
+ output.Advance(buffer.Length);
+ }
+ }
+}
\ No newline at end of file
From c7390ef0d7cb8cd9cd64eef4c791831539d6eb7b Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Fri, 23 Dec 2022 16:51:13 +0100
Subject: [PATCH 38/48] Update verison
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4e2d0287..7d45e9ea 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request]
env:
- VERSION: "4.1.4.${{github.run_number}}"
+ VERSION: "4.2.0.${{github.run_number}}"
jobs:
build:
From 795139d78f6e647baf62006ea45b443717a85cb2 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Fri, 23 Dec 2022 16:56:11 +0100
Subject: [PATCH 39/48] Add feature validations (#1527)
* Add feature validation.
* Update ReleaseNotes.md
* Add validators or subscribe and unsubscribe.
* Add feature validation for disconnect.
* Fix build errors.
* Fix build errors
Co-authored-by: HansM
---
.github/workflows/ReleaseNotes.md | 2 +-
.../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 18 +--
.../MqttApplicationMessageValidator_Tests.cs | 44 +++++++
.../MqttClientOptionsValidator_Tests.cs | 54 +++++++++
.../MQTTnet.Tests/MqttTopicValidator_Tests.cs | 24 ++--
.../MqttClientDisconnectOptions.cs | 10 +-
.../MqttClientDisconnectOptionsValidator.cs | 42 +++++++
Source/MQTTnet/Client/MqttClient.cs | 61 +++++++---
.../Client/Options/MqttClientOptions.cs | 41 +++++--
.../Options/MqttClientOptionsBuilder.cs | 2 +
.../MqttClientOptionsBuilderTlsParameters.cs | 9 +-
.../Options/MqttClientOptionsValidator.cs | 114 ++++++++++++++++++
.../Subscribing/MqttClientSubscribeOptions.cs | 38 +++---
.../MqttClientSubscribeOptionsValidator.cs | 58 +++++++++
.../MqttClientUnsubscribeOptions.cs | 20 +--
.../MqttClientUnsubscribeOptionsValidator.cs | 37 ++++++
.../MqttApplicationMessageValidator.cs | 68 +++++++++++
Source/MQTTnet/Packets/MqttTopicFilter.cs | 8 +-
Source/MQTTnet/Protocol/MqttTopicValidator.cs | 9 +-
19 files changed, 574 insertions(+), 85 deletions(-)
create mode 100644 Source/MQTTnet.Tests/MqttApplicationMessageValidator_Tests.cs
create mode 100644 Source/MQTTnet.Tests/MqttClientOptionsValidator_Tests.cs
create mode 100644 Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs
create mode 100644 Source/MQTTnet/Client/Options/MqttClientOptionsValidator.cs
create mode 100644 Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsValidator.cs
create mode 100644 Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs
create mode 100644 Source/MQTTnet/MqttApplicationMessageValidator.cs
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index f59ec20a..abfab925 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -1 +1 @@
-*
\ No newline at end of file
+* [Client] MQTTv5 features are now checked and an exception is thrown if they are used when using protocol version 3.1.1 and lower. These checks can be disabled in client options. (BREAKING CHANGE!).
diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
index 5784b814..864db187 100644
--- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
+++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs
@@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client;
using MQTTnet.Exceptions;
+using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
@@ -17,9 +18,9 @@ namespace MQTTnet.Extensions.Rpc
{
readonly IMqttClient _mqttClient;
readonly MqttRpcClientOptions _options;
-
+
readonly ConcurrentDictionary> _waitingCalls = new ConcurrentDictionary>();
-
+
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
@@ -83,11 +84,14 @@ namespace MQTTnet.Extensions.Rpc
throw new MqttProtocolViolationException("RPC response topic is empty.");
}
- var requestMessage = new MqttApplicationMessageBuilder().WithTopic(requestTopic)
- .WithPayload(payload)
- .WithQualityOfServiceLevel(qualityOfServiceLevel)
- .WithResponseTopic(responseTopic)
- .Build();
+ var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
+
+ if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
+ {
+ requestMessageBuilder.WithResponseTopic(responseTopic);
+ }
+
+ var requestMessage = requestMessageBuilder.Build();
try
{
diff --git a/Source/MQTTnet.Tests/MqttApplicationMessageValidator_Tests.cs b/Source/MQTTnet.Tests/MqttApplicationMessageValidator_Tests.cs
new file mode 100644
index 00000000..cb47d499
--- /dev/null
+++ b/Source/MQTTnet.Tests/MqttApplicationMessageValidator_Tests.cs
@@ -0,0 +1,44 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MQTTnet.Formatter;
+
+namespace MQTTnet.Tests
+{
+ [TestClass]
+ public sealed class MqttApplicationMessageValidator_Tests
+ {
+ [TestMethod]
+ [ExpectedException(typeof(NotSupportedException))]
+ public void Succeed_When_Using_TopicAlias_And_MQTT_311()
+ {
+ MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V311);
+ }
+
+ [TestMethod]
+ public void Succeed_When_Using_TopicAlias_And_MQTT_500()
+ {
+ MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V500);
+ }
+
+ [TestMethod]
+ public void Succeed_When_Using_UserProperties_And_MQTT_500()
+ {
+ MqttApplicationMessageValidator.ThrowIfNotSupported(
+ new MqttApplicationMessageBuilder().WithTopic("A").WithUserProperty("User", "Property").Build(),
+ MqttProtocolVersion.V500);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(NotSupportedException))]
+ public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
+ {
+ MqttApplicationMessageValidator.ThrowIfNotSupported(
+ new MqttApplicationMessageBuilder().WithTopic("B").WithUserProperty("User", "Property").Build(),
+ MqttProtocolVersion.V311);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/MqttClientOptionsValidator_Tests.cs b/Source/MQTTnet.Tests/MqttClientOptionsValidator_Tests.cs
new file mode 100644
index 00000000..cc264725
--- /dev/null
+++ b/Source/MQTTnet.Tests/MqttClientOptionsValidator_Tests.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MQTTnet.Client;
+using MQTTnet.Formatter;
+
+namespace MQTTnet.Tests
+{
+ [TestClass]
+ public sealed class MqttClientOptionsValidator_Tests
+ {
+ [TestMethod]
+ public void Succeed_When_Using_UserProperties_And_MQTT_500()
+ {
+ new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(NotSupportedException))]
+ public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
+ {
+ new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
+ }
+
+ [TestMethod]
+ public void Succeed_When_Using_WillUserProperties_And_MQTT_500()
+ {
+ new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(NotSupportedException))]
+ public void Throw_When_Using_UserProperties_And_MQTT_311()
+ {
+ new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(NotSupportedException))]
+ public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_311()
+ {
+ new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
+ }
+
+ [TestMethod]
+ public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_500()
+ {
+ new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/MqttTopicValidator_Tests.cs b/Source/MQTTnet.Tests/MqttTopicValidator_Tests.cs
index 10f60522..bd22742d 100644
--- a/Source/MQTTnet.Tests/MqttTopicValidator_Tests.cs
+++ b/Source/MQTTnet.Tests/MqttTopicValidator_Tests.cs
@@ -9,19 +9,13 @@ using MQTTnet.Protocol;
namespace MQTTnet.Tests
{
[TestClass]
- public class MqttTopicValidator_Tests
+ public sealed class MqttTopicValidator_Tests
{
- [TestMethod]
- public void Valid_Topic()
- {
- MqttTopicValidator.ThrowIfInvalid("/a/b/c");
- }
-
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))]
- public void Invalid_Topic_Plus()
+ public void Invalid_Topic_Empty()
{
- MqttTopicValidator.ThrowIfInvalid("/a/+/c");
+ MqttTopicValidator.ThrowIfInvalid(string.Empty);
}
[TestMethod]
@@ -33,9 +27,15 @@ namespace MQTTnet.Tests
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))]
- public void Invalid_Topic_Empty()
+ public void Invalid_Topic_Plus()
{
- MqttTopicValidator.ThrowIfInvalid(string.Empty);
+ MqttTopicValidator.ThrowIfInvalid("/a/+/c");
+ }
+
+ [TestMethod]
+ public void Valid_Topic()
+ {
+ MqttTopicValidator.ThrowIfInvalid("/a/b/c");
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs
index 3f4e88aa..964e30a7 100644
--- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs
+++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs
@@ -7,15 +7,15 @@ namespace MQTTnet.Client
public sealed class MqttClientDisconnectOptions
{
///
- /// Gets or sets the reason code.
- /// Hint: MQTT 5 feature only.
+ /// Gets or sets the reason code.
+ /// MQTT 5.0.0+ feature.
///
public MqttClientDisconnectReason Reason { get; set; } = MqttClientDisconnectReason.NormalDisconnection;
///
- /// Gets or sets the reason string.
- /// Hint: MQTT 5 feature only.
+ /// Gets or sets the reason string.
+ /// MQTT 5.0.0+ feature.
///
public string ReasonString { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs
new file mode 100644
index 00000000..b5fc4cc8
--- /dev/null
+++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Linq;
+using MQTTnet.Formatter;
+
+namespace MQTTnet.Client
+{
+ public static class MqttClientDisconnectOptionsValidator
+ {
+ public static void ThrowIfNotSupported(MqttClientDisconnectOptions options, MqttProtocolVersion protocolVersion)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ if (protocolVersion == MqttProtocolVersion.V500)
+ {
+ // Everything is supported.
+ return;
+ }
+
+ if (options.ReasonString?.Any() == true)
+ {
+ Throw(nameof(options.ReasonString));
+ }
+
+ if (options.Reason != MqttClientDisconnectReason.NormalDisconnection)
+ {
+ Throw(nameof(options.Reason));
+ }
+ }
+
+ static void Throw(string featureName)
+ {
+ throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs
index 47ee9b6b..ed53a6c5 100644
--- a/Source/MQTTnet/Client/MqttClient.cs
+++ b/Source/MQTTnet/Client/MqttClient.cs
@@ -84,25 +84,15 @@ namespace MQTTnet.Client
add => _inspectPacketEvent.AddHandler(value);
remove => _inspectPacketEvent.RemoveHandler(value);
}
-
+
public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
public MqttClientOptions Options { get; private set; }
public async Task ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default)
{
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
-
- if (options.ChannelOptions == null)
- {
- throw new ArgumentException("ChannelOptions are not set.");
- }
-
+ ThrowIfOptionsInvalid(options);
ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
-
ThrowIfDisposed();
if (CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connecting, MqttClientConnectionStatus.Disconnected) != MqttClientConnectionStatus.Disconnected)
@@ -197,7 +187,7 @@ namespace MQTTnet.Client
{
return;
}
-
+
try
{
_disconnectReason = MqttClientDisconnectReason.NormalDisconnection;
@@ -205,6 +195,11 @@ namespace MQTTnet.Client
if (clientWasConnected)
{
+ if (Options.ValidateFeatures)
+ {
+ MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
+ }
+
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
}
@@ -239,6 +234,11 @@ namespace MQTTnet.Client
ThrowIfDisposed();
ThrowIfNotConnected();
+ if (Options.ValidateFeatures)
+ {
+ MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion);
+ }
+
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
switch (applicationMessage.QualityOfServiceLevel)
@@ -296,6 +296,11 @@ namespace MQTTnet.Client
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
}
+ if (Options.ValidateFeatures)
+ {
+ MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
+ }
+
ThrowIfDisposed();
ThrowIfNotConnected();
@@ -314,9 +319,19 @@ namespace MQTTnet.Client
throw new ArgumentNullException(nameof(options));
}
+ foreach (var topicFilter in options.TopicFilters)
+ {
+ MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter);
+ }
+
ThrowIfDisposed();
ThrowIfNotConnected();
+ if (Options.ValidateFeatures)
+ {
+ MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
+ }
+
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
@@ -376,7 +391,7 @@ namespace MQTTnet.Client
var connAckPacket = await SendAndReceiveAsync(connectPacket, cancellationToken).ConfigureAwait(false);
var clientConnectResultFactory = new MqttClientConnectResultFactory();
- result = clientConnectResultFactory.Create(connAckPacket, options.ProtocolVersion);
+ result = clientConnectResultFactory.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion);
}
catch (Exception exception)
{
@@ -705,6 +720,24 @@ namespace MQTTnet.Client
}
}
+ static void ThrowIfOptionsInvalid(MqttClientOptions options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ if (options.ChannelOptions == null)
+ {
+ throw new ArgumentException("ChannelOptions are not set.");
+ }
+
+ if (options.ValidateFeatures)
+ {
+ MqttClientOptionsValidator.ThrowIfNotSupported(options);
+ }
+ }
+
void TryInitiateDisconnect()
{
lock (_disconnectLock)
diff --git a/Source/MQTTnet/Client/Options/MqttClientOptions.cs b/Source/MQTTnet/Client/Options/MqttClientOptions.cs
index 931ece6f..1101b3e5 100644
--- a/Source/MQTTnet/Client/Options/MqttClientOptions.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientOptions.cs
@@ -14,13 +14,13 @@ namespace MQTTnet.Client
{
///
/// Gets or sets the authentication data.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
public byte[] AuthenticationData { get; set; }
///
/// Gets or sets the authentication method.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
public string AuthenticationMethod { get; set; }
@@ -58,6 +58,10 @@ namespace MQTTnet.Client
///
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
+ ///
+ /// Gets or sets the maximum packet size.
+ /// MQTT 5.0.0+ feature.
+ ///
public uint MaximumPacketSize { get; set; }
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
@@ -65,24 +69,26 @@ namespace MQTTnet.Client
///
/// Gets or sets the receive maximum.
/// This gives the maximum length of the receive messages.
+ /// MQTT 5.0.0+ feature.
///
public ushort ReceiveMaximum { get; set; }
///
/// Gets or sets the request problem information.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
public bool RequestProblemInformation { get; set; } = true;
///
/// Gets or sets the request response information.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
public bool RequestResponseInformation { get; set; }
///
/// Gets or sets the session expiry interval.
/// The time after a session expires when it's not actively used.
+ /// MQTT 5.0.0+ feature.
///
public uint SessionExpiryInterval { get; set; }
@@ -95,6 +101,7 @@ namespace MQTTnet.Client
///
/// Gets or sets the topic alias maximum.
/// This gives the maximum length of the topic alias.
+ /// MQTT 5.0.0+ feature.
///
public ushort TopicAliasMaximum { get; set; }
@@ -103,8 +110,10 @@ namespace MQTTnet.Client
/// client.
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated
/// correctly.
- /// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect
- /// properly.
+ ///
+ /// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not
+ /// connect properly.
+ ///
///
public bool TryPrivate { get; set; } = true;
@@ -115,28 +124,41 @@ namespace MQTTnet.Client
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
public List UserProperties { get; set; }
+ ///
+ /// When this feature is enabled the client will check if used properties are supported in the selected protocol
+ /// version.
+ /// This feature can be validated if an application message is generated one time but sent via different protocol
+ /// versions.
+ /// Default values are applied if the validation is off and features are not supported.
+ ///
+ public bool ValidateFeatures { get; set; } = true;
+
///
/// Gets or sets the content type of the will message.
+ /// MQTT 5.0.0+ feature.
///
public string WillContentType { get; set; }
///
/// Gets or sets the correlation data of the will message.
+ /// MQTT 5.0.0+ feature.
///
public byte[] WillCorrelationData { get; set; }
///
/// Gets or sets the will delay interval.
/// This is the time between the client disconnect and the time the will message will be sent.
+ /// MQTT 5.0.0+ feature.
///
public uint WillDelayInterval { get; set; }
///
/// Gets or sets the message expiry interval of the will message.
+ /// MQTT 5.0.0+ feature.
///
public uint WillMessageExpiryInterval { get; set; }
@@ -147,8 +169,9 @@ namespace MQTTnet.Client
///
/// Gets or sets the payload format indicator of the will message.
+ /// MQTT 5.0.0+ feature.
///
- public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; }
+ public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified;
///
/// Gets or sets the QoS level of the will message.
@@ -157,6 +180,7 @@ namespace MQTTnet.Client
///
/// Gets or sets the response topic of the will message.
+ /// MQTT 5.0.0+ feature.
///
public string WillResponseTopic { get; set; }
@@ -172,6 +196,7 @@ namespace MQTTnet.Client
///
/// Gets or sets the user properties of the will message.
+ /// MQTT 5.0.0+ feature.
///
public List WillUserProperties { get; set; }
diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
index 7e3d2547..96e48e39 100644
--- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
@@ -74,6 +74,8 @@ namespace MQTTnet.Client
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
+ MqttClientOptionsValidator.ThrowIfNotSupported(_options);
+
return _options;
}
diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs
index c065bb72..3304c65e 100644
--- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
-using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
@@ -13,7 +12,7 @@ namespace MQTTnet.Client
public sealed class MqttClientOptionsBuilderTlsParameters
{
public bool UseTls { get; set; }
-
+
public Func CertificateValidationHandler { get; set; }
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
@@ -29,13 +28,13 @@ namespace MQTTnet.Client
#endif
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
- public List ApplicationProtocols { get;set; }
+ public List ApplicationProtocols { get; set; }
#endif
- public bool AllowUntrustedCertificates { get; set; }
+ public bool AllowUntrustedCertificates { get; set; }
public bool IgnoreCertificateChainErrors { get; set; }
public bool IgnoreCertificateRevocationErrors { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsValidator.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsValidator.cs
new file mode 100644
index 00000000..38ddbfb4
--- /dev/null
+++ b/Source/MQTTnet/Client/Options/MqttClientOptionsValidator.cs
@@ -0,0 +1,114 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Linq;
+using MQTTnet.Formatter;
+using MQTTnet.Protocol;
+
+namespace MQTTnet.Client
+{
+ public static class MqttClientOptionsValidator
+ {
+ public static void ThrowIfNotSupported(MqttClientOptions options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ if (options.ProtocolVersion == MqttProtocolVersion.V500)
+ {
+ // Everything is supported.
+ return;
+ }
+
+ if (options.WillContentType?.Any() == true)
+ {
+ Throw(nameof(options.WillContentType));
+ }
+
+ if (options.UserProperties?.Any() == true)
+ {
+ Throw(nameof(options.UserProperties));
+ }
+
+ if (options.RequestProblemInformation)
+ {
+ // Since this value is a boolean and true by default, validation would
+ // require a nullable boolean.
+ //Throw(nameof(options.RequestProblemInformation));
+ }
+
+ if (options.RequestResponseInformation)
+ {
+ Throw(nameof(options.RequestResponseInformation));
+ }
+
+ if (options.ReceiveMaximum > 0)
+ {
+ Throw(nameof(options.ReceiveMaximum));
+ }
+
+ if (options.MaximumPacketSize > 0)
+ {
+ Throw(nameof(options.MaximumPacketSize));
+ }
+
+ // Authentication relevant properties.
+
+ if (options.AuthenticationData?.Any() == true)
+ {
+ Throw(nameof(options.AuthenticationData));
+ }
+
+ if (options.AuthenticationMethod?.Any() == true)
+ {
+ Throw(nameof(options.AuthenticationMethod));
+ }
+
+ // Will relevant properties.
+
+ if (options.WillPayloadFormatIndicator != MqttPayloadFormatIndicator.Unspecified)
+ {
+ Throw(nameof(options.WillPayloadFormatIndicator));
+ }
+
+ if (options.WillContentType?.Any() == true)
+ {
+ Throw(nameof(options.WillContentType));
+ }
+
+ if (options.WillCorrelationData?.Any() == true)
+ {
+ Throw(nameof(options.WillCorrelationData));
+ }
+
+ if (options.WillResponseTopic?.Any() == true)
+ {
+ Throw(nameof(options.WillResponseTopic));
+ }
+
+ if (options.WillDelayInterval > 0)
+ {
+ Throw(nameof(options.WillDelayInterval));
+ }
+
+ if (options.WillMessageExpiryInterval > 0)
+ {
+ Throw(nameof(options.WillMessageExpiryInterval));
+ }
+
+ if (options.WillUserProperties?.Any() == true)
+ {
+ Throw(nameof(options.WillUserProperties));
+ }
+ }
+
+ static void Throw(string featureName)
+ {
+ throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs
index 5c1c1ee3..47715a1b 100644
--- a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs
+++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs
@@ -10,27 +10,31 @@ namespace MQTTnet.Client
public sealed class MqttClientSubscribeOptions
{
///
- /// Gets or sets a list of topic filters the client wants to subscribe to.
- /// Topic filters can include regular topics or wild cards.
- ///
- public List TopicFilters { get; set; } = new List();
-
- ///
- /// Gets or sets the subscription identifier.
- /// The client can specify a subscription identifier when subscribing.
- /// The broker will establish and store the mapping relationship between this subscription and subscription identifier when successfully create or modify subscription.
- /// The broker will return the subscription identifier associated with this PUBLISH packet and the PUBLISH packet to the client when need to forward PUBLISH packets matching this subscription to this client.
- /// Hint: MQTT 5 feature only.
+ /// Gets or sets the subscription identifier.
+ /// The client can specify a subscription identifier when subscribing.
+ /// The broker will establish and store the mapping relationship between this subscription and subscription identifier
+ /// when successfully create or modify subscription.
+ /// The broker will return the subscription identifier associated with this PUBLISH packet and the PUBLISH packet to
+ /// the client when need to forward PUBLISH packets matching this subscription to this client.
+ /// MQTT 5.0.0+ feature.
///
public uint SubscriptionIdentifier { get; set; }
///
- /// Gets or sets the user properties.
- /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
- /// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
- /// The feature is very similar to the HTTP header concept.
- /// Hint: MQTT 5 feature only.
+ /// Gets or sets a list of topic filters the client wants to subscribe to.
+ /// Topic filters can include regular topics or wild cards.
+ ///
+ public List TopicFilters { get; set; } = new List();
+
+ ///
+ /// Gets or sets the user properties.
+ /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
+ /// packet.
+ /// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
+ /// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
+ /// The feature is very similar to the HTTP header concept.
+ /// MQTT 5.0.0+ feature.
///
public List UserProperties { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsValidator.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsValidator.cs
new file mode 100644
index 00000000..a28f6881
--- /dev/null
+++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsValidator.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Linq;
+using MQTTnet.Formatter;
+using MQTTnet.Protocol;
+
+namespace MQTTnet.Client
+{
+ public static class MqttClientSubscribeOptionsValidator
+ {
+ public static void ThrowIfNotSupported(MqttClientSubscribeOptions options, MqttProtocolVersion protocolVersion)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ if (protocolVersion == MqttProtocolVersion.V500)
+ {
+ // Everything is supported.
+ return;
+ }
+
+ if (options.UserProperties?.Any() == true)
+ {
+ Throw(nameof(options.UserProperties));
+ }
+
+ if (options.SubscriptionIdentifier != 0)
+ {
+ Throw(nameof(options.SubscriptionIdentifier));
+ }
+
+ if (options.TopicFilters?.Any(t => t.NoLocal) == true)
+ {
+ Throw("NoLocal");
+ }
+
+ if (options.TopicFilters?.Any(t => t.RetainAsPublished) == true)
+ {
+ Throw("RetainAsPublished");
+ }
+
+ if (options.TopicFilters?.Any(t => t.RetainHandling != MqttRetainHandling.SendAtSubscribe) == true)
+ {
+ Throw("RetainHandling");
+ }
+ }
+
+ static void Throw(string featureName)
+ {
+ throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs
index da331087..239c1aaf 100644
--- a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs
+++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptions.cs
@@ -10,18 +10,20 @@ namespace MQTTnet.Client
public sealed class MqttClientUnsubscribeOptions
{
///
- /// Gets or sets a list of topic filters the client wants to unsubscribe from.
- /// Topic filters can include regular topics or wild cards.
+ /// Gets or sets a list of topic filters the client wants to unsubscribe from.
+ /// Topic filters can include regular topics or wild cards.
///
public List TopicFilters { get; set; } = new List();
///
- /// Gets or sets the user properties.
- /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
- /// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
- /// The feature is very similar to the HTTP header concept.
- /// Hint: MQTT 5 feature only.
+ /// Gets or sets the user properties.
+ /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
+ /// packet.
+ /// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
+ /// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
+ /// The feature is very similar to the HTTP header concept.
+ /// MQTT 5.0.0+ feature.
///
- public List UserProperties { get; set; } = new List();
+ public List UserProperties { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs
new file mode 100644
index 00000000..0f901427
--- /dev/null
+++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsValidator.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Linq;
+using MQTTnet.Formatter;
+
+namespace MQTTnet.Client
+{
+ public static class MqttClientUnsubscribeOptionsValidator
+ {
+ public static void ThrowIfNotSupported(MqttClientUnsubscribeOptions options, MqttProtocolVersion protocolVersion)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ if (protocolVersion == MqttProtocolVersion.V500)
+ {
+ // Everything is supported.
+ return;
+ }
+
+ if (options.UserProperties?.Any() == true)
+ {
+ Throw(nameof(options.UserProperties));
+ }
+ }
+
+ static void Throw(string featureName)
+ {
+ throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/MqttApplicationMessageValidator.cs b/Source/MQTTnet/MqttApplicationMessageValidator.cs
new file mode 100644
index 00000000..17edbeaf
--- /dev/null
+++ b/Source/MQTTnet/MqttApplicationMessageValidator.cs
@@ -0,0 +1,68 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Linq;
+using MQTTnet.Formatter;
+using MQTTnet.Protocol;
+
+namespace MQTTnet
+{
+ public static class MqttApplicationMessageValidator
+ {
+ public static void ThrowIfNotSupported(MqttApplicationMessage applicationMessage, MqttProtocolVersion protocolVersion)
+ {
+ if (applicationMessage == null)
+ {
+ throw new ArgumentNullException(nameof(applicationMessage));
+ }
+
+ if (protocolVersion == MqttProtocolVersion.V500)
+ {
+ // Everything is supported.
+ return;
+ }
+
+ if (applicationMessage.ContentType?.Any() == true)
+ {
+ Throw(nameof(applicationMessage.ContentType));
+ }
+
+ if (applicationMessage.UserProperties?.Any() == true)
+ {
+ Throw(nameof(applicationMessage.UserProperties));
+ }
+
+ if (applicationMessage.CorrelationData?.Any() == true)
+ {
+ Throw(nameof(applicationMessage.CorrelationData));
+ }
+
+ if (applicationMessage.ResponseTopic?.Any() == true)
+ {
+ Throw(nameof(applicationMessage.ResponseTopic));
+ }
+
+ if (applicationMessage.SubscriptionIdentifiers?.Any() == true)
+ {
+ Throw(nameof(applicationMessage.SubscriptionIdentifiers));
+ }
+
+ if (applicationMessage.TopicAlias > 0)
+ {
+ Throw(nameof(applicationMessage.TopicAlias));
+ }
+
+ if (applicationMessage.PayloadFormatIndicator != MqttPayloadFormatIndicator.Unspecified)
+ {
+ Throw(nameof(applicationMessage.PayloadFormatIndicator));
+ }
+ }
+
+ static void Throw(string featureName)
+ {
+ throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Packets/MqttTopicFilter.cs b/Source/MQTTnet/Packets/MqttTopicFilter.cs
index 8b9d8064..41876e71 100644
--- a/Source/MQTTnet/Packets/MqttTopicFilter.cs
+++ b/Source/MQTTnet/Packets/MqttTopicFilter.cs
@@ -10,8 +10,8 @@ namespace MQTTnet.Packets
{
///
/// Gets or sets a value indicating whether the sender will not receive its own published application messages.
+ /// MQTT 5.0.0+ feature.
///
- /// Hint: MQTT 5 feature only.
public bool NoLocal { get; set; }
///
@@ -27,15 +27,15 @@ namespace MQTTnet.Packets
///
/// Gets or sets a value indicating whether messages are retained as published or not.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
public bool RetainAsPublished { get; set; }
///
/// Gets or sets the retain handling.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
- public MqttRetainHandling RetainHandling { get; set; }
+ public MqttRetainHandling RetainHandling { get; set; } = MqttRetainHandling.SendAtSubscribe;
///
/// Gets or sets the MQTT topic.
diff --git a/Source/MQTTnet/Protocol/MqttTopicValidator.cs b/Source/MQTTnet/Protocol/MqttTopicValidator.cs
index f9c9283c..b373397c 100644
--- a/Source/MQTTnet/Protocol/MqttTopicValidator.cs
+++ b/Source/MQTTnet/Protocol/MqttTopicValidator.cs
@@ -11,7 +11,10 @@ namespace MQTTnet.Protocol
{
public static void ThrowIfInvalid(MqttApplicationMessage applicationMessage)
{
- if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
+ if (applicationMessage == null)
+ {
+ throw new ArgumentNullException(nameof(applicationMessage));
+ }
if (applicationMessage.TopicAlias == 0)
{
@@ -26,7 +29,7 @@ namespace MQTTnet.Protocol
throw new MqttProtocolViolationException("Topic should not be empty.");
}
- foreach(var @char in topic)
+ foreach (var @char in topic)
{
if (@char == '+')
{
@@ -54,4 +57,4 @@ namespace MQTTnet.Protocol
}
}
}
-}
+}
\ No newline at end of file
From abe7bbda15095c797e87d7e258fd3e012618618d Mon Sep 17 00:00:00 2001
From: ghord
Date: Fri, 23 Dec 2022 17:02:03 +0100
Subject: [PATCH 40/48] Pass correct clientId to Unsubscribe interceptor
(#1631)
---
.../MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs
index 4155331d..b409d959 100644
--- a/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs
@@ -483,7 +483,7 @@ namespace MQTTnet.Server
async Task InterceptUnsubscribe(string topicFilter, MqttSubscription mqttSubscription, CancellationToken cancellationToken)
{
- var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs(cancellationToken, topicFilter, _session.Items, topicFilter)
+ var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs(cancellationToken, _session.Id, _session.Items, topicFilter)
{
Response =
{
From fdf00f1b18dc61f9c2bd7afe8d24af5efe7f73ce Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Fri, 23 Dec 2022 17:09:02 +0100
Subject: [PATCH 41/48] More options for disconnect (#1557)
* Expose more options in disconnect options.
* Expose more options in disconnect API.
* Refactor code.
* Expose user properties in disconnected event.
* Expose more disconnect options in server.
* Fix build errors
* Add new samples.
* Fix client cleanup
* Fix merge conflicts
---
.github/workflows/ReleaseNotes.md | 3 +
Samples/Client/Client_Connection_Samples.cs | 24 +-
Samples/Server/Server_Diagnostics_Samples.cs | 53 +++++
.../LowLevelMqttClient_Tests.cs | 2 +-
.../ManagedMqttClient_Tests.cs | 16 +-
.../MqttClient/MqttClient_Connection_Tests.cs | 162 ++++++++++++++
.../MqttClient}/MqttClient_Tests.cs | 66 ++----
.../MqttClientOptionsBuilder_Tests.cs | 3 +-
.../MQTTnet.Tests/Mockups/TestEnvironment.cs | 10 +
Source/MQTTnet.Tests/Server/General.cs | 2 +-
.../MqttClientConnectedEventArgs.cs | 2 +-
.../MqttClientDisconnectOptions.cs | 17 +-
.../MqttClientDisconnectOptionsBuilder.cs | 60 +++--
.../MqttClientDisconnectOptionsReason.cs | 27 +++
.../MqttClientDisconnectOptionsValidator.cs | 2 +-
.../MqttClientDisconnectedEventArgs.cs | 42 +++-
Source/MQTTnet/Client/MqttClient.cs | 205 +++++++++++-------
Source/MQTTnet/Client/MqttClientExtensions.cs | 47 +++-
.../Options/MqttClientOptionsBuilder.cs | 10 -
.../MqttClientSubscribeOptionsBuilder.cs | 72 +++---
.../MqttClientUnsubscribeOptionsBuilder.cs | 79 +++----
.../Formatter/MqttDisconnectPacketFactory.cs | 2 +
.../Implementations/CrossPlatformSocket.cs | 1 -
Source/MQTTnet/Internal/TaskExtensions.cs | 44 +++-
.../MQTTnet/MqttApplicationMessageBuilder.cs | 96 +-------
.../Events/ClientDisconnectedEventArgs.cs | 43 +++-
Source/MQTTnet/Server/Internal/MqttClient.cs | 15 +-
.../Internal/MqttClientSessionsManager.cs | 13 +-
28 files changed, 752 insertions(+), 366 deletions(-)
create mode 100644 Samples/Server/Server_Diagnostics_Samples.cs
rename Source/MQTTnet.Tests/{Client => Clients/LowLevelMqttClient}/LowLevelMqttClient_Tests.cs (99%)
rename Source/MQTTnet.Tests/{Client => Clients/ManagedMqttClient}/ManagedMqttClient_Tests.cs (95%)
create mode 100644 Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs
rename Source/MQTTnet.Tests/{Client => Clients/MqttClient}/MqttClient_Tests.cs (93%)
rename Source/MQTTnet.Tests/{Client => Clients}/MqttClientOptionsBuilder_Tests.cs (93%)
create mode 100644 Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsReason.cs
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index abfab925..1a651a56 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -1 +1,4 @@
+* [Client] Added support for passing MQTT v5 options (User properties etc.) for disconnects.
+* [Client] An internal exception is no longer caught silently when calling _DisconnectAsync_ to indicate that the disconnect is not clean (BREAKING CHANGE).
* [Client] MQTTv5 features are now checked and an exception is thrown if they are used when using protocol version 3.1.1 and lower. These checks can be disabled in client options. (BREAKING CHANGE!).
+* [Server] Exposed MQTT v5 sent properties from the affected client in _ClientDisconnectedAsync_ event.
diff --git a/Samples/Client/Client_Connection_Samples.cs b/Samples/Client/Client_Connection_Samples.cs
index f42667b7..81808a4e 100644
--- a/Samples/Client/Client_Connection_Samples.cs
+++ b/Samples/Client/Client_Connection_Samples.cs
@@ -16,6 +16,28 @@ namespace MQTTnet.Samples.Client;
public static class Client_Connection_Samples
{
+ public static async Task Clean_Disconnect()
+ {
+ /*
+ * This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
+ * to the server and close the connection afterwards.
+ *
+ * See sample _Connect_Client_ for more details.
+ */
+
+ var mqttFactory = new MqttFactory();
+
+ using (var mqttClient = mqttFactory.CreateMqttClient())
+ {
+ var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
+ await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
+
+ // This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
+ // connection is closed in a "not clean" way. See MQTT specification for more details.
+ await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
+ }
+ }
+
public static async Task Connect_Client()
{
/*
@@ -232,7 +254,7 @@ public static class Client_Connection_Samples
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
// Using a reason code requires MQTT version 5.0.0!
- await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
+ await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
}
}
diff --git a/Samples/Server/Server_Diagnostics_Samples.cs b/Samples/Server/Server_Diagnostics_Samples.cs
new file mode 100644
index 00000000..0f31e9be
--- /dev/null
+++ b/Samples/Server/Server_Diagnostics_Samples.cs
@@ -0,0 +1,53 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using MQTTnet.Internal;
+using MQTTnet.Packets;
+using MQTTnet.Server;
+
+namespace MQTTnet.Samples.Server;
+
+// ReSharper disable UnusedType.Global
+// ReSharper disable UnusedMember.Global
+// ReSharper disable InconsistentNaming
+// ReSharper disable EmptyConstructor
+// ReSharper disable MemberCanBeMadeStatic.Local
+
+public static class Server_Diagnostics_Samples
+{
+ public static async Task Get_Notified_When_Client_Received_Message()
+ {
+ /*
+ * This sample starts a MQTT server and attaches an event which gets fired whenever
+ * a client received and acknowledged a PUBLISH packet.
+ */
+
+ var mqttFactory = new MqttFactory();
+ var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
+
+ using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
+ {
+ // Attach the event handler.
+ mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
+ {
+ Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
+
+ // It is also possible to read additional data from the client response. This requires casting the response packet.
+ var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
+ Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
+
+ var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
+ Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}");
+ return CompletedTask.Instance;
+ };
+
+ await mqttServer.StartAsync();
+
+ Console.WriteLine("Press Enter to exit.");
+ Console.ReadLine();
+
+ await mqttServer.StopAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/Client/LowLevelMqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/LowLevelMqttClient/LowLevelMqttClient_Tests.cs
similarity index 99%
rename from Source/MQTTnet.Tests/Client/LowLevelMqttClient_Tests.cs
rename to Source/MQTTnet.Tests/Clients/LowLevelMqttClient/LowLevelMqttClient_Tests.cs
index e7366d3e..6e9485f2 100644
--- a/Source/MQTTnet.Tests/Client/LowLevelMqttClient_Tests.cs
+++ b/Source/MQTTnet.Tests/Clients/LowLevelMqttClient/LowLevelMqttClient_Tests.cs
@@ -14,7 +14,7 @@ using MQTTnet.LowLevelClient;
using MQTTnet.Packets;
using MQTTnet.Protocol;
-namespace MQTTnet.Tests.Client
+namespace MQTTnet.Tests.Clients.LowLevelMqttClient
{
[TestClass]
public sealed class LowLevelMqttClient_Tests : BaseTestClass
diff --git a/Source/MQTTnet.Tests/Client/ManagedMqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/ManagedMqttClient/ManagedMqttClient_Tests.cs
similarity index 95%
rename from Source/MQTTnet.Tests/Client/ManagedMqttClient_Tests.cs
rename to Source/MQTTnet.Tests/Clients/ManagedMqttClient/ManagedMqttClient_Tests.cs
index 69c9847b..f16d55fa 100644
--- a/Source/MQTTnet.Tests/Client/ManagedMqttClient_Tests.cs
+++ b/Source/MQTTnet.Tests/Clients/ManagedMqttClient/ManagedMqttClient_Tests.cs
@@ -17,7 +17,7 @@ using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
-namespace MQTTnet.Tests.Client
+namespace MQTTnet.Tests.Clients.ManagedMqttClient
{
[TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass
@@ -164,7 +164,7 @@ namespace MQTTnet.Tests.Client
.Build();
var dyingClient = testEnvironment.CreateClient();
- var dyingManagedClient = new ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
+ var dyingManagedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
@@ -199,7 +199,7 @@ namespace MQTTnet.Tests.Client
{
var server = await testEnvironment.StartServer();
- var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
+ var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var connected = GetConnectedTask(managedClient);
@@ -226,7 +226,7 @@ namespace MQTTnet.Tests.Client
await testEnvironment.StartServer();
- var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
+ var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var storage = new ManagedMqttClientTestStorage();
@@ -439,7 +439,7 @@ namespace MQTTnet.Tests.Client
}
}
- async Task CreateManagedClientAsync(
+ async Task CreateManagedClientAsync(
TestEnvironment testEnvironment,
IMqttClient underlyingClient = null,
TimeSpan? connectionCheckInterval = null,
@@ -456,7 +456,7 @@ namespace MQTTnet.Tests.Client
// at connection check intervals
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
- var managedClient = new ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
+ var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
var connected = GetConnectedTask(managedClient);
@@ -473,7 +473,7 @@ namespace MQTTnet.Tests.Client
/// name="managedClient" />
/// has connected
///
- Task GetConnectedTask(ManagedMqttClient managedClient)
+ Task GetConnectedTask(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient)
{
var connected = new TaskCompletionSource();
@@ -495,7 +495,7 @@ namespace MQTTnet.Tests.Client
/// name="expectedNumberOfMessages" />
/// have been received
///
- Task> SetupReceivingOfMessages(ManagedMqttClient managedClient, int expectedNumberOfMessages)
+ Task> SetupReceivingOfMessages(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient, int expectedNumberOfMessages)
{
var receivedMessages = new List();
var result = new AsyncTaskCompletionSource>();
diff --git a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs
new file mode 100644
index 00000000..e2ce36cf
--- /dev/null
+++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs
@@ -0,0 +1,162 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MQTTnet.Client;
+using MQTTnet.Exceptions;
+using MQTTnet.Formatter;
+using MQTTnet.Internal;
+using MQTTnet.Protocol;
+using MQTTnet.Server;
+
+namespace MQTTnet.Tests.Clients.MqttClient
+{
+ [TestClass]
+ public sealed class MqttClient_Connection_Tests : BaseTestClass
+ {
+ [TestMethod]
+ [ExpectedException(typeof(MqttCommunicationException))]
+ public async Task Connect_To_Invalid_Server_Port_Not_Opened()
+ {
+ var client = new MqttFactory().CreateMqttClient();
+ using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
+ {
+ await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).Build(), timeout.Token);
+ }
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(OperationCanceledException))]
+ public async Task Connect_To_Invalid_Server_Wrong_IP()
+ {
+ var client = new MqttFactory().CreateMqttClient();
+ using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
+ {
+ await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
+ }
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(MqttCommunicationException))]
+ public async Task Connect_To_Invalid_Server_Wrong_Protocol()
+ {
+ var client = new MqttFactory().CreateMqttClient();
+ await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
+ }
+
+ [TestMethod]
+ public async Task ConnectTimeout_Throws_Exception()
+ {
+ var factory = new MqttFactory();
+ using (var client = factory.CreateMqttClient())
+ {
+ var disconnectHandlerCalled = false;
+ try
+ {
+ client.DisconnectedAsync += args =>
+ {
+ disconnectHandlerCalled = true;
+ return CompletedTask.Instance;
+ };
+
+ await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
+
+ Assert.Fail("Must fail!");
+ }
+ catch (Exception exception)
+ {
+ Assert.IsNotNull(exception);
+ Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
+ }
+
+ await LongTestDelay(); // disconnected handler is called async
+ Assert.IsTrue(disconnectHandlerCalled);
+ }
+ }
+
+ [TestMethod]
+ public async Task Disconnect_Clean()
+ {
+ using (var testEnvironment = CreateTestEnvironment())
+ {
+ var server = await testEnvironment.StartServer();
+
+ ClientDisconnectedEventArgs eventArgs = null;
+ server.ClientDisconnectedAsync += args =>
+ {
+ eventArgs = args;
+ return CompletedTask.Instance;
+ };
+
+ var client = await testEnvironment.ConnectClient();
+
+ var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
+
+ // Perform a clean disconnect.
+ await client.DisconnectAsync(disconnectOptions);
+
+ Assert.IsNotNull(eventArgs);
+ Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
+ }
+ }
+
+ [TestMethod]
+ public async Task Disconnect_Clean_With_Custom_Reason()
+ {
+ using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
+ {
+ var server = await testEnvironment.StartServer();
+
+ ClientDisconnectedEventArgs eventArgs = null;
+ server.ClientDisconnectedAsync += args =>
+ {
+ eventArgs = args;
+ return CompletedTask.Instance;
+ };
+
+ var client = await testEnvironment.ConnectClient();
+
+ var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
+
+ // Perform a clean disconnect.
+ await client.DisconnectAsync(disconnectOptions);
+
+ Assert.IsNotNull(eventArgs);
+ Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
+ }
+ }
+
+ [TestMethod]
+ public async Task Disconnect_Clean_With_User_Properties()
+ {
+ using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
+ {
+ var server = await testEnvironment.StartServer();
+
+ ClientDisconnectedEventArgs eventArgs = null;
+ server.ClientDisconnectedAsync += args =>
+ {
+ eventArgs = args;
+ return CompletedTask.Instance;
+ };
+
+ var client = await testEnvironment.ConnectClient();
+
+ var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithUserProperty("test_name", "test_value").Build();
+
+ // Perform a clean disconnect.
+ await client.DisconnectAsync(disconnectOptions);
+
+ Assert.IsNotNull(eventArgs);
+ Assert.IsNotNull(eventArgs.UserProperties);
+ Assert.AreEqual(1, eventArgs.UserProperties.Count);
+ Assert.AreEqual("test_name", eventArgs.UserProperties[0].Name);
+ Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet.Tests/Client/MqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs
similarity index 93%
rename from Source/MQTTnet.Tests/Client/MqttClient_Tests.cs
rename to Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs
index 8553ebd0..7b65e07f 100644
--- a/Source/MQTTnet.Tests/Client/MqttClient_Tests.cs
+++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs
@@ -21,7 +21,7 @@ using MQTTnet.Tests.Mockups;
// ReSharper disable InconsistentNaming
-namespace MQTTnet.Tests.Client
+namespace MQTTnet.Tests.Clients.MqttClient
{
[TestClass]
public sealed class MqttClient_Tests : BaseTestClass
@@ -71,60 +71,34 @@ namespace MQTTnet.Tests.Client
}
[TestMethod]
- [ExpectedException(typeof(MqttCommunicationException))]
- public async Task Connect_To_Invalid_Server_Port_Not_Opened()
+ public async Task Connect_Disconnect_Connect()
{
- var client = new MqttFactory().CreateMqttClient();
- await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(5)).Build());
- }
-
- [TestMethod]
- [ExpectedException(typeof(OperationCanceledException))]
- public async Task Connect_To_Invalid_Server_Wrong_IP()
- {
- var client = new MqttFactory().CreateMqttClient();
- using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
+ using (var testEnvironment = CreateTestEnvironment())
{
- await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
+ await testEnvironment.StartServer();
+
+ var clientOptions = testEnvironment.CreateDefaultClientOptions();
+ var client = testEnvironment.CreateClient();
+
+ await client.ConnectAsync(clientOptions);
+ await client.DisconnectAsync();
+ await client.ConnectAsync(clientOptions);
}
}
-
+
[TestMethod]
- [ExpectedException(typeof(MqttCommunicationException))]
- public async Task Connect_To_Invalid_Server_Wrong_Protocol()
+ [ExpectedException(typeof(InvalidOperationException))]
+ public async Task Connect_Multiple_Times_Should_Fail()
{
- var client = new MqttFactory().CreateMqttClient();
- await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
- }
-
- [TestMethod]
- public async Task ConnectTimeout_Throws_Exception()
- {
- var factory = new MqttFactory();
- using (var client = factory.CreateMqttClient())
+ using (var testEnvironment = CreateTestEnvironment())
{
- var disconnectHandlerCalled = false;
- try
- {
- client.DisconnectedAsync += args =>
- {
- disconnectHandlerCalled = true;
- return CompletedTask.Instance;
- };
+ await testEnvironment.StartServer();
- await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
+ var clientOptions = testEnvironment.CreateDefaultClientOptions();
+ var client = testEnvironment.CreateClient();
- Assert.Fail("Must fail!");
- }
- catch (Exception exception)
- {
- Assert.IsNotNull(exception);
- Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
- //Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
- }
-
- await LongTestDelay(); // disconnected handler is called async
- Assert.IsTrue(disconnectHandlerCalled);
+ await client.ConnectAsync(clientOptions);
+ await client.ConnectAsync(clientOptions);
}
}
diff --git a/Source/MQTTnet.Tests/Client/MqttClientOptionsBuilder_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClientOptionsBuilder_Tests.cs
similarity index 93%
rename from Source/MQTTnet.Tests/Client/MqttClientOptionsBuilder_Tests.cs
rename to Source/MQTTnet.Tests/Clients/MqttClientOptionsBuilder_Tests.cs
index 59db1094..dac2404e 100644
--- a/Source/MQTTnet.Tests/Client/MqttClientOptionsBuilder_Tests.cs
+++ b/Source/MQTTnet.Tests/Clients/MqttClientOptionsBuilder_Tests.cs
@@ -6,9 +6,8 @@ using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
-using MQTTnet.Extensions;
-namespace MQTTnet.Tests.Client
+namespace MQTTnet.Tests.Clients
{
[TestClass]
public class MqttClientOptionsBuilder_Tests
diff --git a/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs b/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs
index 44d692d9..9995ee82 100644
--- a/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs
+++ b/Source/MQTTnet.Tests/Mockups/TestEnvironment.cs
@@ -238,6 +238,16 @@ namespace MQTTnet.Tests.Mockups
}
}
+ public MqttClientOptions CreateDefaultClientOptions()
+ {
+ return CreateDefaultClientOptionsBuilder().Build();
+ }
+
+ public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
+ {
+ return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort);
+ }
+
public ILowLevelMqttClient CreateLowLevelClient()
{
lock (_clients)
diff --git a/Source/MQTTnet.Tests/Server/General.cs b/Source/MQTTnet.Tests/Server/General.cs
index eeb75fc6..9d1b0c25 100644
--- a/Source/MQTTnet.Tests/Server/General.cs
+++ b/Source/MQTTnet.Tests/Server/General.cs
@@ -636,7 +636,7 @@ namespace MQTTnet.Tests.Server
// nothing
Assert.AreEqual(false, c1.IsConnected);
- await c1.DisconnectAsync();
+ await c1.TryDisconnectAsync();
Assert.AreEqual(false, c1.IsConnected);
await Task.Delay(500);
diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs
index efb28e83..2ea08978 100644
--- a/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs
+++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
///
/// Gets the authentication result.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
public MqttClientConnectResult ConnectResult { get; }
}
diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs
index 964e30a7..7f57ca2a 100644
--- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs
+++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptions.cs
@@ -2,6 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
+using MQTTnet.Packets;
+
namespace MQTTnet.Client
{
public sealed class MqttClientDisconnectOptions
@@ -10,12 +13,24 @@ namespace MQTTnet.Client
/// Gets or sets the reason code.
/// MQTT 5.0.0+ feature.
///
- public MqttClientDisconnectReason Reason { get; set; } = MqttClientDisconnectReason.NormalDisconnection;
+ public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection;
///
/// Gets or sets the reason string.
/// MQTT 5.0.0+ feature.
///
public string ReasonString { get; set; }
+
+ ///
+ /// Gets or sets the session expiry interval.
+ /// MQTT 5.0.0+ feature.
+ ///
+ public uint SessionExpiryInterval { get; set; }
+
+ ///
+ /// Gets or sets the user properties.
+ /// MQTT 5.0.0+ feature.
+ ///
+ public List UserProperties { get; set; }
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsBuilder.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsBuilder.cs
index 34f16b74..3c402309 100644
--- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsBuilder.cs
+++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsBuilder.cs
@@ -2,32 +2,62 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Collections.Generic;
+using MQTTnet.Packets;
+
namespace MQTTnet.Client
{
public sealed class MqttClientDisconnectOptionsBuilder
{
- MqttClientDisconnectReason _reason = MqttClientDisconnectReason.NormalDisconnection;
+ MqttClientDisconnectOptionsReason _reason = MqttClientDisconnectOptionsReason.NormalDisconnection;
string _reasonString;
-
- public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
- {
- _reasonString = value;
- return this;
- }
-
- public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectReason value)
- {
- _reason = value;
- return this;
- }
-
+ uint _sessionExpiryInterval;
+ List _userProperties;
+
public MqttClientDisconnectOptions Build()
{
return new MqttClientDisconnectOptions
{
Reason = _reason,
- ReasonString = _reasonString
+ ReasonString = _reasonString,
+ UserProperties = _userProperties,
+ SessionExpiryInterval = _sessionExpiryInterval
};
}
+
+ public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectOptionsReason value)
+ {
+ _reason = value;
+ return this;
+ }
+
+ public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
+ {
+ _reasonString = value;
+ return this;
+ }
+
+ public MqttClientDisconnectOptionsBuilder WithSessionExpiryInterval(uint value)
+ {
+ _sessionExpiryInterval = value;
+ return this;
+ }
+
+ public MqttClientDisconnectOptionsBuilder WithUserProperties(List userProperties)
+ {
+ _userProperties = userProperties;
+ return this;
+ }
+
+ public MqttClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
+ {
+ if (_userProperties == null)
+ {
+ _userProperties = new List();
+ }
+
+ _userProperties.Add(new MqttUserProperty(name, value));
+ return this;
+ }
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsReason.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsReason.cs
new file mode 100644
index 00000000..c51ec995
--- /dev/null
+++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsReason.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace MQTTnet.Client
+{
+ ///
+ /// This enum only contains values which are valid when a client sends the reason to the server.
+ ///
+ public enum MqttClientDisconnectOptionsReason
+ {
+ NormalDisconnection = 0,
+ DisconnectWithWillMessage = 4,
+ UnspecifiedError = 128,
+ MalformedPacket = 129,
+ ProtocolError = 130,
+ ImplementationSpecificError = 131,
+ TopicNameInvalid = 144,
+ ReceiveMaximumExceeded = 147,
+ TopicAliasInvalid = 148,
+ PacketTooLarge = 149,
+ MessageRateTooHigh = 150,
+ QuotaExceeded = 151,
+ AdministrativeAction = 152,
+ PayloadFormatInvalid = 153
+ }
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs
index b5fc4cc8..0a060483 100644
--- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs
+++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectOptionsValidator.cs
@@ -28,7 +28,7 @@ namespace MQTTnet.Client
Throw(nameof(options.ReasonString));
}
- if (options.Reason != MqttClientDisconnectReason.NormalDisconnection)
+ if (options.Reason != MqttClientDisconnectOptionsReason.NormalDisconnection)
{
Throw(nameof(options.Reason));
}
diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs
index 009e0208..0a24e44f 100644
--- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs
+++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs
@@ -3,27 +3,47 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Generic;
+using MQTTnet.Packets;
namespace MQTTnet.Client
{
public sealed class MqttClientDisconnectedEventArgs : EventArgs
{
- public bool ClientWasConnected { get; internal set; }
+ public MqttClientDisconnectedEventArgs(
+ bool clientWasConnected,
+ MqttClientConnectResult connectResult,
+ MqttClientDisconnectReason reason,
+ string reasonString,
+ List userProperties,
+ Exception exception)
+ {
+ ClientWasConnected = clientWasConnected;
+ ConnectResult = connectResult;
+ Exception = exception;
+ Reason = reason;
+ ReasonString = reasonString;
+ UserProperties = userProperties;
+ }
- public Exception Exception { get; internal set; }
+ public bool ClientWasConnected { get; }
///
- /// Gets the authentication result.
- /// Hint: MQTT 5 feature only.
+ /// Gets the authentication result.
+ /// MQTT 5.0.0+ feature.
///
- public MqttClientConnectResult ConnectResult { get; internal set; }
+ public MqttClientConnectResult ConnectResult { get; }
+
+ public Exception Exception { get; }
///
- /// Gets or sets the reason.
- /// Hint: MQTT 5 feature only.
+ /// Gets or sets the reason.
+ /// MQTT 5.0.0+ feature.
///
- public MqttClientDisconnectReason Reason { get; internal set; }
-
- public string ReasonString { get; internal set; }
+ public MqttClientDisconnectReason Reason { get; }
+
+ public string ReasonString { get; }
+
+ public List UserProperties { get; }
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs
index ed53a6c5..3ed4dd80 100644
--- a/Source/MQTTnet/Client/MqttClient.cs
+++ b/Source/MQTTnet/Client/MqttClient.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
@@ -36,13 +37,20 @@ namespace MQTTnet.Client
readonly IMqttNetLogger _rootLogger;
IMqttChannelAdapter _adapter;
- CancellationTokenSource _backgroundCancellationTokenSource;
+
bool _cleanDisconnectInitiated;
volatile int _connectionStatus;
- MqttClientDisconnectReason _disconnectReason;
+
+ // The value for this field can be set from two different enums.
+ // They contain the same values but the set is reduced in one case.
+ int _disconnectReason;
string _disconnectReasonString;
+ List _disconnectUserProperties;
+
Task _keepAlivePacketsSenderTask;
DateTime _lastPacketSentTimestamp;
+
+ CancellationTokenSource _mqttClientAlive;
MqttPacketDispatcher _packetDispatcher;
Task _packetReceiverTask;
AsyncQueue _publishPacketReceiverQueue;
@@ -111,28 +119,29 @@ namespace MQTTnet.Client
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
}
+ Cleanup();
+
_packetIdentifierProvider.Reset();
_packetDispatcher = new MqttPacketDispatcher();
- _backgroundCancellationTokenSource = new CancellationTokenSource();
- var backgroundCancellationToken = _backgroundCancellationTokenSource.Token;
+ _mqttClientAlive = new CancellationTokenSource();
+ var mqttClientAliveToken = _mqttClientAlive.Token;
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_inspectPacketEvent, _rootLogger), _rootLogger);
_adapter = adapter;
- using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
+ if (cancellationToken.CanBeCanceled)
{
- _logger.Verbose("Trying to connect with server '{0}'.", options.ChannelOptions);
- await adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
- _logger.Verbose("Connection with server established.");
-
- _publishPacketReceiverQueue?.Dispose();
- _publishPacketReceiverQueue = new AsyncQueue();
- _publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
-
- _packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
-
- connectResult = await AuthenticateAsync(options, effectiveCancellationToken.Token).ConfigureAwait(false);
+ connectResult = await ConnectInternal(cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ // Fall back to the general timeout specified in the options if the user passed
+ // CancellationToken.None or similar.
+ using (var timeout = new CancellationTokenSource(Options.Timeout))
+ {
+ connectResult = await ConnectInternal(timeout.Token).ConfigureAwait(false);
+ }
}
_lastPacketSentTimestamp = DateTime.UtcNow;
@@ -146,7 +155,7 @@ namespace MQTTnet.Client
if (keepAliveInterval != TimeSpan.Zero)
{
- _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(backgroundCancellationToken), backgroundCancellationToken);
+ _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(mqttClientAliveToken), mqttClientAliveToken);
}
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
@@ -163,7 +172,7 @@ namespace MQTTnet.Client
}
catch (Exception exception)
{
- _disconnectReason = MqttClientDisconnectReason.UnspecifiedError;
+ _disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError;
_logger.Error(exception, "Error while connecting with server.");
@@ -183,26 +192,42 @@ namespace MQTTnet.Client
ThrowIfDisposed();
var clientWasConnected = IsConnected;
+
if (DisconnectIsPendingOrFinished())
{
return;
}
-
+
try
{
- _disconnectReason = MqttClientDisconnectReason.NormalDisconnection;
+ if (!clientWasConnected)
+ {
+ ThrowNotConnected();
+ }
+
+ _disconnectReason = (int)options.Reason;
_cleanDisconnectInitiated = true;
- if (clientWasConnected)
+ if (Options.ValidateFeatures)
+ {
+ MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
+ }
+
+ // Sending the DISCONNECT may fail due to connection issues. The resulting exception
+ // must be throw to let the caller know that the disconnect is not a clean one.
+ var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
+
+ if (cancellationToken.CanBeCanceled)
{
- if (Options.ValidateFeatures)
- {
- MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
- }
-
- var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
}
+ else
+ {
+ using (var timeout = new CancellationTokenSource(Options.Timeout))
+ {
+ await SendAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
+ }
+ }
}
finally
{
@@ -307,7 +332,18 @@ namespace MQTTnet.Client
var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
- var subAckPacket = await SendAndReceiveAsync(subscribePacket, cancellationToken).ConfigureAwait(false);
+ MqttSubAckPacket subAckPacket;
+ if (cancellationToken.CanBeCanceled)
+ {
+ subAckPacket = await SendAndReceiveAsync(subscribePacket, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ using (var timeout = new CancellationTokenSource(Options.Timeout))
+ {
+ subAckPacket = await SendAndReceiveAsync(subscribePacket, timeout.Token).ConfigureAwait(false);
+ }
+ }
return _clientSubscribeResultFactory.Create(subscribePacket, subAckPacket);
}
@@ -335,7 +371,18 @@ namespace MQTTnet.Client
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
- var unsubAckPacket = await SendAndReceiveAsync(unsubscribePacket, cancellationToken).ConfigureAwait(false);
+ MqttUnsubAckPacket unsubAckPacket;
+ if (cancellationToken.CanBeCanceled)
+ {
+ unsubAckPacket = await SendAndReceiveAsync(unsubscribePacket, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ using (var timeout = new CancellationTokenSource(Options.Timeout))
+ {
+ unsubAckPacket = await SendAndReceiveAsync(unsubscribePacket, timeout.Token).ConfigureAwait(false);
+ }
+ }
return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket);
}
@@ -412,19 +459,19 @@ namespace MQTTnet.Client
{
try
{
- _backgroundCancellationTokenSource?.Cancel(false);
+ _mqttClientAlive?.Cancel(false);
}
finally
{
- _backgroundCancellationTokenSource?.Dispose();
- _backgroundCancellationTokenSource = null;
+ _mqttClientAlive?.Dispose();
+ _mqttClientAlive = null;
_publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = null;
_adapter?.Dispose();
_adapter = null;
-
+
_packetDispatcher?.Dispose();
_packetDispatcher = null;
}
@@ -435,6 +482,26 @@ namespace MQTTnet.Client
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand);
}
+ async Task ConnectInternal(CancellationToken cancellationToken)
+ {
+ var backgroundCancellationToken = _mqttClientAlive.Token;
+
+ using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
+ {
+ _logger.Verbose("Trying to connect with server '{0}'.", Options.ChannelOptions);
+ await _adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
+ _logger.Verbose("Connection with server established.");
+
+ _publishPacketReceiverQueue?.Dispose();
+ _publishPacketReceiverQueue = new AsyncQueue();
+
+ _publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
+ _packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
+
+ return await AuthenticateAsync(Options, effectiveCancellationToken.Token).ConfigureAwait(false);
+ }
+ }
+
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
{
TryInitiateDisconnect();
@@ -461,10 +528,10 @@ namespace MQTTnet.Client
try
{
_packetDispatcher.Dispose(new MqttClientDisconnectedException(exception));
-
- var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender);
- var publishPacketReceiverTask = WaitForTaskAsync(_publishPacketReceiverTask, sender);
- var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender);
+
+ var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
+ var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
+ var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger);
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
}
@@ -480,14 +547,13 @@ namespace MQTTnet.Client
_logger.Info("Disconnected.");
- var eventArgs = new MqttClientDisconnectedEventArgs
- {
- ClientWasConnected = clientWasConnected,
- Exception = exception,
- ConnectResult = connectResult,
- Reason = _disconnectReason,
- ReasonString = _disconnectReasonString
- };
+ var eventArgs = new MqttClientDisconnectedEventArgs(
+ clientWasConnected,
+ connectResult,
+ (MqttClientDisconnectReason)_disconnectReason,
+ _disconnectReasonString,
+ _disconnectUserProperties,
+ exception);
// This handler must be executed in a new thread because otherwise a dead lock may happen
// when trying to reconnect in that handler etc.
@@ -550,7 +616,6 @@ namespace MQTTnet.Client
{
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
-
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
return eventArgs;
@@ -569,8 +634,9 @@ namespace MQTTnet.Client
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
{
- _disconnectReason = (MqttClientDisconnectReason)disconnectPacket.ReasonCode;
+ _disconnectReason = (int)disconnectPacket.ReasonCode;
_disconnectReasonString = disconnectPacket.ReasonString;
+ _disconnectUserProperties = disconnectPacket.UserProperties;
// Also dispatch disconnect to waiting threads to generate a proper exception.
_packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket));
@@ -632,7 +698,6 @@ namespace MQTTnet.Client
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var pubAckPacket = await SendAndReceiveAsync(publishPacket, cancellationToken).ConfigureAwait(false);
-
return _clientPublishResultFactory.Create(pubAckPacket);
}
@@ -708,7 +773,7 @@ namespace MQTTnet.Client
{
if (IsConnected)
{
- throw new MqttProtocolViolationException(message);
+ throw new InvalidOperationException(message);
}
}
@@ -716,7 +781,7 @@ namespace MQTTnet.Client
{
if (!IsConnected)
{
- throw new MqttCommunicationException("The client is not connected.");
+ ThrowNotConnected();
}
}
@@ -738,13 +803,18 @@ namespace MQTTnet.Client
}
}
+ static void ThrowNotConnected()
+ {
+ throw new MqttCommunicationException("The client is not connected.");
+ }
+
void TryInitiateDisconnect()
{
lock (_disconnectLock)
{
try
{
- _backgroundCancellationTokenSource?.Cancel(false);
+ _mqttClientAlive?.Cancel(false);
}
catch (Exception exception)
{
@@ -864,7 +934,7 @@ namespace MQTTnet.Client
{
exception = aggregateException.GetBaseException();
}
-
+
if (exception is OperationCanceledException)
{
}
@@ -943,36 +1013,5 @@ namespace MQTTnet.Client
_logger.Verbose("Stopped sending keep alive packets.");
}
}
-
- async Task WaitForTaskAsync(Task task, Task sender)
- {
- if (task == null)
- {
- return;
- }
-
- if (task == sender)
- {
- // Return here to avoid deadlocks, but first any eventual exception in the task
- // must be handled to avoid not getting an unhandled task exception
- if (!task.IsFaulted)
- {
- return;
- }
-
- // By accessing the Exception property the exception is considered handled and will
- // not result in an unhandled task exception later by the finalizer
- _logger.Warning(task.Exception, "Error while waiting for background task.");
- return;
- }
-
- try
- {
- await task.ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
- }
- }
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs
index ba917aad..da2f2995 100644
--- a/Source/MQTTnet/Client/MqttClientExtensions.cs
+++ b/Source/MQTTnet/Client/MqttClientExtensions.cs
@@ -14,20 +14,28 @@ namespace MQTTnet.Client
{
public static class MqttClientExtensions
{
- public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
+ public static Task DisconnectAsync(
+ this IMqttClient client,
+ MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
+ string reasonString = null,
+ uint sessionExpiryInterval = 0,
+ List userProperties = null,
+ CancellationToken cancellationToken = default)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}
- return client.DisconnectAsync(
- new MqttClientDisconnectOptions
- {
- Reason = reason,
- ReasonString = reasonString
- },
- CancellationToken.None);
+ var disconnectOptions = new MqttClientDisconnectOptions
+ {
+ Reason = reason,
+ ReasonString = reasonString,
+ SessionExpiryInterval = sessionExpiryInterval,
+ UserProperties = userProperties
+ };
+
+ return client.DisconnectAsync(disconnectOptions, cancellationToken);
}
public static Task PublishBinaryAsync(
@@ -128,6 +136,29 @@ namespace MQTTnet.Client
return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken);
}
+ public static async Task TryDisconnectAsync(
+ this IMqttClient client,
+ MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
+ string reasonString = null)
+ {
+ if (client == null)
+ {
+ throw new ArgumentNullException(nameof(client));
+ }
+
+ try
+ {
+ await client.DisconnectAsync(reason, reasonString).ConfigureAwait(false);
+ return true;
+ }
+ catch
+ {
+ // Ignore all errors.
+ }
+
+ return false;
+ }
+
public static async Task TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{
if (client == null)
diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
index 96e48e39..029f5a08 100644
--- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
@@ -339,16 +339,6 @@ namespace MQTTnet.Client
public MqttClientOptionsBuilder WithUserProperty(string name, string value)
{
- if (name is null)
- {
- throw new ArgumentNullException(nameof(name));
- }
-
- if (value is null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-
if (_options.UserProperties == null)
{
_options.UserProperties = new List();
diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs
index c6b5890a..7075ef89 100644
--- a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs
+++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs
@@ -14,26 +14,9 @@ namespace MQTTnet.Client
{
readonly MqttClientSubscribeOptions _subscribeOptions = new MqttClientSubscribeOptions();
- ///
- /// Adds the user property to the subscribe options.
- /// Hint: MQTT 5 feature only.
- ///
- /// The property name.
- /// The property value.
- /// A new instance of the class.
- public MqttClientSubscribeOptionsBuilder WithUserProperty(string name, string value)
+ public MqttClientSubscribeOptions Build()
{
- if (name == null) throw new ArgumentNullException(nameof(name));
- if (value == null) throw new ArgumentNullException(nameof(value));
-
- if (_subscribeOptions.UserProperties == null)
- {
- _subscribeOptions.UserProperties = new List();
- }
-
- _subscribeOptions.UserProperties.Add(new MqttUserProperty(name, value));
-
- return this;
+ return _subscribeOptions;
}
public MqttClientSubscribeOptionsBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier)
@@ -42,7 +25,7 @@ namespace MQTTnet.Client
{
throw new MqttProtocolViolationException("Subscription identifier cannot be 0.");
}
-
+
_subscribeOptions.SubscriptionIdentifier = subscriptionIdentifier;
return this;
}
@@ -54,19 +37,23 @@ namespace MQTTnet.Client
bool retainAsPublished = false,
MqttRetainHandling retainHandling = MqttRetainHandling.SendAtSubscribe)
{
- return WithTopicFilter(new MqttTopicFilter
- {
- Topic = topic,
- QualityOfServiceLevel = qualityOfServiceLevel,
- NoLocal = noLocal,
- RetainAsPublished = retainAsPublished,
- RetainHandling = retainHandling
- });
+ return WithTopicFilter(
+ new MqttTopicFilter
+ {
+ Topic = topic,
+ QualityOfServiceLevel = qualityOfServiceLevel,
+ NoLocal = noLocal,
+ RetainAsPublished = retainAsPublished,
+ RetainHandling = retainHandling
+ });
}
public MqttClientSubscribeOptionsBuilder WithTopicFilter(Action topicFilterBuilder)
{
- if (topicFilterBuilder == null) throw new ArgumentNullException(nameof(topicFilterBuilder));
+ if (topicFilterBuilder == null)
+ {
+ throw new ArgumentNullException(nameof(topicFilterBuilder));
+ }
var internalTopicFilterBuilder = new MqttTopicFilterBuilder();
topicFilterBuilder(internalTopicFilterBuilder);
@@ -76,14 +63,20 @@ namespace MQTTnet.Client
public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilterBuilder topicFilterBuilder)
{
- if (topicFilterBuilder == null) throw new ArgumentNullException(nameof(topicFilterBuilder));
+ if (topicFilterBuilder == null)
+ {
+ throw new ArgumentNullException(nameof(topicFilterBuilder));
+ }
return WithTopicFilter(topicFilterBuilder.Build());
}
public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter)
{
- if (topicFilter == null) throw new ArgumentNullException(nameof(topicFilter));
+ if (topicFilter == null)
+ {
+ throw new ArgumentNullException(nameof(topicFilter));
+ }
if (_subscribeOptions.TopicFilters == null)
{
@@ -95,9 +88,20 @@ namespace MQTTnet.Client
return this;
}
- public MqttClientSubscribeOptions Build()
+ ///
+ /// Adds the user property to the subscribe options.
+ /// MQTT 5.0.0+ feature.
+ ///
+ public MqttClientSubscribeOptionsBuilder WithUserProperty(string name, string value)
{
- return _subscribeOptions;
+ if (_subscribeOptions.UserProperties == null)
+ {
+ _subscribeOptions.UserProperties = new List();
+ }
+
+ _subscribeOptions.UserProperties.Add(new MqttUserProperty(name, value));
+
+ return this;
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs
index b9aabe4a..1db31117 100644
--- a/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs
+++ b/Source/MQTTnet/Client/Unsubscribing/MqttClientUnsubscribeOptionsBuilder.cs
@@ -8,48 +8,21 @@ using MQTTnet.Packets;
namespace MQTTnet.Client
{
- public class MqttClientUnsubscribeOptionsBuilder
+ public sealed class MqttClientUnsubscribeOptionsBuilder
{
- private readonly MqttClientUnsubscribeOptions _unsubscribeOptions = new MqttClientUnsubscribeOptions();
+ readonly MqttClientUnsubscribeOptions _unsubscribeOptions = new MqttClientUnsubscribeOptions();
- ///
- /// Adds the user property to the unsubscribe options.
- /// Hint: MQTT 5 feature only.
- ///
- /// The property name.
- /// The property value.
- /// A new instance of the class.
- public MqttClientUnsubscribeOptionsBuilder WithUserProperty(string name, string value)
+ public MqttClientUnsubscribeOptions Build()
{
- if (name is null) throw new ArgumentNullException(nameof(name));
- if (value is null) throw new ArgumentNullException(nameof(value));
-
- return WithUserProperty(new MqttUserProperty(name, value));
- }
-
- ///
- /// Adds the user property to the unsubscribe options.
- /// Hint: MQTT 5 feature only.
- ///
- /// The user property.
- /// A new instance of the class.
- public MqttClientUnsubscribeOptionsBuilder WithUserProperty(MqttUserProperty userProperty)
- {
- if (userProperty is null) throw new ArgumentNullException(nameof(userProperty));
-
- if (_unsubscribeOptions.UserProperties is null)
- {
- _unsubscribeOptions.UserProperties = new List();
- }
-
- _unsubscribeOptions.UserProperties.Add(userProperty);
-
- return this;
+ return _unsubscribeOptions;
}
public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(string topic)
{
- if (topic is null) throw new ArgumentNullException(nameof(topic));
+ if (topic is null)
+ {
+ throw new ArgumentNullException(nameof(topic));
+ }
if (_unsubscribeOptions.TopicFilters is null)
{
@@ -63,14 +36,42 @@ namespace MQTTnet.Client
public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter)
{
- if (topicFilter is null) throw new ArgumentNullException(nameof(topicFilter));
+ if (topicFilter is null)
+ {
+ throw new ArgumentNullException(nameof(topicFilter));
+ }
return WithTopicFilter(topicFilter.Topic);
}
- public MqttClientUnsubscribeOptions Build()
+ ///
+ /// Adds the user property to the unsubscribe options.
+ /// MQTT 5.0.0+ feature.
+ ///
+ public MqttClientUnsubscribeOptionsBuilder WithUserProperty(string name, string value)
{
- return _unsubscribeOptions;
+ return WithUserProperty(new MqttUserProperty(name, value));
+ }
+
+ ///
+ /// Adds the user property to the unsubscribe options.
+ /// MQTT 5.0.0+ feature.
+ ///
+ public MqttClientUnsubscribeOptionsBuilder WithUserProperty(MqttUserProperty userProperty)
+ {
+ if (userProperty is null)
+ {
+ throw new ArgumentNullException(nameof(userProperty));
+ }
+
+ if (_unsubscribeOptions.UserProperties is null)
+ {
+ _unsubscribeOptions.UserProperties = new List();
+ }
+
+ _unsubscribeOptions.UserProperties.Add(userProperty);
+
+ return this;
}
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs b/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs
index 889bb977..1c72c039 100644
--- a/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs
+++ b/Source/MQTTnet/Formatter/MqttDisconnectPacketFactory.cs
@@ -39,6 +39,8 @@ namespace MQTTnet.Formatter
else
{
packet.ReasonCode = (MqttDisconnectReasonCode)clientDisconnectOptions.Reason;
+ packet.UserProperties = clientDisconnectOptions.UserProperties;
+ packet.SessionExpiryInterval = clientDisconnectOptions.SessionExpiryInterval;
}
return packet;
diff --git a/Source/MQTTnet/Implementations/CrossPlatformSocket.cs b/Source/MQTTnet/Implementations/CrossPlatformSocket.cs
index f60bdab6..d7ae1f04 100644
--- a/Source/MQTTnet/Implementations/CrossPlatformSocket.cs
+++ b/Source/MQTTnet/Implementations/CrossPlatformSocket.cs
@@ -182,7 +182,6 @@ namespace MQTTnet.Implementations
try
{
#if NET5_0_OR_GREATER
-
if (_networkStream != null)
{
await _networkStream.DisposeAsync().ConfigureAwait(false);
diff --git a/Source/MQTTnet/Internal/TaskExtensions.cs b/Source/MQTTnet/Internal/TaskExtensions.cs
index d4c8c4d7..a63b791a 100644
--- a/Source/MQTTnet/Internal/TaskExtensions.cs
+++ b/Source/MQTTnet/Internal/TaskExtensions.cs
@@ -2,8 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using MQTTnet.Diagnostics;
+using System;
using System.Threading.Tasks;
+using MQTTnet.Diagnostics;
namespace MQTTnet.Internal
{
@@ -11,7 +12,8 @@ namespace MQTTnet.Internal
{
public static void RunInBackground(this Task task, MqttNetSourceLogger logger = null)
{
- task?.ContinueWith(t =>
+ task?.ContinueWith(
+ t =>
{
// Consume the exception first so that we get no exception regarding the not observed exception.
var exception = t.Exception;
@@ -19,5 +21,41 @@ namespace MQTTnet.Internal
},
TaskContinuationOptions.OnlyOnFaulted);
}
+
+ public static async Task WaitAsync(this Task task, Task sender, MqttNetSourceLogger logger)
+ {
+ if (logger == null)
+ {
+ throw new ArgumentNullException(nameof(logger));
+ }
+
+ if (task == null)
+ {
+ return;
+ }
+
+ if (task == sender)
+ {
+ // Return here to avoid deadlocks, but first any eventual exception in the task
+ // must be handled to avoid not getting an unhandled task exception
+ if (!task.IsFaulted)
+ {
+ return;
+ }
+
+ // By accessing the Exception property the exception is considered handled and will
+ // not result in an unhandled task exception later by the finalizer
+ logger.Warning(task.Exception, "Error while waiting for background task.");
+ return;
+ }
+
+ try
+ {
+ await task.ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs
index 2f1674e3..16fbbaab 100644
--- a/Source/MQTTnet/MqttApplicationMessageBuilder.cs
+++ b/Source/MQTTnet/MqttApplicationMessageBuilder.cs
@@ -55,14 +55,8 @@ namespace MQTTnet
///
/// Adds the content type to the message.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
- ///
- /// A new instance of the
- ///
- /// class.
- ///
public MqttApplicationMessageBuilder WithContentType(string contentType)
{
_contentType = contentType;
@@ -71,18 +65,8 @@ namespace MQTTnet
///
/// Adds the correlation data to the message.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
- ///
- /// The correlation data.
- ///
- ///
- /// A new instance of the
- ///
- /// class.
- ///
public MqttApplicationMessageBuilder WithCorrelationData(byte[] correlationData)
{
_correlationData = correlationData;
@@ -91,18 +75,8 @@ namespace MQTTnet
///
/// Adds the message expiry interval in seconds to the message.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
- ///
- /// The message expiry interval.
- ///
- ///
- /// A new instance of the
- ///
- /// class.
- ///
public MqttApplicationMessageBuilder WithMessageExpiryInterval(uint messageExpiryInterval)
{
_messageExpiryInterval = messageExpiryInterval;
@@ -185,18 +159,8 @@ namespace MQTTnet
///
/// Adds the payload format indicator to the message.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
- ///
- /// The payload format indicator.
- ///
- ///
- /// A new instance of the
- ///
- /// class.
- ///
public MqttApplicationMessageBuilder WithPayloadFormatIndicator(MqttPayloadFormatIndicator payloadFormatIndicator)
{
_payloadFormatIndicator = payloadFormatIndicator;
@@ -220,18 +184,8 @@ namespace MQTTnet
///
/// Adds the response topic to the message.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
- ///
- /// The response topic.
- ///
- ///
- /// A new instance of the
- ///
- /// class.
- ///
public MqttApplicationMessageBuilder WithResponseTopic(string responseTopic)
{
_responseTopic = responseTopic;
@@ -251,18 +205,8 @@ namespace MQTTnet
///
/// Adds the subscription identifier to the message.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
- ///
- /// The subscription identifier.
- ///
- ///
- /// A new instance of the
- ///
- /// class.
- ///
public MqttApplicationMessageBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier)
{
if (_subscriptionIdentifiers == null)
@@ -289,18 +233,8 @@ namespace MQTTnet
///
/// Adds the topic alias to the message.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
- ///
- /// The topic alias.
- ///
- ///
- /// A new instance of the
- ///
- /// class.
- ///
public MqttApplicationMessageBuilder WithTopicAlias(ushort topicAlias)
{
_topicAlias = topicAlias;
@@ -309,22 +243,8 @@ namespace MQTTnet
///
/// Adds the user property to the message.
- /// Hint: MQTT 5 feature only.
+ /// MQTT 5.0.0+ feature.
///
- ///
- /// The property name.
- ///
- ///
- /// The property value.
- ///
- ///
- /// A new instance of the
- ///
- /// class.
- ///
public MqttApplicationMessageBuilder WithUserProperty(string name, string value)
{
if (_userProperties == null)
diff --git a/Source/MQTTnet/Server/Events/ClientDisconnectedEventArgs.cs b/Source/MQTTnet/Server/Events/ClientDisconnectedEventArgs.cs
index 20e2b2dc..bbc5d148 100644
--- a/Source/MQTTnet/Server/Events/ClientDisconnectedEventArgs.cs
+++ b/Source/MQTTnet/Server/Events/ClientDisconnectedEventArgs.cs
@@ -4,17 +4,30 @@
using System;
using System.Collections;
+using System.Collections.Generic;
+using MQTTnet.Packets;
+using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class ClientDisconnectedEventArgs : EventArgs
{
- public ClientDisconnectedEventArgs(string clientId, MqttClientDisconnectType disconnectType, string endpoint, IDictionary sessionItems)
+ readonly MqttDisconnectPacket _disconnectPacket;
+
+ public ClientDisconnectedEventArgs(
+ string clientId,
+ MqttDisconnectPacket disconnectPacket,
+ MqttClientDisconnectType disconnectType,
+ string endpoint,
+ IDictionary sessionItems)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
DisconnectType = disconnectType;
Endpoint = endpoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
+
+ // The DISCONNECT packet can be null in case of a non clean disconnect or session takeover.
+ _disconnectPacket = disconnectPacket;
}
///
@@ -27,9 +40,37 @@ namespace MQTTnet.Server
public string Endpoint { get; }
+ ///
+ /// Gets the reason code sent by the client.
+ /// Only available for clean disconnects.
+ /// MQTT 5.0.0+ feature.
+ ///
+ public MqttDisconnectReasonCode? ReasonCode => _disconnectPacket?.ReasonCode;
+
+ ///
+ /// Gets the reason string sent by the client.
+ /// Only available for clean disconnects.
+ /// MQTT 5.0.0+ feature.
+ ///
+ public string ReasonString => _disconnectPacket?.ReasonString;
+
+ ///
+ /// Gets the session expiry interval sent by the client.
+ /// Only available for clean disconnects.
+ /// MQTT 5.0.0+ feature.
+ ///
+ public uint SessionExpiryInterval => _disconnectPacket?.SessionExpiryInterval ?? 0;
+
///
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
///
public IDictionary SessionItems { get; }
+
+ ///
+ /// Gets the user properties sent by the client.
+ /// Only available for clean disconnects.
+ /// MQTT 5.0.0+ feature.
+ ///
+ public List UserProperties => _disconnectPacket?.UserProperties;
}
}
\ No newline at end of file
diff --git a/Source/MQTTnet/Server/Internal/MqttClient.cs b/Source/MQTTnet/Server/Internal/MqttClient.cs
index 4a09cd90..97895676 100644
--- a/Source/MQTTnet/Server/Internal/MqttClient.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClient.cs
@@ -57,12 +57,12 @@ namespace MQTTnet.Server
public IMqttChannelAdapter ChannelAdapter { get; }
+ public MqttDisconnectPacket DisconnectPacket { get; set; }
+
public string Endpoint { get; }
public string Id => _connectPacket.ClientId;
- public bool IsCleanDisconnect { get; private set; }
-
public bool IsRunning { get; private set; }
public bool IsTakenOver { get; set; }
@@ -95,8 +95,7 @@ namespace MQTTnet.Server
{
var cancellationToken = _cancellationToken.Token;
- _ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default)
- .ConfigureAwait(false);
+ _ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false);
IsRunning = true;
@@ -111,7 +110,9 @@ namespace MQTTnet.Server
_cancellationToken = null;
}
- if (!IsTakenOver && !IsCleanDisconnect && Session.LatestConnectPacket.WillFlag && !Session.WillMessageSent)
+ var isCleanDisconnect = DisconnectPacket != null;
+
+ if (!IsTakenOver && !isCleanDisconnect && Session.LatestConnectPacket.WillFlag && !Session.WillMessageSent)
{
var willPublishPacket = MqttPacketFactories.Publish.Create(Session.LatestConnectPacket);
var willApplicationMessage = MqttApplicationMessageFactory.Create(willPublishPacket);
@@ -431,9 +432,9 @@ namespace MQTTnet.Server
{
throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only.");
}
- else if (currentPacket is MqttDisconnectPacket)
+ else if (currentPacket is MqttDisconnectPacket disconnectPacket)
{
- IsCleanDisconnect = true;
+ DisconnectPacket = disconnectPacket;
return;
}
else
diff --git a/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
index a21b6926..b76380ab 100644
--- a/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
+++ b/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs
@@ -387,8 +387,8 @@ namespace MQTTnet.Server
if (client.Id != null && !client.IsTakenOver && _eventContainer.ClientDisconnectedEvent.HasHandlers)
{
- var disconnectType = client.IsCleanDisconnect ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean;
- var eventArgs = new ClientDisconnectedEventArgs(client.Id, disconnectType, endpoint, client.Session.Items);
+ var disconnectType = client.DisconnectPacket != null ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean;
+ var eventArgs = new ClientDisconnectedEventArgs(client.Id, client.DisconnectPacket, disconnectType, endpoint, client.Session.Items);
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
@@ -592,8 +592,13 @@ namespace MQTTnet.Server
if (_eventContainer.ClientDisconnectedEvent.HasHandlers)
{
- var eventArgs = new ClientDisconnectedEventArgs(oldClient.Id, MqttClientDisconnectType.Takeover, oldClient.Endpoint, oldClient.Session.Items);
-
+ var eventArgs = new ClientDisconnectedEventArgs(
+ oldClient.Id,
+ null,
+ MqttClientDisconnectType.Takeover,
+ oldClient.Endpoint,
+ oldClient.Session.Items);
+
await _eventContainer.ClientDisconnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
}
}
From 9f7d15e1b1f085761d98f110f69cf29babecef82 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Fri, 23 Dec 2022 17:13:25 +0100
Subject: [PATCH 42/48] Update ReleaseNotes.md
---
.github/workflows/ReleaseNotes.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index 1a651a56..8ca95868 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -2,3 +2,4 @@
* [Client] An internal exception is no longer caught silently when calling _DisconnectAsync_ to indicate that the disconnect is not clean (BREAKING CHANGE).
* [Client] MQTTv5 features are now checked and an exception is thrown if they are used when using protocol version 3.1.1 and lower. These checks can be disabled in client options. (BREAKING CHANGE!).
* [Server] Exposed MQTT v5 sent properties from the affected client in _ClientDisconnectedAsync_ event.
+* [Server] Fixed wrong client ID passed to _InterceptingUnsubscriptionEventArgs_ (#1631, thanks to @ghord).
From 2fa7200fb3da16791f14808ef898a627a19a2c87 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Fri, 23 Dec 2022 17:15:49 +0100
Subject: [PATCH 43/48] 1544 add underlying socketoption of tcp keepalive into
the server option (#1606)
* Expose TCP keep alive options to options builder
* Update ReleaseNotes.md
---
.github/workflows/ReleaseNotes.md | 1 +
.../Implementations/CrossPlatformSocket.cs | 50 ++--
.../Implementations/MqttTcpServerListener.cs | 20 ++
.../Options/MqttServerOptionsBuilder.cs | 229 +++++++++---------
.../MqttServerTcpEndpointBaseOptions.cs | 24 ++
5 files changed, 185 insertions(+), 139 deletions(-)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index 8ca95868..fcc18ffa 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -3,3 +3,4 @@
* [Client] MQTTv5 features are now checked and an exception is thrown if they are used when using protocol version 3.1.1 and lower. These checks can be disabled in client options. (BREAKING CHANGE!).
* [Server] Exposed MQTT v5 sent properties from the affected client in _ClientDisconnectedAsync_ event.
* [Server] Fixed wrong client ID passed to _InterceptingUnsubscriptionEventArgs_ (#1631, thanks to @ghord).
+* [Server] Exposed socket settings for TCP keep alive in TCP options (#1544).
diff --git a/Source/MQTTnet/Implementations/CrossPlatformSocket.cs b/Source/MQTTnet/Implementations/CrossPlatformSocket.cs
index d7ae1f04..ea642431 100644
--- a/Source/MQTTnet/Implementations/CrossPlatformSocket.cs
+++ b/Source/MQTTnet/Implementations/CrossPlatformSocket.cs
@@ -72,8 +72,8 @@ namespace MQTTnet.Implementations
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, value);
#else
- get { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
- set { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
+ get { throw new NotSupportedException("TcpKeepAliveInterval requires at least netcoreapp3.0."); }
+ set { throw new NotSupportedException("TcpKeepAliveInterval requires at least netcoreapp3.0."); }
#endif
}
@@ -83,8 +83,8 @@ namespace MQTTnet.Implementations
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, value);
#else
- get { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
- set { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
+ get { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least netcoreapp3.0."); }
+ set { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least netcoreapp3.0."); }
#endif
}
@@ -94,8 +94,8 @@ namespace MQTTnet.Implementations
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, value);
#else
- get { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
- set { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
+ get { throw new NotSupportedException("TcpKeepAliveTime requires at least netcoreapp3.0."); }
+ set { throw new NotSupportedException("TcpKeepAliveTime requires at least netcoreapp3.0."); }
#endif
}
@@ -155,7 +155,7 @@ namespace MQTTnet.Implementations
}
catch (ObjectDisposedException)
{
- // This will happen when _socket.EndAccept gets called by Task library but the socket is already disposed.
+ // This will happen when _socket.EndAccept_ gets called by Task library but the socket is already disposed.
return null;
}
}
@@ -181,16 +181,18 @@ namespace MQTTnet.Implementations
try
{
-#if NET5_0_OR_GREATER
+#if NETCOREAPP3_0_OR_GREATER
if (_networkStream != null)
{
await _networkStream.DisposeAsync().ConfigureAwait(false);
}
-
- await _socket.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
#else
_networkStream?.Dispose();
+#endif
+#if NET5_0_OR_GREATER
+ await _socket.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
+#else
// Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(_socketDisposeAction))
{
@@ -219,7 +221,7 @@ namespace MQTTnet.Implementations
}
catch (ObjectDisposedException)
{
- // This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
+ // This will happen when _socket.EndConnect_ gets called by Task library but the socket is already disposed.
cancellationToken.ThrowIfCancellationRequested();
}
}
@@ -246,38 +248,44 @@ namespace MQTTnet.Implementations
_socket.Listen(connectionBacklog);
}
+#if NET452 || NET461
public async Task ReceiveAsync(ArraySegment buffer, SocketFlags socketFlags)
{
try
{
-#if NET452 || NET461
return await Task.Factory.FromAsync(SocketWrapper.BeginReceive, _socket.EndReceive, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
-#else
- return await _socket.ReceiveAsync(buffer, socketFlags).ConfigureAwait(false);
-#endif
}
catch (ObjectDisposedException)
{
- // This will happen when _socket.EndReceive gets called by Task library but the socket is already disposed.
+ // This will happen when _socket.EndReceive_ gets called by Task library but the socket is already disposed.
return -1;
}
}
+#else
+ public Task ReceiveAsync(ArraySegment buffer, SocketFlags socketFlags)
+ {
+ return _socket.ReceiveAsync(buffer, socketFlags);
+ }
+#endif
+#if NET452 || NET461
public async Task SendAsync(ArraySegment buffer, SocketFlags socketFlags)
{
try
{
-#if NET452 || NET461
await Task.Factory.FromAsync(SocketWrapper.BeginSend, _socket.EndSend, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
-#else
- await _socket.SendAsync(buffer, socketFlags).ConfigureAwait(false);
-#endif
}
catch (ObjectDisposedException)
{
- // This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
+ // This will happen when _socket.EndSend_ gets called by Task library but the socket is already disposed.
}
}
+#else
+ public Task SendAsync(ArraySegment buffer, SocketFlags socketFlags)
+ {
+ return _socket.SendAsync(buffer, socketFlags);
+ }
+#endif
#if NET452 || NET461
sealed class SocketWrapper
diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs
index 9341d275..ac9a521f 100644
--- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs
+++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs
@@ -86,6 +86,26 @@ namespace MQTTnet.Implementations
_socket.LingerState = _options.LingerState;
}
+ if (_options.KeepAlive.HasValue)
+ {
+ _socket.KeepAlive = _options.KeepAlive.Value;
+ }
+
+ if (_options.TcpKeepAliveInterval.HasValue)
+ {
+ _socket.TcpKeepAliveInterval = _options.TcpKeepAliveInterval.Value;
+ }
+
+ if (_options.TcpKeepAliveRetryCount.HasValue)
+ {
+ _socket.TcpKeepAliveInterval = _options.TcpKeepAliveRetryCount.Value;
+ }
+
+ if (_options.TcpKeepAliveTime.HasValue)
+ {
+ _socket.TcpKeepAliveTime = _options.TcpKeepAliveTime.Value;
+ }
+
_socket.Bind(_localEndPoint);
// Get the local endpoint back from the socket. The port may have changed.
diff --git a/Source/MQTTnet/Server/Options/MqttServerOptionsBuilder.cs b/Source/MQTTnet/Server/Options/MqttServerOptionsBuilder.cs
index b6a50cca..3b359203 100644
--- a/Source/MQTTnet/Server/Options/MqttServerOptionsBuilder.cs
+++ b/Source/MQTTnet/Server/Options/MqttServerOptionsBuilder.cs
@@ -7,8 +7,6 @@ using System.Net;
using System.Net.Security;
using System.Security.Authentication;
using MQTTnet.Certificates;
-using System.Threading.Tasks;
-
#if !WINDOWS_UWP
using System.Security.Cryptography.X509Certificates;
#endif
@@ -19,7 +17,22 @@ namespace MQTTnet.Server
public class MqttServerOptionsBuilder
{
readonly MqttServerOptions _options = new MqttServerOptions();
-
+
+ public MqttServerOptions Build()
+ {
+ return _options;
+ }
+
+#if !WINDOWS_UWP
+ public MqttServerOptionsBuilder WithClientCertificate(RemoteCertificateValidationCallback validationCallback = null, bool checkCertificateRevocation = false)
+ {
+ _options.TlsEndpointOptions.ClientCertificateRequired = true;
+ _options.TlsEndpointOptions.CheckCertificateRevocation = checkCertificateRevocation;
+ _options.TlsEndpointOptions.RemoteCertificateValidationCallback = validationCallback;
+ return this;
+ }
+#endif
+
public MqttServerOptionsBuilder WithConnectionBacklog(int value)
{
_options.DefaultEndpointOptions.ConnectionBacklog = value;
@@ -27,12 +40,6 @@ namespace MQTTnet.Server
return this;
}
- public MqttServerOptionsBuilder WithMaxPendingMessagesPerClient(int value)
- {
- _options.MaxPendingMessagesPerClient = value;
- return this;
- }
-
public MqttServerOptionsBuilder WithDefaultCommunicationTimeout(TimeSpan value)
{
_options.DefaultCommunicationTimeout = value;
@@ -45,12 +52,6 @@ namespace MQTTnet.Server
return this;
}
- public MqttServerOptionsBuilder WithDefaultEndpointPort(int value)
- {
- _options.DefaultEndpointOptions.Port = value;
- return this;
- }
-
public MqttServerOptionsBuilder WithDefaultEndpointBoundIPAddress(IPAddress value)
{
_options.DefaultEndpointOptions.BoundInterNetworkAddress = value ?? IPAddress.Any;
@@ -63,9 +64,15 @@ namespace MQTTnet.Server
return this;
}
- public MqttServerOptionsBuilder WithoutDefaultEndpoint()
+ public MqttServerOptionsBuilder WithDefaultEndpointPort(int value)
{
- _options.DefaultEndpointOptions.IsEnabled = false;
+ _options.DefaultEndpointOptions.Port = value;
+ return this;
+ }
+
+ public MqttServerOptionsBuilder WithDefaultEndpointReuseAddress()
+ {
+ _options.DefaultEndpointOptions.ReuseAddress = true;
return this;
}
@@ -75,12 +82,6 @@ namespace MQTTnet.Server
return this;
}
- public MqttServerOptionsBuilder WithEncryptedEndpointPort(int value)
- {
- _options.TlsEndpointOptions.Port = value;
- return this;
- }
-
public MqttServerOptionsBuilder WithEncryptedEndpointBoundIPAddress(IPAddress value)
{
_options.TlsEndpointOptions.BoundInterNetworkAddress = value;
@@ -93,10 +94,91 @@ namespace MQTTnet.Server
return this;
}
+ public MqttServerOptionsBuilder WithEncryptedEndpointPort(int value)
+ {
+ _options.TlsEndpointOptions.Port = value;
+ return this;
+ }
+
+ public MqttServerOptionsBuilder WithEncryptionSslProtocol(SslProtocols value)
+ {
+ _options.TlsEndpointOptions.SslProtocol = value;
+ return this;
+ }
+
+ public MqttServerOptionsBuilder WithKeepAlive()
+ {
+ _options.DefaultEndpointOptions.KeepAlive = true;
+ _options.TlsEndpointOptions.KeepAlive = true;
+ return this;
+ }
+
+ public MqttServerOptionsBuilder WithMaxPendingMessagesPerClient(int value)
+ {
+ _options.MaxPendingMessagesPerClient = value;
+ return this;
+ }
+
+ public MqttServerOptionsBuilder WithoutDefaultEndpoint()
+ {
+ _options.DefaultEndpointOptions.IsEnabled = false;
+ return this;
+ }
+
+ public MqttServerOptionsBuilder WithoutEncryptedEndpoint()
+ {
+ _options.TlsEndpointOptions.IsEnabled = false;
+ return this;
+ }
+
+ public MqttServerOptionsBuilder WithPersistentSessions(bool value = true)
+ {
+ _options.EnablePersistentSessions = value;
+ return this;
+ }
+
+#if !WINDOWS_UWP
+ public MqttServerOptionsBuilder WithRemoteCertificateValidationCallback(RemoteCertificateValidationCallback value)
+ {
+ _options.TlsEndpointOptions.RemoteCertificateValidationCallback = value;
+ return this;
+ }
+#endif
+
+ public MqttServerOptionsBuilder WithTcpKeepAliveInterval(int value)
+ {
+ _options.DefaultEndpointOptions.TcpKeepAliveInterval = value;
+ _options.TlsEndpointOptions.TcpKeepAliveInterval = value;
+ return this;
+ }
+
+ public MqttServerOptionsBuilder WithTcpKeepAliveRetryCount(int value)
+ {
+ _options.DefaultEndpointOptions.TcpKeepAliveRetryCount = value;
+ _options.TlsEndpointOptions.TcpKeepAliveRetryCount = value;
+ return this;
+ }
+
+ public MqttServerOptionsBuilder WithTcpKeepAliveTime(int value)
+ {
+ _options.DefaultEndpointOptions.TcpKeepAliveTime = value;
+ _options.TlsEndpointOptions.TcpKeepAliveTime = value;
+ return this;
+ }
+
+ public MqttServerOptionsBuilder WithTlsEndpointReuseAddress()
+ {
+ _options.TlsEndpointOptions.ReuseAddress = true;
+ return this;
+ }
+
#if !WINDOWS_UWP
public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value, IMqttServerCertificateCredentials credentials = null)
{
- if (value == null) throw new ArgumentNullException(nameof(value));
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
_options.TlsEndpointOptions.CertificateProvider = new BlobCertificateProvider(value)
{
@@ -108,103 +190,14 @@ namespace MQTTnet.Server
public MqttServerOptionsBuilder WithEncryptionCertificate(X509Certificate2 certificate)
{
- if (certificate == null) throw new ArgumentNullException(nameof(certificate));
+ if (certificate == null)
+ {
+ throw new ArgumentNullException(nameof(certificate));
+ }
_options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate);
return this;
}
#endif
-
- public MqttServerOptionsBuilder WithEncryptionSslProtocol(SslProtocols value)
- {
- _options.TlsEndpointOptions.SslProtocol = value;
- return this;
- }
-
-#if !WINDOWS_UWP
- public MqttServerOptionsBuilder WithClientCertificate(RemoteCertificateValidationCallback validationCallback = null, bool checkCertificateRevocation = false)
- {
- _options.TlsEndpointOptions.ClientCertificateRequired = true;
- _options.TlsEndpointOptions.CheckCertificateRevocation = checkCertificateRevocation;
- _options.TlsEndpointOptions.RemoteCertificateValidationCallback = validationCallback;
- return this;
- }
-#endif
-
- public MqttServerOptionsBuilder WithoutEncryptedEndpoint()
- {
- _options.TlsEndpointOptions.IsEnabled = false;
- return this;
- }
-
-#if !WINDOWS_UWP
- public MqttServerOptionsBuilder WithRemoteCertificateValidationCallback(RemoteCertificateValidationCallback value)
- {
- _options.TlsEndpointOptions.RemoteCertificateValidationCallback = value;
- return this;
- }
-#endif
-
- // public MqttServerOptionsBuilder WithApplicationMessageInterceptor(IMqttServerApplicationMessageInterceptor value)
- // {
- // _options.ApplicationMessageInterceptor = value;
- // return this;
- // }
- //
- // public MqttServerOptionsBuilder WithApplicationMessageInterceptor(Action value)
- // {
- // _options.ApplicationMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(value);
- // return this;
- // }
- //
- // public MqttServerOptionsBuilder WithApplicationMessageInterceptor(Func value)
- // {
- // _options.ApplicationMessageInterceptor = new MqttServerApplicationMessageInterceptorDelegate(value);
- // return this;
- // }
-
- // public MqttServerOptionsBuilder WithMultiThreadedApplicationMessageInterceptor(Action value)
- // {
- // _options.ApplicationMessageInterceptor = new MqttServerMultiThreadedApplicationMessageInterceptorDelegate(value);
- // return this;
- // }
- //
- // public MqttServerOptionsBuilder WithMultiThreadedApplicationMessageInterceptor(Func value)
- // {
- // _options.ApplicationMessageInterceptor = new MqttServerMultiThreadedApplicationMessageInterceptorDelegate(value);
- // return this;
- // }
-
- public MqttServerOptionsBuilder WithDefaultEndpointReuseAddress()
- {
- _options.DefaultEndpointOptions.ReuseAddress = true;
- return this;
- }
-
- public MqttServerOptionsBuilder WithTlsEndpointReuseAddress()
- {
- _options.TlsEndpointOptions.ReuseAddress = true;
- return this;
- }
-
- public MqttServerOptionsBuilder WithPersistentSessions(bool value = true)
- {
- _options.EnablePersistentSessions = value;
- return this;
- }
-
- // ///
- // /// Gets or sets the client ID which is used when publishing messages from the server directly.
- // ///
- // public MqttServerOptionsBuilder WithClientId(string value)
- // {
- // _options.ClientId = value;
- // return this;
- // }
-
- public MqttServerOptions Build()
- {
- return _options;
- }
}
-}
+}
\ No newline at end of file
diff --git a/Source/MQTTnet/Server/Options/MqttServerTcpEndpointBaseOptions.cs b/Source/MQTTnet/Server/Options/MqttServerTcpEndpointBaseOptions.cs
index feab55bb..1fbdb26b 100644
--- a/Source/MQTTnet/Server/Options/MqttServerTcpEndpointBaseOptions.cs
+++ b/Source/MQTTnet/Server/Options/MqttServerTcpEndpointBaseOptions.cs
@@ -17,6 +17,30 @@ namespace MQTTnet.Server
public bool NoDelay { get; set; } = true;
+ ///
+ /// Gets or sets whether the sockets keep alive feature should be used.
+ /// The value _null_ indicates that the OS and framework defaults should be used.
+ ///
+ public bool? KeepAlive { get; set; }
+
+ ///
+ /// Gets or sets the TCP keep alive interval.
+ /// The value _null_ indicates that the OS and framework defaults should be used.
+ ///
+ public int? TcpKeepAliveInterval { get; set; }
+
+ ///
+ /// Gets or sets the TCP keep alive retry count.
+ /// The value _null_ indicates that the OS and framework defaults should be used.
+ ///
+ public int? TcpKeepAliveRetryCount { get; set; }
+
+ ///
+ /// Gets or sets the TCP keep alive time.
+ /// The value _null_ indicates that the OS and framework defaults should be used.
+ ///
+ public int? TcpKeepAliveTime { get; set; }
+
public LingerOption LingerState { get; set; } = new LingerOption(true, 0);
#if WINDOWS_UWP
From d74c81c3d12c182640ce47c3a7b2e29832411a8b Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Fri, 23 Dec 2022 20:01:13 +0100
Subject: [PATCH 44/48] Fix unit tests
---
.../Clients/MqttClient/MqttClient_Connection_Tests.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs
index e2ce36cf..84502e2e 100644
--- a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs
+++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Connection_Tests.cs
@@ -81,7 +81,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
[TestMethod]
public async Task Disconnect_Clean()
{
- using (var testEnvironment = CreateTestEnvironment())
+ using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
From e0ce1f309ef58c3c959aabb1bdc1e7e8678dcb3c Mon Sep 17 00:00:00 2001
From: Jeffery Michael <37258233+YAJeff@users.noreply.github.com>
Date: Wed, 28 Dec 2022 00:20:11 -0800
Subject: [PATCH 45/48] dotnet 7 packages (#1636)
* Added dotnet 7 support/builds.
* Added NETCOREAPP_3_1_OR_GREATER where applicable.
---
Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs | 4 ++--
Source/MQTTnet.AspnetCore/EndpointRouterExtensions.cs | 2 +-
Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj | 8 ++++----
Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj | 6 +++++-
.../MQTTnet.Extensions.ManagedClient.csproj | 2 +-
.../MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj | 2 +-
.../MQTTnet.Extensions.WebSocket4Net.csproj | 2 +-
Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj | 4 ++--
Source/MQTTnet.Tests/MQTTnet.Tests.csproj | 4 ++--
Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs | 2 +-
.../Options/MqttClientOptionsBuilderTlsParameters.cs | 4 ++--
Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs | 4 ++--
.../Diagnostics/Runtime/TargetFrameworkProvider.cs | 2 ++
Source/MQTTnet/Implementations/MqttTcpChannel.cs | 4 ++--
Source/MQTTnet/Implementations/MqttTcpServerListener.cs | 2 +-
Source/MQTTnet/MQTTnet.csproj | 2 +-
.../Server/Options/MqttServerTlsTcpEndpointOptions.cs | 4 ++--
17 files changed, 32 insertions(+), 26 deletions(-)
diff --git a/Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs b/Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs
index 845a67de..c3a6227a 100644
--- a/Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs
+++ b/Source/MQTTnet.AspnetCore/Client/Tcp/TcpConnection.cs
@@ -44,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
}
-#if NETCOREAPP3_1 || NET5_0_OR_GREATER
+#if NETCOREAPP3_1_OR_GREATER
public override ValueTask DisposeAsync()
#else
public Task DisposeAsync()
@@ -57,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_socket?.Dispose();
-#if NETCOREAPP3_1 || NET5_0_OR_GREATER
+#if NETCOREAPP3_1_OR_GREATER
return base.DisposeAsync();
}
diff --git a/Source/MQTTnet.AspnetCore/EndpointRouterExtensions.cs b/Source/MQTTnet.AspnetCore/EndpointRouterExtensions.cs
index f5c72419..5c49cd4c 100644
--- a/Source/MQTTnet.AspnetCore/EndpointRouterExtensions.cs
+++ b/Source/MQTTnet.AspnetCore/EndpointRouterExtensions.cs
@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
-#if NETCOREAPP3_1 || NET5_0_OR_GREATER
+#if NETCOREAPP3_1_OR_GREATER
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
diff --git a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
index 1745b38d..6cd13d23 100644
--- a/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
+++ b/Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj
@@ -1,7 +1,7 @@
-
+
- netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0
+ netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0
MQTTnet.AspNetCore
MQTTnet.AspNetCore
@@ -51,11 +51,11 @@
RELEASE;NETSTANDARD2_0
-
+
-
+
diff --git a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
index ad9a1f03..15c6e2b5 100644
--- a/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
+++ b/Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj
@@ -4,7 +4,7 @@
true
Exe
Full
- net6.0
+ net6.0;net7.0
7.2
false
false
@@ -26,6 +26,10 @@
+
+
+
+
diff --git a/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj b/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj
index 45f5464f..7723c486 100644
--- a/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj
+++ b/Source/MQTTnet.Extensions.ManagedClient/MQTTnet.Extensions.ManagedClient.csproj
@@ -1,7 +1,7 @@
- netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0
+ netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0
$(TargetFrameworks);net452;net461
$(TargetFrameworks);uap10.0
diff --git a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
index d2098e10..42e2a07c 100644
--- a/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
+++ b/Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj
@@ -1,7 +1,7 @@
- netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0
+ netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0
$(TargetFrameworks);net452;net461
$(TargetFrameworks);uap10.0
diff --git a/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj b/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj
index d78358aa..080ae1f8 100644
--- a/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj
+++ b/Source/MQTTnet.Extensions.WebSocket4Net/MQTTnet.Extensions.WebSocket4Net.csproj
@@ -1,7 +1,7 @@
- netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0
+ netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0
$(TargetFrameworks);net452;net461
$(TargetFrameworks);uap10.0
diff --git a/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj b/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj
index 6272084b..ef336f45 100644
--- a/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj
+++ b/Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj
@@ -1,9 +1,9 @@
-
+
Exe
Full
- net5.0;net6.0
+ net5.0;net6.0;net7.0
$(TargetFrameworks);net452;net461
false
false
diff --git a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj
index 8b56b6cb..36720c6b 100644
--- a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj
+++ b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj
@@ -1,7 +1,7 @@
-
+
- net452;net461;net48;netcoreapp3.1;net6.0
+ net452;net461;net48;netcoreapp3.1;net6.0;net7.0
false
7.3
false
diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
index 029f5a08..86ac9eec 100644
--- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs
@@ -46,7 +46,7 @@ namespace MQTTnet.Client
Certificates = _tlsParameters.Certificates?.ToList(),
#endif
-#if NETCOREAPP3_1 || NET5_0_OR_GREATER
+#if NETCOREAPP3_1_OR_GREATER
ApplicationProtocols = _tlsParameters.ApplicationProtocols,
#endif
};
diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs
index 3304c65e..5f5f7ce7 100644
--- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilderTlsParameters.cs
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
public Func CertificateValidationHandler { get; set; }
-#if NET48 || NETCOREAPP3_1 || NET5 || NET6
+#if NET48 || NETCOREAPP3_1_OR_GREATER
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
#else
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
@@ -27,7 +27,7 @@ namespace MQTTnet.Client
public IEnumerable Certificates { get; set; }
#endif
-#if NETCOREAPP3_1 || NET5_0_OR_GREATER
+#if NETCOREAPP3_1_OR_GREATER
public List ApplicationProtocols { get; set; }
#endif
diff --git a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs
index d33a3291..9939d0a1 100644
--- a/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs
+++ b/Source/MQTTnet/Client/Options/MqttClientTlsOptions.cs
@@ -29,13 +29,13 @@ namespace MQTTnet.Client
public List Certificates { get; set; }
#endif
-#if NETCOREAPP3_1 || NET5_0_OR_GREATER
+#if NETCOREAPP3_1_OR_GREATER
public List ApplicationProtocols { get; set; }
public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; }
#endif
-#if NET48 || NETCOREAPP3_1 || NET5 || NET6
+#if NET48 || NETCOREAPP3_1_OR_GREATER
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
#else
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
diff --git a/Source/MQTTnet/Diagnostics/Runtime/TargetFrameworkProvider.cs b/Source/MQTTnet/Diagnostics/Runtime/TargetFrameworkProvider.cs
index 046c3dc2..fd259587 100644
--- a/Source/MQTTnet/Diagnostics/Runtime/TargetFrameworkProvider.cs
+++ b/Source/MQTTnet/Diagnostics/Runtime/TargetFrameworkProvider.cs
@@ -30,6 +30,8 @@ namespace MQTTnet.Diagnostics
return "net5.0";
#elif NET6_0
return "net6.0";
+#elif NET7_0
+ return "net7.0";
#endif
}
}
diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs
index c1e0b316..1d700384 100644
--- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs
+++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs
@@ -103,7 +103,7 @@ namespace MQTTnet.Implementations
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
try
{
-#if NETCOREAPP3_1 || NET5_0_OR_GREATER
+#if NETCOREAPP3_1_OR_GREATER
var sslOptions = new SslClientAuthenticationOptions
{
ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols,
@@ -127,7 +127,7 @@ namespace MQTTnet.Implementations
}
catch
{
-#if NETSTANDARD2_1 || NETCOREAPP3_1 || NET5_0_OR_GREATER
+#if NETSTANDARD2_1 || NETCOREAPP3_1_OR_GREATER
await sslStream.DisposeAsync().ConfigureAwait(false);
#else
sslStream.Dispose();
diff --git a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs
index ac9a521f..02b954ba 100644
--- a/Source/MQTTnet/Implementations/MqttTcpServerListener.cs
+++ b/Source/MQTTnet/Implementations/MqttTcpServerListener.cs
@@ -198,7 +198,7 @@ namespace MQTTnet.Implementations
{
var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback);
- #if NETCOREAPP3_1 || NET5_0_OR_GREATER
+ #if NETCOREAPP3_1_OR_GREATER
await sslStream.AuthenticateAsServerAsync(
new SslServerAuthenticationOptions()
{
diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj
index 945a8693..12044cfe 100644
--- a/Source/MQTTnet/MQTTnet.csproj
+++ b/Source/MQTTnet/MQTTnet.csproj
@@ -10,7 +10,7 @@
- netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0
+ netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0
$(TargetFrameworks);net452;net461
$(TargetFrameworks);uap10.0
7.3
diff --git a/Source/MQTTnet/Server/Options/MqttServerTlsTcpEndpointOptions.cs b/Source/MQTTnet/Server/Options/MqttServerTlsTcpEndpointOptions.cs
index f24881c0..903dd7aa 100644
--- a/Source/MQTTnet/Server/Options/MqttServerTlsTcpEndpointOptions.cs
+++ b/Source/MQTTnet/Server/Options/MqttServerTlsTcpEndpointOptions.cs
@@ -24,8 +24,8 @@ namespace MQTTnet.Server
public bool CheckCertificateRevocation { get; set; }
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
-
-#if NETCOREAPP3_1 || NET5_0_OR_GREATER
+
+#if NETCOREAPP3_1_OR_GREATER
public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; }
#endif
}
From aeb07e880e41b2e162170234d78cd07560bb5e09 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Wed, 28 Dec 2022 09:25:33 +0100
Subject: [PATCH 46/48] Update ReleaseNotes.md
---
.github/workflows/ReleaseNotes.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/ReleaseNotes.md b/.github/workflows/ReleaseNotes.md
index fcc18ffa..fe28bf6d 100644
--- a/.github/workflows/ReleaseNotes.md
+++ b/.github/workflows/ReleaseNotes.md
@@ -1,3 +1,4 @@
+* [Core] Added net7.0 builds (#1635, thanks to @YAJeff).
* [Client] Added support for passing MQTT v5 options (User properties etc.) for disconnects.
* [Client] An internal exception is no longer caught silently when calling _DisconnectAsync_ to indicate that the disconnect is not clean (BREAKING CHANGE).
* [Client] MQTTv5 features are now checked and an exception is thrown if they are used when using protocol version 3.1.1 and lower. These checks can be disabled in client options. (BREAKING CHANGE!).
From a93e935585f102214f7d2288ba11e4c8713b4d39 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Wed, 28 Dec 2022 09:37:01 +0100
Subject: [PATCH 47/48] Update ci.yml
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7d45e9ea..2b230293 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,7 +21,7 @@ jobs:
with:
dotnet-version: |
3.1.x
- 6.0.x
+ 7.0.x
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1
From ead58dd606c8e02a26e9807207d057a35f493992 Mon Sep 17 00:00:00 2001
From: Christian <6939810+chkr1011@users.noreply.github.com>
Date: Wed, 28 Dec 2022 13:59:05 +0100
Subject: [PATCH 48/48] Update ci.yml
---
.github/workflows/ci.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2b230293..0597dcdc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,6 +21,7 @@ jobs:
with:
dotnet-version: |
3.1.x
+ 6.0.x
7.0.x
- name: Setup MSBuild