Compare commits

...
34 Commits
Author SHA1 Message Date
Christian cb3c44f48d Rename InspectPackage method (#1713) 2023-04-23 13:57:59 +02:00
Christian 8b3b9d33de Add overloads for options builder (#1711)
* Add new overload for options builder

* Refactor code

* Update ReleaseNotes.md
2023-04-11 22:11:16 +02:00
scottbrogden-iheartmediaandChristian 59d99509c8 Notify what subscription topics failed as part of the SynchronizingSu… (#1678)
* Notify what subscription topics failed as part of the SynchronizingSubscriptionsFailedAsync callback

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-04-08 14:03:58 +02:00
RossHNPCandChristian 2933b76237 ClientMessageQueueInterceptor continues with subscription processing. (#1685)
* ClientMessageQueueInterceptor continues with subscription processing.

* Update readme

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-04-06 17:55:33 +02:00
ChristianandMichi 36c6d8974e Allow disabling of packet fragmentation (#1709)
* Add compatibility option for AWS

* Fix UWP build

* Fix comment in MqttClientOptions (#1706)

* Refactor naming

* Fix wrong option value

---------

Co-authored-by: Michi <michael@volz.dev>
2023-04-06 17:54:36 +02:00
Christian d6cab76728 Add user properties to event args to interceptors for subscribing and unsubscribing (#1707)
Add user properties to event args
2023-04-06 17:31:42 +02:00
Christian b4f740f338 Update readme 2023-04-05 22:12:58 +02:00
Christian a004de25ac Fix unit test 2023-02-22 21:46:33 +01:00
老九 d0584413ca Add PayloadSegmentable feature (#1585) 2023-02-22 20:53:55 +01:00
Christian ba57537108 Fix running state handling in server (#1680)
Fix running state handling
2023-02-17 21:17:31 +01:00
Christian 6dcb110737 Add InterceptingClientEnqueueAsync event (#1679) 2023-02-17 19:13:06 +01:00
Christian b599a832da Add new method for reading a single retained message (#1676) 2023-02-15 19:46:37 +01:00
Christian 9cbd5dd778 Update ReleaseNotes.md 2023-01-28 13:08:24 +01:00
Christian ed31c51237 Code cleanup 2023-01-28 13:08:21 +01:00
Christian e01707d25e Improve samples 2023-01-28 13:08:08 +01:00
Jeffery MichaelandJeff Scherrer 73f118fe1c Moved the usage of certificate provider into the server listener (#1652)
* Moved the usage of certificate provider into the server listener so that each connection has the posibility of receiving a new SSL cert.

* Removed warning from unused field.

* Reduced changeds on original codebase.

---------

Co-authored-by: Jeff Scherrer <Jeff.Scherrer@vw.com>
2023-01-28 12:18:31 +01:00
Int32Overflow 0234918f30 Performance impact fix (#1661) 2023-01-28 12:12:20 +01:00
Jeffery Michael c90ba7e061 Fixed broken UWP build (#1655)
Added compiler declaritive to check for WINDOWS_UWP on IsAssignableFrom call.
2023-01-28 12:02:22 +01:00
Christian 2aa4219838 Expose server session items (#1638)
* Expose server session items

* Add support for custom session items for injected application messages
2023-01-19 20:15:15 +01:00
Günther Foidl b98417ab39 Avoid some full memory barriers (#1590)
* Avoid some full memory barriers

* Reset in stats itself

* Updated MqttClientStatistics

* Fixed order of members as artifact from merge-conflict
2023-01-19 20:08:51 +01:00
Christian ead58dd606 Update ci.yml 2022-12-28 13:59:05 +01:00
Christian a93e935585 Update ci.yml 2022-12-28 09:37:01 +01:00
Christian aeb07e880e Update ReleaseNotes.md 2022-12-28 09:25:33 +01:00
Jeffery Michael e0ce1f309e dotnet 7 packages (#1636)
* Added dotnet 7 support/builds.

* Added NETCOREAPP_3_1_OR_GREATER where applicable.
2022-12-28 09:20:11 +01:00
Christian d74c81c3d1 Fix unit tests 2022-12-23 20:01:13 +01:00
Christian 2fa7200fb3 1544 add underlying socketoption of tcp keepalive into the server option (#1606)
* Expose TCP keep alive options to options builder

* Update ReleaseNotes.md
2022-12-23 17:15:49 +01:00
Christian 9f7d15e1b1 Update ReleaseNotes.md 2022-12-23 17:13:25 +01:00
Christian fdf00f1b18 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
2022-12-23 17:09:02 +01:00
ghord abe7bbda15 Pass correct clientId to Unsubscribe interceptor (#1631) 2022-12-23 17:02:03 +01:00
ChristianandHansM 795139d78f 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 <HansM2013@gmx.de>
2022-12-23 16:56:11 +01:00
Christian c7390ef0d7 Update verison 2022-12-23 16:51:13 +01:00
老九andGünther Foidl 8a926a3db3 Performance optimization of SendPacketAsync (#1624)
* performance optimization of SendPacketAsync

* Update Source/MQTTnet.AspnetCore/MqttConnectionContext.cs

Co-authored-by: Günther Foidl <gue@korporal.at>

Co-authored-by: Günther Foidl <gue@korporal.at>
2022-12-16 17:13:11 +01:00
soyoo 7d5e49b44c A typo in comment. (#1628) 2022-12-16 17:12:46 +01:00
Christian 555c3eac7a Reset release notes 2022-12-13 17:15:43 +01:00
138 changed files with 3335 additions and 1508 deletions
+20 -9
View File
@@ -1,9 +1,20 @@
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger). * [Core] Added net7.0 builds (#1635, thanks to @YAJeff).
* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593). * [Core] Added support for payload segments (#1585, thanks to @xljiulang)
* [Core] Performance improvements and reduced memory usage due to improved buffer copies (#1599, thanks to @xljiulang). * [Client] Added support for passing MQTT v5 options (User properties etc.) for disconnects.
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun). * [Client] An internal exception is no longer caught silently when calling _DisconnectAsync_ to indicate that the disconnect is not clean (BREAKING CHANGE).
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang). * [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] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang). * [Client] Added support for disabling packet fragmentation (required for i.e. AWS, #1690, thanks to @logicaloud).
* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang). * [Client] Added new overloads for options builder.
* [Server] Add support for interception of will messages (#1613). * [Client] Renamed _InspectPackage_ to _InspectPacketAsync_ (BREAKING CHANGE).
* [Server] Fixed wrong topic filter detections (#1615, thanks to @logicaloud). * [ManagedClient] Exposed added and removed topics in _ManagedProcessFailedEventArgs_ (#1678, thanks to @scottbrogden-iheartmedia).
* [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).
* [Server] Exposed server session items at server level and allow custom session items for injected application messages (#1638).
* [Server] Improved performance for retained message handling when subscribing using "DoNotSendOnSubscribe" or "SendAtSubscribeIfNewSubscriptionOnly" (#1661, thanks to @Int32Overflow).
* [Server] Added support for changing the used TLS certificate while the server is running (#1652, thanks to @YAJeff). The certificate provider will now be invoked for every new connection!
* [Server] Added a new API method which allows reading a single retained message without the need to processing the entire set of retained messages (#1659).
* [Server] Added a new event (InterceptingClientEnqueueAsync) which allows intercepting when an application message is enqueued for a client (#1648).
* [Server] Fixed race condition when handling connections which leads to stopped message transfers (#1677, thanks to @RazvanEmilR).
* [Server] Fixed not delivered packets to other sessions if the _InterceptingClientEnqueueEvent_ is used and accepting the packet is denied (#1685, thanks to @RossHNPC).
* [Server] Added missing user properties to subscribe and unsubscribe interceptor (#1702).
+2 -1
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
env: env:
VERSION: "4.1.4.${{github.run_number}}" VERSION: "4.2.0.${{github.run_number}}"
jobs: jobs:
build: build:
@@ -22,6 +22,7 @@ jobs:
dotnet-version: | dotnet-version: |
3.1.x 3.1.x
6.0.x 6.0.x
7.0.x
- name: Setup MSBuild - name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1 uses: microsoft/setup-msbuild@v1.1
+16
View File
@@ -50,6 +50,22 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
* A custom message interceptor can be added which allows transforming or extending every received application message * A custom message interceptor can be added which allows transforming or extending every received application message
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients * Validate subscriptions and deny subscribing of certain topics depending on requesting clients
## Getting Started
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/MQTTnet/
Use these command in the Package Manager console to install MQTTnet manually:
```
Install-Package MQTTnet
```
Samples for using MQTTnet are part of this repository. For starters these samples are recommended:
- [Connect with a broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Connection_Samples.cs)
- [Subscribing to data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Subscribe_Samples.cs)
- [Publishing data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Publish_Samples.cs)
- [Host own broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Server/Server_Simple_Samples.cs)
## Code of Conduct ## Code of Conduct
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
+50 -2
View File
@@ -16,6 +16,28 @@ namespace MQTTnet.Samples.Client;
public static class Client_Connection_Samples 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() public static async Task Connect_Client()
{ {
/* /*
@@ -49,6 +71,32 @@ public static class Client_Connection_Samples
} }
} }
public static async Task Connect_With_Amazon_AWS()
{
/*
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
*
* The broker requires special settings which are set here.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("amazon.web.services.broker")
// Disabling packet fragmentation is very important!
.WithoutPacketFragmentation()
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("The MQTT client is connected.");
await mqttClient.DisconnectAsync();
}
}
public static async Task Connect_Client_Timeout() public static async Task Connect_Client_Timeout()
{ {
/* /*
@@ -232,7 +280,7 @@ public static class Client_Connection_Samples
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection. // Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
// Using a reason code requires MQTT version 5.0.0! // Using a reason code requires MQTT version 5.0.0!
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError); await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
} }
} }
@@ -297,7 +345,7 @@ public static class Client_Connection_Samples
/* /*
* This sample sends a PINGREQ packet to the server and waits for a reply. * 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(); var mqttFactory = new MqttFactory();
@@ -26,7 +26,7 @@ public static class PackageInspection_Samples
.WithTcpServer("broker.hivemq.com") .WithTcpServer("broker.hivemq.com")
.Build(); .Build();
mqttClient.InspectPackage += OnInspectPackage; mqttClient.InspectPacketAsync += OnInspectPacket;
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -39,7 +39,7 @@ public static class PackageInspection_Samples
} }
} }
static Task OnInspectPackage(InspectMqttPacketEventArgs eventArgs) static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
{ {
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound) if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
{ {
+18 -1
View File
@@ -4,7 +4,13 @@
using System.Reflection; using System.Reflection;
Console.WriteLine("Welcome to MQTTnet samples!"); Console.BackgroundColor = ConsoleColor.White;
Console.BackgroundColor = ConsoleColor.Red;
Console.Title = "Samples - MQTTnet";
Console.WriteLine("----------------------------------------------");
Console.WriteLine("- Welcome to MQTTnet samples! -");
Console.WriteLine("----------------------------------------------");
Console.ResetColor();
Console.WriteLine(); Console.WriteLine();
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList(); var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
@@ -16,7 +22,11 @@ foreach (var sampleClass in sampleClasses)
index++; index++;
} }
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample class (press Enter to continue): "); Console.Write("Please choose sample class (press Enter to continue): ");
Console.ResetColor();
var input = Console.ReadLine(); var input = Console.ReadLine();
var selectedIndex = int.Parse(input ?? "0"); var selectedIndex = int.Parse(input ?? "0");
var selectedSampleClass = sampleClasses[selectedIndex]; var selectedSampleClass = sampleClasses[selectedIndex];
@@ -29,12 +39,19 @@ foreach (var sampleMethod in sampleMethods)
index++; index++;
} }
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.Write("Please choose sample (press Enter to continue): "); Console.Write("Please choose sample (press Enter to continue): ");
Console.ResetColor();
input = Console.ReadLine(); input = Console.ReadLine();
selectedIndex = int.Parse(input ?? "0"); selectedIndex = int.Parse(input ?? "0");
var selectedSampleMethod = sampleMethods[selectedIndex]; var selectedSampleMethod = sampleMethods[selectedIndex];
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Executing sample..."); Console.WriteLine("Executing sample...");
Console.ResetColor();
Console.WriteLine(); Console.WriteLine();
try try
@@ -17,7 +17,7 @@ using MQTTnet.Server;
namespace MQTTnet.Samples.Server; namespace MQTTnet.Samples.Server;
public static class Server_Asp_Net_Samples public static class Server_ASP_NET_Samples
{ {
public static Task Start_Server_With_WebSockets_Support() public static Task Start_Server_With_WebSockets_Support()
{ {
@@ -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();
}
}
}
+75
View File
@@ -0,0 +1,75 @@
// 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.
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using MQTTnet.Server;
namespace MQTTnet.Samples.Server;
public static class Server_TLS_Samples
{
public static async Task Run_Server_With_Self_Signed_Certificate()
{
/*
* This sample starts a simple MQTT server which will accept any TCP connection.
* It also has an encrypted connection using a self signed TLS certificate.
*
* See sample "Run_Minimal_Server" for more details.
*/
var mqttFactory = new MqttFactory();
// This certificate is self signed so that
var certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync();
}
}
static X509Certificate2 CreateSelfSignedCertificate(string oid)
{
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Loopback);
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost");
using (var rsa = RSA.Create())
{
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
certRequest.CertificateExtensions.Add(sanBuilder.Build());
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
{
var pfxCertificate = new X509Certificate2(
certificate.Export(X509ContentType.Pfx),
(string)null!,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
return pfxCertificate;
}
}
}
}
@@ -103,7 +103,7 @@ namespace MQTTnet.AspNetCore.Tests
connection.Transport = pipe; connection.Transport = pipe;
var ctx = new MqttConnectionContext(serializer, connection); var ctx = new MqttConnectionContext(serializer, connection);
await ctx.SendPacketAsync(new MqttPublishPacket { Payload = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false); await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new ArraySegment<byte>(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false);
var readResult = await pipe.Send.Reader.ReadAsync(); var readResult = await pipe.Send.Reader.ReadAsync();
Assert.IsTrue(readResult.Buffer.Length > 20000); Assert.IsTrue(readResult.Buffer.Length > 20000);
@@ -19,7 +19,7 @@ namespace MQTTnet.AspNetCore.Tests
{ {
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", Payload = new byte[5]}).Join(); var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", PayloadSegment = new byte[5]}).Join();
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count); var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
@@ -44,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool); _sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
_receiver = new SocketReceiver(_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() public override ValueTask DisposeAsync()
#else #else
public Task DisposeAsync() public Task DisposeAsync()
@@ -57,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
_socket?.Dispose(); _socket?.Dispose();
#if NETCOREAPP3_1 || NET5_0_OR_GREATER #if NETCOREAPP3_1_OR_GREATER
return base.DisposeAsync(); return base.DisposeAsync();
} }
@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information. // 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.Builder;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks> <TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<AssemblyName>MQTTnet.AspNetCore</AssemblyName> <AssemblyName>MQTTnet.AspNetCore</AssemblyName>
<RootNamespace>MQTTnet.AspNetCore</RootNamespace> <RootNamespace>MQTTnet.AspNetCore</RootNamespace>
@@ -51,11 +51,11 @@
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants> <DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' "> <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' ">
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' "> <ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' and '$(TargetFramework)' != 'net7.0' ">
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
</ItemGroup> </ItemGroup>
@@ -211,8 +211,17 @@ namespace MQTTnet.AspNetCore
{ {
var buffer = PacketFormatterAdapter.Encode(packet); var buffer = PacketFormatterAdapter.Encode(packet);
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); WritePacketBuffer(_output, buffer);
await _output.FlushAsync(cancellationToken).ConfigureAwait(false); await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
}
BytesSent += buffer.Length; BytesSent += buffer.Length;
} }
@@ -2,38 +2,34 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Server;
using System; using System;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore
{ {
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
{ {
IMqttNetLogger _logger = new MqttNetNullLogger(); IMqttNetLogger _logger = MqttNetNullLogger.Instance;
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; } public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) public void Dispose()
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
return Task.CompletedTask;
}
public Task StopAsync()
{
return Task.CompletedTask;
} }
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext) public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
{ {
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket)); if (webSocket == null)
{
throw new ArgumentNullException(nameof(webSocket));
}
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}"; var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
@@ -48,7 +44,7 @@ namespace MQTTnet.AspNetCore
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate); var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _logger)) using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
{ {
await clientHandler(channelAdapter).ConfigureAwait(false); await clientHandler(channelAdapter).ConfigureAwait(false);
} }
@@ -60,8 +56,15 @@ namespace MQTTnet.AspNetCore
} }
} }
public void Dispose() public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{ {
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
return Task.CompletedTask;
}
public Task StopAsync()
{
return Task.CompletedTask;
} }
} }
} }
@@ -73,7 +73,7 @@ namespace MQTTnet.Benchmarks
var channel = new MemoryMqttChannel(_stream); var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger()); _channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
} }
static byte[] Join(params ArraySegment<byte>[] chunks) static byte[] Join(params ArraySegment<byte>[] chunks)
@@ -4,7 +4,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<DebugType>Full</DebugType> <DebugType>Full</DebugType>
<TargetFrameworks>net6.0</TargetFrameworks> <TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<LangVersion>7.2</LangVersion> <LangVersion>7.2</LangVersion>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
@@ -26,6 +26,10 @@
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" /> <PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" /> <ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" /> <ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
@@ -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<byte>(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);
}
}
}
@@ -54,7 +54,7 @@ namespace MQTTnet.Benchmarks
public void Deserialize_10000_Messages() public void Deserialize_10000_Messages()
{ {
var channel = new BenchmarkMqttChannel(_serializedPacket); var channel = new BenchmarkMqttChannel(_serializedPacket);
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger()); var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
for (var i = 0; i < 10000; i++) for (var i = 0; i < 10000; i++)
{ {
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
@@ -353,13 +353,13 @@ namespace MQTTnet.Extensions.ManagedClient
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime; return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
} }
async Task HandleSubscriptionExceptionAsync(Exception exception) async Task HandleSubscriptionExceptionAsync(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
{ {
_logger.Warning(exception, "Synchronizing subscriptions failed."); _logger.Warning(exception, "Synchronizing subscriptions failed.");
if (_synchronizingSubscriptionsFailedEvent.HasHandlers) if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
{ {
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false); await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception, addedSubscriptions, removedSubscriptions)).ConfigureAwait(false);
} }
} }
@@ -457,11 +457,13 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
_logger.Info("Publishing subscriptions at reconnect"); _logger.Info("Publishing subscriptions at reconnect");
List<MqttTopicFilter> topicFilters = null;
try try
{ {
if (_reconnectSubscriptions.Any()) if (_reconnectSubscriptions.Any())
{ {
var topicFilters = new List<MqttTopicFilter>(); topicFilters = new List<MqttTopicFilter>();
foreach (var sub in _reconnectSubscriptions) foreach (var sub in _reconnectSubscriptions)
{ {
@@ -479,7 +481,7 @@ namespace MQTTnet.Extensions.ManagedClient
} }
catch (Exception exception) catch (Exception exception)
{ {
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false); await HandleSubscriptionExceptionAsync(exception, topicFilters, null).ConfigureAwait(false);
} }
} }
@@ -591,6 +593,9 @@ namespace MQTTnet.Extensions.ManagedClient
} }
await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false); await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
//clear because these worked, maybe the subscribe below will fail, only report those
removedSubscriptions.Clear();
} }
if (addedSubscriptions != null && addedSubscriptions.Any()) if (addedSubscriptions != null && addedSubscriptions.Any())
@@ -607,7 +612,7 @@ namespace MQTTnet.Extensions.ManagedClient
} }
catch (Exception exception) catch (Exception exception)
{ {
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false); await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false);
} }
} }
@@ -24,7 +24,7 @@ namespace MQTTnet.Extensions.ManagedClient
/// <summary> /// <summary>
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet. /// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
/// Amazon AWS limits this number to 8. The default is int.MaxValue. /// Amazon Web Services (AWS) limits this number to 8. The default is int.MaxValue.
/// </summary> /// </summary>
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue; public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
} }
@@ -2,17 +2,33 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using MQTTnet.Packets;
using System; using System;
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Extensions.ManagedClient namespace MQTTnet.Extensions.ManagedClient
{ {
public class ManagedProcessFailedEventArgs : EventArgs public class ManagedProcessFailedEventArgs : EventArgs
{ {
public ManagedProcessFailedEventArgs(Exception exception) public ManagedProcessFailedEventArgs(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
{ {
Exception = exception ?? throw new ArgumentNullException(nameof(exception)); Exception = exception ?? throw new ArgumentNullException(nameof(exception));
if (addedSubscriptions != null)
{
AddedSubscriptions = new List<string>(addedSubscriptions.Select(item => item.Topic));
}
if (removedSubscriptions != null)
{
RemovedSubscriptions = new List<string>(removedSubscriptions);
}
} }
public Exception Exception { get; } public Exception Exception { get; }
public List<string> AddedSubscriptions { get; }
public List<string> RemovedSubscriptions { get; }
} }
} }
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
+12 -7
View File
@@ -4,10 +4,12 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -83,11 +85,14 @@ namespace MQTTnet.Extensions.Rpc
throw new MqttProtocolViolationException("RPC response topic is empty."); throw new MqttProtocolViolationException("RPC response topic is empty.");
} }
var requestMessage = new MqttApplicationMessageBuilder().WithTopic(requestTopic) var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
.WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel) if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
.WithResponseTopic(responseTopic) {
.Build(); requestMessageBuilder.WithResponseTopic(responseTopic);
}
var requestMessage = requestMessageBuilder.Build();
try try
{ {
@@ -115,7 +120,6 @@ namespace MQTTnet.Extensions.Rpc
finally finally
{ {
_waitingCalls.TryRemove(responseTopic, out _); _waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false); await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
} }
} }
@@ -127,7 +131,8 @@ namespace MQTTnet.Extensions.Rpc
return CompletedTask.Instance; return CompletedTask.Instance;
} }
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload); var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
awaitable.TrySetResult(payloadBuffer);
// Set this message to handled to that other code can avoid execution etc. // Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true; eventArgs.IsHandled = true;
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks> <TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
@@ -27,8 +27,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
return new MqttChannelAdapter( return new MqttChannelAdapter(
new MqttTcpChannel(options), new MqttTcpChannel(options),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)), new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
packetInspector, logger)
logger); {
PacketInspector = packetInspector
};
} }
case MqttClientWebSocketOptions webSocketOptions: case MqttClientWebSocketOptions webSocketOptions:
@@ -36,8 +38,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
return new MqttChannelAdapter( return new MqttChannelAdapter(
new WebSocket4NetMqttChannel(options, webSocketOptions), new WebSocket4NetMqttChannel(options, webSocketOptions),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)), new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
packetInspector, logger)
logger); {
PacketInspector = packetInspector
};
} }
default: default:
+10 -1
View File
@@ -33,9 +33,18 @@ namespace MQTTnet.TestApp
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
var payloadText = string.Empty;
if (e.ApplicationMessage.PayloadSegment.Count > 0)
{
payloadText = Encoding.UTF8.GetString(
e.ApplicationMessage.PayloadSegment.Array,
e.ApplicationMessage.PayloadSegment.Offset,
e.ApplicationMessage.PayloadSegment.Count);
}
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}"); Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}"); Console.WriteLine($"+ Payload = {payloadText}");
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}"); Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine(); Console.WriteLine();
@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<DebugType>Full</DebugType> <DebugType>Full</DebugType>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks> <TargetFrameworks>net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
+4 -4
View File
@@ -196,7 +196,7 @@ namespace MQTTnet.TestApp
return new MqttApplicationMessage return new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes(Payload), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(Payload)),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
}; };
} }
@@ -229,7 +229,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
}; };
@@ -280,7 +280,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
}; };
@@ -331,7 +331,7 @@ namespace MQTTnet.TestApp
var message = new MqttApplicationMessage var message = new MqttApplicationMessage
{ {
Topic = "A/B/C", Topic = "A/B/C",
Payload = Encoding.UTF8.GetBytes("Hello World"), PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
}; };
+11 -4
View File
@@ -5,6 +5,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
@@ -100,7 +101,7 @@ namespace MQTTnet.TestApp
{ {
// Replace the payload with the timestamp. But also extending a JSON // Replace the payload with the timestamp. But also extending a JSON
// based payload with the timestamp is a suitable use case. // based payload with the timestamp is a suitable use case.
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")); e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")));
} }
if (e.ApplicationMessage.Topic == "not_allowed_topic") if (e.ApplicationMessage.Topic == "not_allowed_topic")
@@ -143,10 +144,16 @@ namespace MQTTnet.TestApp
mqttServer.InterceptingPublishAsync += e => mqttServer.InterceptingPublishAsync += e =>
{ {
MqttNetConsoleLogger.PrintToConsole( var payloadText = string.Empty;
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'", if (e.ApplicationMessage.PayloadSegment.Count > 0)
ConsoleColor.Magenta); {
payloadText = Encoding.UTF8.GetString(
e.ApplicationMessage.PayloadSegment.Array,
e.ApplicationMessage.PayloadSegment.Offset,
e.ApplicationMessage.PayloadSegment.Count);
}
MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta);
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
@@ -14,7 +14,7 @@ using MQTTnet.LowLevelClient;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Client namespace MQTTnet.Tests.Clients.LowLevelMqttClient
{ {
[TestClass] [TestClass]
public sealed class LowLevelMqttClient_Tests : BaseTestClass public sealed class LowLevelMqttClient_Tests : BaseTestClass
@@ -17,7 +17,7 @@ using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Client namespace MQTTnet.Tests.Clients.ManagedMqttClient
{ {
[TestClass] [TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass public sealed class ManagedMqttClient_Tests : BaseTestClass
@@ -164,7 +164,7 @@ namespace MQTTnet.Tests.Client
.Build(); .Build();
var dyingClient = testEnvironment.CreateClient(); 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()); await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
@@ -199,7 +199,7 @@ namespace MQTTnet.Tests.Client
{ {
var server = await testEnvironment.StartServer(); 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 clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var connected = GetConnectedTask(managedClient); var connected = GetConnectedTask(managedClient);
@@ -226,7 +226,7 @@ namespace MQTTnet.Tests.Client
await testEnvironment.StartServer(); 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 clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
var storage = new ManagedMqttClientTestStorage(); var storage = new ManagedMqttClientTestStorage();
@@ -439,7 +439,7 @@ namespace MQTTnet.Tests.Client
} }
} }
async Task<ManagedMqttClient> CreateManagedClientAsync( async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> CreateManagedClientAsync(
TestEnvironment testEnvironment, TestEnvironment testEnvironment,
IMqttClient underlyingClient = null, IMqttClient underlyingClient = null,
TimeSpan? connectionCheckInterval = null, TimeSpan? connectionCheckInterval = null,
@@ -456,7 +456,7 @@ namespace MQTTnet.Tests.Client
// at connection check intervals // at connection check intervals
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1); 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); var connected = GetConnectedTask(managedClient);
@@ -473,7 +473,7 @@ namespace MQTTnet.Tests.Client
/// name="managedClient" /> /// name="managedClient" />
/// has connected /// has connected
/// </summary> /// </summary>
Task GetConnectedTask(ManagedMqttClient managedClient) Task GetConnectedTask(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient)
{ {
var connected = new TaskCompletionSource<bool>(); var connected = new TaskCompletionSource<bool>();
@@ -495,7 +495,7 @@ namespace MQTTnet.Tests.Client
/// name="expectedNumberOfMessages" /> /// name="expectedNumberOfMessages" />
/// have been received /// have been received
/// </summary> /// </summary>
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(ManagedMqttClient managedClient, int expectedNumberOfMessages) Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient, int expectedNumberOfMessages)
{ {
var receivedMessages = new List<MqttApplicationMessage>(); var receivedMessages = new List<MqttApplicationMessage>();
var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>(); var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
@@ -0,0 +1,166 @@
// 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(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);
await LongTestDelay();
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);
await LongTestDelay();
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);
}
}
}
}
@@ -21,7 +21,7 @@ using MQTTnet.Tests.Mockups;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace MQTTnet.Tests.Client namespace MQTTnet.Tests.Clients.MqttClient
{ {
[TestClass] [TestClass]
public sealed class MqttClient_Tests : BaseTestClass public sealed class MqttClient_Tests : BaseTestClass
@@ -71,60 +71,34 @@ namespace MQTTnet.Tests.Client
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttCommunicationException))] public async Task Connect_Disconnect_Connect()
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
{ {
var client = new MqttFactory().CreateMqttClient(); using (var testEnvironment = CreateTestEnvironment())
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(5)).Build()); {
} await testEnvironment.StartServer();
[TestMethod] var clientOptions = testEnvironment.CreateDefaultClientOptions();
[ExpectedException(typeof(OperationCanceledException))] var client = testEnvironment.CreateClient();
public async Task Connect_To_Invalid_Server_Wrong_IP()
{ await client.ConnectAsync(clientOptions);
var client = new MqttFactory().CreateMqttClient(); await client.DisconnectAsync();
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2))) await client.ConnectAsync(clientOptions);
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
} }
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttCommunicationException))] [ExpectedException(typeof(InvalidOperationException))]
public async Task Connect_To_Invalid_Server_Wrong_Protocol() public async Task Connect_Multiple_Times_Should_Fail()
{ {
var client = new MqttFactory().CreateMqttClient(); using (var testEnvironment = CreateTestEnvironment())
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build()); {
} await testEnvironment.StartServer();
[TestMethod] var clientOptions = testEnvironment.CreateDefaultClientOptions();
public async Task ConnectTimeout_Throws_Exception() var client = testEnvironment.CreateClient();
{
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()); await client.ConnectAsync(clientOptions);
await client.ConnectAsync(clientOptions);
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);
} }
} }
@@ -6,9 +6,8 @@ using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Extensions;
namespace MQTTnet.Tests.Client namespace MQTTnet.Tests.Clients
{ {
[TestClass] [TestClass]
public class MqttClientOptionsBuilder_Tests public class MqttClientOptionsBuilder_Tests
@@ -30,7 +30,7 @@ namespace MQTTnet.Tests.Diagnostics
var packets = new List<string>(); var packets = new List<string>();
mqttClient.InspectPackage += eventArgs => mqttClient.InspectPacketAsync += eventArgs =>
{ {
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer)); packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
return CompletedTask.Instance; return CompletedTask.Instance;
@@ -111,7 +111,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("HELLO"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C" Topic = "A/B/C"
}; };
@@ -318,10 +318,9 @@ namespace MQTTnet.Tests.Formatter
var publishPacket = new MqttPublishPacket var publishPacket = new MqttPublishPacket
{ {
Topic = "abcdefghijklmnopqrstuvwxyz0123456789", Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
Payload = payload PayloadSegment = new ArraySegment<byte>(payload)
}; };
var serializationHelper = new MqttPacketSerializationHelper(); var serializationHelper = new MqttPacketSerializationHelper();
var buffer = serializationHelper.Encode(publishPacket); var buffer = serializationHelper.Encode(publishPacket);
@@ -329,17 +328,17 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNotNull(publishPacketCopy); Assert.IsNotNull(publishPacketCopy);
Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic); Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic);
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy.PayloadSegment.ToArray());
// Now modify the payload and test again. // Now modify the payload and test again.
publishPacket.Payload = Encoding.UTF8.GetBytes("MQTT"); publishPacket.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("MQTT"));
buffer = serializationHelper.Encode(publishPacket); buffer = serializationHelper.Encode(publishPacket);
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket; var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
Assert.IsNotNull(publishPacketCopy2); Assert.IsNotNull(publishPacketCopy2);
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic); Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy2.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy2.PayloadSegment.ToArray());
} }
[TestMethod] [TestMethod]
@@ -463,7 +462,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("HELLO"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "A/B/C" Topic = "A/B/C"
}; };
@@ -567,7 +566,6 @@ namespace MQTTnet.Tests.Formatter
using (var adapter = new MqttChannelAdapter( using (var adapter = new MqttChannelAdapter(
channel, channel,
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
null,
new MqttNetEventLogger())) new MqttNetEventLogger()))
{ {
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
@@ -583,19 +581,12 @@ namespace MQTTnet.Tests.Formatter
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer) MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
{ {
var channel = new MemoryMqttChannel(buffer); var channel = new MemoryMqttChannel(buffer);
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, null, new MqttNetEventLogger()); var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, new MqttNetEventLogger());
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
return packetFormatterAdapter.ProtocolVersion; return packetFormatterAdapter.ProtocolVersion;
} }
MqttBufferReader ReaderFactory(byte[] data)
{
var reader = new MqttBufferReader();
reader.SetBuffer(data, 0, data.Length);
return reader;
}
TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket
{ {
var writer = bufferWriter ?? WriterFactory(); var writer = bufferWriter ?? WriterFactory();
@@ -604,7 +595,7 @@ namespace MQTTnet.Tests.Formatter
using (var channel = new MemoryMqttChannel(buffer.Join().ToArray())) using (var channel = new MemoryMqttChannel(buffer.Join().ToArray()))
{ {
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger()); var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
} }
} }
@@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
@@ -296,7 +298,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("Payload"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic", Topic = "Topic",
ResponseTopic = "/Response", ResponseTopic = "/Response",
@@ -320,7 +322,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
Assert.AreEqual(publishPacket.Retain, deserialized.Retain); Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1. Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1.
@@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Formatter; using MQTTnet.Formatter;
@@ -257,7 +259,7 @@ namespace MQTTnet.Tests.Formatter
PacketIdentifier = 123, PacketIdentifier = 123,
Dup = true, Dup = true,
Retain = true, Retain = true,
Payload = Encoding.ASCII.GetBytes("Payload"), PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
Topic = "Topic", Topic = "Topic",
ResponseTopic = "/Response", ResponseTopic = "/Response",
@@ -281,7 +283,7 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
Assert.AreEqual(publishPacket.Retain, deserialized.Retain); Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload); CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic); Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
+2 -2
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks> <TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0;net7.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
@@ -51,10 +51,7 @@ namespace MQTTnet.Tests.Mockups
public void AssertReceivedCountEquals(int expectedCount) public void AssertReceivedCountEquals(int expectedCount)
{ {
lock (_receivedEventArgs) Assert.AreEqual(expectedCount, Count);
{
Assert.AreEqual(expectedCount, _receivedEventArgs.Count);
}
} }
public string GeneratePayloadSequence() public string GeneratePayloadSequence()
@@ -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() public ILowLevelMqttClient CreateLowLevelClient()
{ {
lock (_clients) lock (_clients)
@@ -0,0 +1,48 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MQTTnet.Tests
{
[TestClass]
public class MqttApplicationMessageTest
{
[TestMethod]
public void PayloadSegment()
{
var message = new MqttApplicationMessage();
Assert.AreEqual(0, message.Payload.Length);
Assert.AreEqual(0, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2 };
Assert.AreEqual(2, message.Payload.Length);
Assert.AreEqual(2, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2, 3 };
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 });
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 }, 1, 1);
Assert.AreEqual(1, message.Payload.Length);
Assert.AreEqual(1, message.PayloadSegment.Count);
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsFalse(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
}
}
}
@@ -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);
}
}
}
@@ -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();
}
}
}
@@ -31,7 +31,7 @@ namespace MQTTnet.Tests
{ {
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535)); var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
var adapter = new MqttChannelAdapter(channel, formatterAdapter, null, MqttNetNullLogger.Instance); var adapter = new MqttChannelAdapter(channel, formatterAdapter, MqttNetNullLogger.Instance);
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
} }
} }
@@ -9,19 +9,13 @@ using MQTTnet.Protocol;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
[TestClass] [TestClass]
public class MqttTopicValidator_Tests public sealed class MqttTopicValidator_Tests
{ {
[TestMethod]
public void Valid_Topic()
{
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
}
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))] [ExpectedException(typeof(MqttProtocolViolationException))]
public void Invalid_Topic_Plus() public void Invalid_Topic_Empty()
{ {
MqttTopicValidator.ThrowIfInvalid("/a/+/c"); MqttTopicValidator.ThrowIfInvalid(string.Empty);
} }
[TestMethod] [TestMethod]
@@ -33,9 +27,15 @@ namespace MQTTnet.Tests
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))] [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");
} }
} }
} }
+2 -2
View File
@@ -636,7 +636,7 @@ namespace MQTTnet.Tests.Server
// nothing // nothing
Assert.AreEqual(false, c1.IsConnected); Assert.AreEqual(false, c1.IsConnected);
await c1.DisconnectAsync(); await c1.TryDisconnectAsync();
Assert.AreEqual(false, c1.IsConnected); Assert.AreEqual(false, c1.IsConnected);
await Task.Delay(500); await Task.Delay(500);
@@ -954,7 +954,7 @@ namespace MQTTnet.Tests.Server
var client = testEnvironment.CreateClient(); var client = testEnvironment.CreateClient();
client.InspectPackage += e => client.InspectPacketAsync += e =>
{ {
if (e.Buffer.Length > 0) if (e.Buffer.Length > 0)
{ {
@@ -228,7 +228,7 @@ namespace MQTTnet.Tests.Server
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId) CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
{ {
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out _, out _); MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId); return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
} }
} }
@@ -1,58 +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 System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Publish_Tests : BaseTestClass
{
[TestMethod]
public async Task Return_Success_When_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
[TestMethod]
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
}
}
@@ -5,6 +5,8 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -27,5 +29,118 @@ namespace MQTTnet.Tests.Server
await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce); await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
} }
} }
[TestMethod]
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
[TestMethod]
public async Task Return_Success_When_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
[TestMethod]
public async Task Intercept_Client_Enqueue()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
server.InterceptingClientEnqueueAsync += e =>
{
e.AcceptEnqueue = false;
return CompletedTask.Instance;
};
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
await LongTestDelay();
// Do not increase because the internal enqueue to the target client is not accepted!
receivedMessages.AssertReceivedCountEquals(1);
}
}
[TestMethod]
public async Task Intercept_Client_Enqueue_Multiple_Clients_Subscribed_Messages_Are_Filtered()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiverOne = await testEnvironment.ConnectClient(o => o.WithClientId("One"));
await receiverOne.SubscribeAsync("A");
var receivedMessagesOne = testEnvironment.CreateApplicationMessageHandler(receiverOne);
var receiverTwo = await testEnvironment.ConnectClient(o => o.WithClientId("Two"));
await receiverTwo.SubscribeAsync("A");
var receivedMessagesTwo = testEnvironment.CreateApplicationMessageHandler(receiverTwo);
var receiverThree = await testEnvironment.ConnectClient(o => o.WithClientId("Three"));
await receiverThree.SubscribeAsync("A");
var receivedMessagesThree = testEnvironment.CreateApplicationMessageHandler(receiverThree);
server.InterceptingClientEnqueueAsync += e =>
{
if (e.ReceiverClientId.Contains("Two")) e.AcceptEnqueue = false;
return CompletedTask.Instance;
};
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
await LongTestDelay();
receivedMessagesOne.AssertReceivedCountEquals(1);
receivedMessagesTwo.AssertReceivedCountEquals(0);
receivedMessagesThree.AssertReceivedCountEquals(1);
}
}
} }
} }
@@ -164,7 +164,7 @@ namespace MQTTnet.Tests.Server
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build(); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build();
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions)); var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => client.ConnectAsync(clientOptions));
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode); Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
} }
} }
@@ -90,6 +90,29 @@ namespace MQTTnet.Tests.Server
} }
} }
[TestMethod]
public async Task Intercept_Subscribe_With_User_Properties()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
InterceptingSubscriptionEventArgs eventArgs = null;
server.InterceptingSubscriptionAsync += e =>
{
eventArgs = e;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var subscribeOptions = testEnvironment.Factory.CreateSubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build();
await client.SubscribeAsync(subscribeOptions);
CollectionAssert.AreEqual(subscribeOptions.UserProperties.ToList(), eventArgs.UserProperties);
}
}
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttClientDisconnectedException))] [ExpectedException(typeof(MqttClientDisconnectedException))]
public async Task Disconnect_While_Subscribing() public async Task Disconnect_While_Subscribing()
@@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Server
var topics = t.Value; var topics = t.Value;
foreach (var topic in topics) foreach (var topic in topics)
{ {
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
bucketDepths.TryGetValue(topicHash, out var currentValue); bucketDepths.TryGetValue(topicHash, out var currentValue);
++currentValue; ++currentValue;
@@ -96,7 +96,7 @@ namespace MQTTnet.Tests.Server
{ {
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#"; var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
} }
@@ -110,7 +110,7 @@ namespace MQTTnet.Tests.Server
{ {
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+"; var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
} }
@@ -150,7 +150,7 @@ namespace MQTTnet.Tests.Server
// UInt64 is limited to 8 levels // UInt64 is limited to 8 levels
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -212,7 +212,7 @@ namespace MQTTnet.Tests.Server
var topic = sb.ToString(); var topic = sb.ToString();
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present"); Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
@@ -269,7 +269,7 @@ namespace MQTTnet.Tests.Server
var topic = sb.ToString(); var topic = sb.ToString();
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -319,7 +319,7 @@ namespace MQTTnet.Tests.Server
var l3 = "#"; var l3 = "#";
var topic = $"{l0}/{l1}/{l2}/{l3}"; var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -354,7 +354,7 @@ namespace MQTTnet.Tests.Server
var l3 = "prop1"; var l3 = "prop1";
var topic = $"{l0}/{l1}/{l2}/{l3}"; var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present"); Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
@@ -447,7 +447,7 @@ namespace MQTTnet.Tests.Server
var l3 = "prop1"; var l3 = "prop1";
var topic = $"{l0}/{l1}/{l2}/{l3}"; var topic = $"{l0}/{l1}/{l2}/{l3}";
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
Assert.IsTrue(topicHasWildcard, "Wildcard not detected"); Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
@@ -475,7 +475,7 @@ namespace MQTTnet.Tests.Server
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask) void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
{ {
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard); MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("Topic: " + topic); Console.WriteLine("Topic: " + topic);
@@ -501,7 +501,7 @@ namespace MQTTnet.Tests.Server
{ {
var topicToFind = topicsToFind[countDown]; var topicToFind = topicsToFind[countDown];
MqttSubscription.CalculateTopicHash(topicToFind, out var topicHash, out _, out _); MqttTopicHash.Calculate(topicToFind, out var topicHash, out _, out _);
clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result); clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result);
if (result.IsSubscribed) if (result.IsSubscribed)
+204
View File
@@ -0,0 +1,204 @@
#if !WINDOWS_UWP && (NET48_OR_GREATER || NET5_0_OR_GREATER)
using System;
using System.Linq;
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Certificates;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Tls_Tests : BaseTestClass
{
static X509Certificate2 CreateCertificate(string oid)
{
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Loopback);
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost");
using (var rsa = RSA.Create())
{
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false));
certRequest.CertificateExtensions.Add(sanBuilder.Build());
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
{
var pfxCertificate = new X509Certificate2(
certificate.Export(X509ContentType.Pfx),
(string)null,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
return pfxCertificate;
}
}
}
[TestMethod]
public async Task Tls_Swap_Test()
{
var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
var serverOptionsBuilder = testEnvironment.Factory.CreateServerOptionsBuilder();
var firstOid = "1.3.6.1.5.5.7.3.1";
var secondOid = "1.3.6.1.5.5.7.3.2";
var certificateProvider = new CertificateProvider
{
CurrentCertificate = CreateCertificate(firstOid)
};
serverOptionsBuilder.WithoutDefaultEndpoint().WithEncryptedEndpoint().WithEncryptionSslProtocol(SslProtocols.Tls12).WithEncryptionCertificate(certificateProvider);
var serverOptions = serverOptionsBuilder.Build();
var server = testEnvironment.CreateServer(serverOptions);
var publishedCount = 0;
server.InterceptingPublishAsync += args =>
{
Interlocked.Increment(ref publishedCount);
return Task.CompletedTask;
};
await server.StartAsync();
var firstClient = await ConnectClientAsync(
testEnvironment,
args =>
{
Assert.AreEqual(firstOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
return true;
});
var firstClientReceivedCount = 0;
firstClient.ApplicationMessageReceivedAsync += args =>
{
Interlocked.Increment(ref firstClientReceivedCount);
return Task.CompletedTask;
};
await firstClient.SubscribeAsync("TestTopic1");
await firstClient.PublishAsync(
new MqttApplicationMessage
{
Topic = "TestTopic1",
Payload = new byte[] { 1, 2, 3, 4 }
});
await testEnvironment.Server.InjectApplicationMessage(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
Topic = "TestTopic1",
Payload = new byte[] { 1, 2, 3, 4 }
}));
certificateProvider.CurrentCertificate = CreateCertificate(secondOid);
// Validate that the certificate was switched
var secondClient = await ConnectClientAsync(
testEnvironment,
args =>
{
Assert.AreEqual(secondOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
return true;
});
var secondClientReceivedCount = 0;
secondClient.ApplicationMessageReceivedAsync += args =>
{
Interlocked.Increment(ref secondClientReceivedCount);
return Task.CompletedTask;
};
await secondClient.SubscribeAsync("TestTopic2");
await firstClient.PublishAsync(
new MqttApplicationMessage
{
Topic = "TestTopic2",
Payload = new byte[] { 1, 2, 3, 4 }
});
await testEnvironment.Server.InjectApplicationMessage(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
Topic = "TestTopic2",
Payload = new byte[] { 1, 2, 3, 4 }
}));
// Ensure first client still works
await firstClient.PublishAsync(
new MqttApplicationMessage
{
Topic = "TestTopic1",
Payload = new byte[] { 1, 2, 3, 4 }
});
await testEnvironment.Server.InjectApplicationMessage(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
Topic = "TestTopic1",
Payload = new byte[] { 1, 2, 3, 4 }
}));
await Task.Delay(1000);
Assert.AreEqual(6, publishedCount);
Assert.AreEqual(4, firstClientReceivedCount);
Assert.AreEqual(2, secondClientReceivedCount);
await server.StopAsync();
}
static async Task<IMqttClient> ConnectClientAsync(TestEnvironment testEnvironment, Func<MqttClientCertificateValidationEventArgs, bool> certValidator)
{
var clientOptionsBuilder = testEnvironment.Factory.CreateClientOptionsBuilder();
clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString())
.WithTcpServer("localhost", 8883)
.WithTls(
tls =>
{
tls.UseTls = true;
tls.SslProtocol = SslProtocols.Tls12;
tls.CertificateValidationHandler = certValidator;
});
var clientOptions = clientOptionsBuilder.Build();
return await testEnvironment.ConnectClient(clientOptions);
}
sealed class CertificateProvider : ICertificateProvider
{
public X509Certificate2 CurrentCertificate { get; set; }
public X509Certificate2 GetCertificate()
{
return CurrentCertificate;
}
}
}
}
#endif
@@ -2,10 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
{ {
@@ -28,5 +32,28 @@ namespace MQTTnet.Tests.Server
await client.UnsubscribeAsync("#"); await client.UnsubscribeAsync("#");
} }
} }
[TestMethod]
public async Task Intercept_Unsubscribe_With_User_Properties()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
InterceptingUnsubscriptionEventArgs eventArgs = null;
server.InterceptingUnsubscriptionAsync += e =>
{
eventArgs = e;
return CompletedTask.Instance;
};
var client = await testEnvironment.ConnectClient();
var unsubscribeOptions = testEnvironment.Factory.CreateUnsubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build();
await client.UnsubscribeAsync(unsubscribeOptions);
CollectionAssert.AreEqual(unsubscribeOptions.UserProperties.ToList(), eventArgs.UserProperties);
}
}
} }
} }
@@ -150,18 +150,8 @@ namespace MQTTnet.Tests
{ {
Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter)); Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter));
ulong topicHash; MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
ulong topicHashMask; MqttTopicHash.Calculate(filter, out var filterTopicHash, out var filterTopicHashMask, out _);
bool topicHasWildcard;
MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
ulong filterTopicHash;
ulong filterTopicHashMask;
bool filterTopicHasWildcard;
MqttSubscription.CalculateTopicHash(filter, out filterTopicHash, out filterTopicHashMask, out filterTopicHasWildcard);
if (expectedResult == MqttTopicFilterCompareResult.IsMatch) if (expectedResult == MqttTopicFilterCompareResult.IsMatch)
{ {
+34 -23
View File
@@ -27,19 +27,15 @@ namespace MQTTnet.Adapter
readonly byte[] _fixedHeaderBuffer = new byte[2]; readonly byte[] _fixedHeaderBuffer = new byte[2];
readonly MqttNetSourceLogger _logger; readonly MqttNetSourceLogger _logger;
readonly MqttPacketInspector _packetInspector;
readonly byte[] _singleByteBuffer = new byte[1]; readonly byte[] _singleByteBuffer = new byte[1];
readonly AsyncLock _syncRoot = new AsyncLock(); readonly AsyncLock _syncRoot = new AsyncLock();
long _bytesReceived; Statistics _statistics; // mutable struct, don't make readonly!
long _bytesSent;
public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, MqttPacketInspector packetInspector, IMqttNetLogger logger) public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, IMqttNetLogger logger)
{ {
_channel = channel ?? throw new ArgumentNullException(nameof(channel)); _channel = channel ?? throw new ArgumentNullException(nameof(channel));
_packetInspector = packetInspector;
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter)); PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
@@ -51,9 +47,9 @@ namespace MQTTnet.Adapter
_logger = logger.WithSource(nameof(MqttChannelAdapter)); _logger = logger.WithSource(nameof(MqttChannelAdapter));
} }
public long BytesReceived => Interlocked.Read(ref _bytesReceived); public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived);
public long BytesSent => Interlocked.Read(ref _bytesSent); public long BytesSent => Volatile.Read(ref _statistics._bytesSent);
public X509Certificate2 ClientCertificate => _channel.ClientCertificate; public X509Certificate2 ClientCertificate => _channel.ClientCertificate;
@@ -65,6 +61,10 @@ namespace MQTTnet.Adapter
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public MqttPacketInspector PacketInspector { get; set; }
public bool AllowPacketFragmentation { get; set; } = true;
public async Task ConnectAsync(CancellationToken cancellationToken) public async Task ConnectAsync(CancellationToken cancellationToken)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -127,7 +127,7 @@ namespace MQTTnet.Adapter
try try
{ {
_packetInspector?.BeginReceivePacket(); PacketInspector?.BeginReceivePacket();
ReceivedMqttPacket receivedPacket; ReceivedMqttPacket receivedPacket;
var receivedPacketTask = ReceiveAsync(cancellationToken); var receivedPacketTask = ReceiveAsync(cancellationToken);
@@ -145,9 +145,9 @@ namespace MQTTnet.Adapter
return null; return null;
} }
_packetInspector?.EndReceivePacket(); PacketInspector?.EndReceivePacket();
Interlocked.Add(ref _bytesSent, receivedPacket.TotalLength); Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength);
if (PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.Unknown) if (PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.Unknown)
{ {
@@ -183,8 +183,7 @@ namespace MQTTnet.Adapter
public void ResetStatistics() public void ResetStatistics()
{ {
Interlocked.Exchange(ref _bytesReceived, 0L); _statistics.Reset();
Interlocked.Exchange(ref _bytesSent, 0L);
} }
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken) public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
@@ -202,21 +201,21 @@ namespace MQTTnet.Adapter
try try
{ {
var packetBuffer = PacketFormatterAdapter.Encode(packet); var packetBuffer = PacketFormatterAdapter.Encode(packet);
_packetInspector?.BeginSendPacket(packetBuffer); PacketInspector?.BeginSendPacket(packetBuffer);
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet); _logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
if (packetBuffer.Payload.Count > 0) if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation)
{
await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false);
}
else
{ {
await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false); await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false);
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false); await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
} }
else
{
await _channel.WriteAsync(packetBuffer.Packet, true, cancellationToken).ConfigureAwait(false);
}
Interlocked.Add(ref _bytesReceived, packetBuffer.Length); Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -275,7 +274,7 @@ namespace MQTTnet.Adapter
return 0; return 0;
} }
_packetInspector?.FillReceiveBuffer(_singleByteBuffer); PacketInspector?.FillReceiveBuffer(_singleByteBuffer);
encodedByte = _singleByteBuffer[0]; encodedByte = _singleByteBuffer[0];
@@ -328,7 +327,7 @@ namespace MQTTnet.Adapter
totalBytesRead += bytesRead; totalBytesRead += bytesRead;
} }
_packetInspector?.FillReceiveBuffer(buffer); PacketInspector?.FillReceiveBuffer(buffer);
var hasRemainingLength = buffer[1] != 0; var hasRemainingLength = buffer[1] != 0;
if (!hasRemainingLength) if (!hasRemainingLength)
@@ -412,7 +411,7 @@ namespace MQTTnet.Adapter
bodyOffset += readBytes; bodyOffset += readBytes;
} while (bodyOffset < bodyLength); } while (bodyOffset < bodyLength);
_packetInspector?.FillReceiveBuffer(body); PacketInspector?.FillReceiveBuffer(body);
var bodySegment = new ArraySegment<byte>(body, 0, bodyLength); var bodySegment = new ArraySegment<byte>(body, 0, bodyLength);
return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength); return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength);
@@ -459,5 +458,17 @@ namespace MQTTnet.Adapter
throw new MqttCommunicationException(exception); throw new MqttCommunicationException(exception);
} }
struct Statistics
{
public long _bytesReceived;
public long _bytesSent;
public void Reset()
{
Volatile.Write(ref _bytesReceived, 0);
Volatile.Write(ref _bytesSent, 0);
}
}
} }
} }
@@ -82,12 +82,7 @@ namespace MQTTnet.Adapter
{ {
try try
{ {
var eventArgs = new InspectMqttPacketEventArgs var eventArgs = new InspectMqttPacketEventArgs(direction, buffer);
{
Buffer = buffer,
Direction = direction
};
_asyncEvent.InvokeAsync(eventArgs).GetAwaiter().GetResult(); _asyncEvent.InvokeAsync(eventArgs).GetAwaiter().GetResult();
} }
catch (Exception exception) catch (Exception exception)
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets the authentication result. /// Gets the authentication result.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttClientConnectResult ConnectResult { get; } public MqttClientConnectResult ConnectResult { get; }
} }
@@ -2,20 +2,35 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public sealed class MqttClientDisconnectOptions public sealed class MqttClientDisconnectOptions
{ {
/// <summary> /// <summary>
/// Gets or sets the reason code. /// Gets or sets the reason code.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttClientDisconnectReason Reason { get; set; } = MqttClientDisconnectReason.NormalDisconnection; public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection;
/// <summary> /// <summary>
/// Gets or sets the reason string. /// Gets or sets the reason string.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string ReasonString { get; set; } public string ReasonString { get; set; }
/// <summary>
/// Gets or sets the session expiry interval.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public uint SessionExpiryInterval { get; set; }
/// <summary>
/// Gets or sets the user properties.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
} }
} }
@@ -2,12 +2,34 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public sealed class MqttClientDisconnectOptionsBuilder public sealed class MqttClientDisconnectOptionsBuilder
{ {
MqttClientDisconnectReason _reason = MqttClientDisconnectReason.NormalDisconnection; MqttClientDisconnectOptionsReason _reason = MqttClientDisconnectOptionsReason.NormalDisconnection;
string _reasonString; string _reasonString;
uint _sessionExpiryInterval;
List<MqttUserProperty> _userProperties;
public MqttClientDisconnectOptions Build()
{
return new MqttClientDisconnectOptions
{
Reason = _reason,
ReasonString = _reasonString,
UserProperties = _userProperties,
SessionExpiryInterval = _sessionExpiryInterval
};
}
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectOptionsReason value)
{
_reason = value;
return this;
}
public MqttClientDisconnectOptionsBuilder WithReasonString(string value) public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
{ {
@@ -15,19 +37,27 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectReason value) public MqttClientDisconnectOptionsBuilder WithSessionExpiryInterval(uint value)
{ {
_reason = value; _sessionExpiryInterval = value;
return this; return this;
} }
public MqttClientDisconnectOptions Build() public MqttClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> userProperties)
{ {
return new MqttClientDisconnectOptions _userProperties = userProperties;
return this;
}
public MqttClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
{ {
Reason = _reason, if (_userProperties == null)
ReasonString = _reasonString {
}; _userProperties = new List<MqttUserProperty>();
}
_userProperties.Add(new MqttUserProperty(name, value));
return this;
} }
} }
} }
@@ -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
{
/// <summary>
/// This enum only contains values which are valid when a client sends the reason to the server.
/// </summary>
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
}
}
@@ -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 != MqttClientDisconnectOptionsReason.NormalDisconnection)
{
Throw(nameof(options.Reason));
}
}
static void Throw(string featureName)
{
throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
}
}
}
@@ -3,27 +3,47 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using MQTTnet.Packets;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public sealed class MqttClientDisconnectedEventArgs : EventArgs public sealed class MqttClientDisconnectedEventArgs : EventArgs
{ {
public bool ClientWasConnected { get; internal set; } public MqttClientDisconnectedEventArgs(
bool clientWasConnected,
MqttClientConnectResult connectResult,
MqttClientDisconnectReason reason,
string reasonString,
List<MqttUserProperty> 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; }
/// <summary> /// <summary>
/// Gets the authentication result. /// Gets the authentication result.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttClientConnectResult ConnectResult { get; internal set; } public MqttClientConnectResult ConnectResult { get; }
public Exception Exception { get; }
/// <summary> /// <summary>
/// Gets or sets the reason. /// Gets or sets the reason.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttClientDisconnectReason Reason { get; internal set; } public MqttClientDisconnectReason Reason { get; }
public string ReasonString { get; internal set; } public string ReasonString { get; }
public List<MqttUserProperty> UserProperties { get; }
} }
} }
+1 -1
View File
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync; event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<InspectMqttPacketEventArgs, Task> InspectPackage; event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
bool IsConnected { get; } bool IsConnected { get; }
+158 -86
View File
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
@@ -36,13 +37,20 @@ namespace MQTTnet.Client
readonly IMqttNetLogger _rootLogger; readonly IMqttNetLogger _rootLogger;
IMqttChannelAdapter _adapter; IMqttChannelAdapter _adapter;
CancellationTokenSource _backgroundCancellationTokenSource;
bool _cleanDisconnectInitiated; bool _cleanDisconnectInitiated;
volatile int _connectionStatus; 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; string _disconnectReasonString;
List<MqttUserProperty> _disconnectUserProperties;
Task _keepAlivePacketsSenderTask; Task _keepAlivePacketsSenderTask;
DateTime _lastPacketSentTimestamp; DateTime _lastPacketSentTimestamp;
CancellationTokenSource _mqttClientAlive;
MqttPacketDispatcher _packetDispatcher; MqttPacketDispatcher _packetDispatcher;
Task _packetReceiverTask; Task _packetReceiverTask;
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue; AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
@@ -79,7 +87,7 @@ namespace MQTTnet.Client
remove => _disconnectedEvent.RemoveHandler(value); remove => _disconnectedEvent.RemoveHandler(value);
} }
public event Func<InspectMqttPacketEventArgs, Task> InspectPackage public event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync
{ {
add => _inspectPacketEvent.AddHandler(value); add => _inspectPacketEvent.AddHandler(value);
remove => _inspectPacketEvent.RemoveHandler(value); remove => _inspectPacketEvent.RemoveHandler(value);
@@ -91,18 +99,8 @@ namespace MQTTnet.Client
public async Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default) public async Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default)
{ {
if (options == null) ThrowIfOptionsInvalid(options);
{
throw new ArgumentNullException(nameof(options));
}
if (options.ChannelOptions == null)
{
throw new ArgumentException("ChannelOptions are not set.");
}
ThrowIfConnected("It is not allowed to connect with a server after the connection is established."); ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
ThrowIfDisposed(); ThrowIfDisposed();
if (CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connecting, MqttClientConnectionStatus.Disconnected) != MqttClientConnectionStatus.Disconnected) if (CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connecting, MqttClientConnectionStatus.Disconnected) != MqttClientConnectionStatus.Disconnected)
@@ -121,28 +119,29 @@ namespace MQTTnet.Client
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options)); await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
} }
Cleanup();
_packetIdentifierProvider.Reset(); _packetIdentifierProvider.Reset();
_packetDispatcher = new MqttPacketDispatcher(); _packetDispatcher = new MqttPacketDispatcher();
_backgroundCancellationTokenSource = new CancellationTokenSource(); _mqttClientAlive = new CancellationTokenSource();
var backgroundCancellationToken = _backgroundCancellationTokenSource.Token; var mqttClientAliveToken = _mqttClientAlive.Token;
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_inspectPacketEvent, _rootLogger), _rootLogger); var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_inspectPacketEvent, _rootLogger), _rootLogger);
_adapter = adapter; _adapter = adapter;
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken)) if (cancellationToken.CanBeCanceled)
{ {
_logger.Verbose("Trying to connect with server '{0}'.", options.ChannelOptions); connectResult = await ConnectInternal(cancellationToken).ConfigureAwait(false);
await adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false); }
_logger.Verbose("Connection with server established."); else
{
_publishPacketReceiverQueue?.Dispose(); // Fall back to the general timeout specified in the options if the user passed
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>(); // CancellationToken.None or similar.
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken); using (var timeout = new CancellationTokenSource(Options.Timeout))
{
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken); connectResult = await ConnectInternal(timeout.Token).ConfigureAwait(false);
}
connectResult = await AuthenticateAsync(options, effectiveCancellationToken.Token).ConfigureAwait(false);
} }
_lastPacketSentTimestamp = DateTime.UtcNow; _lastPacketSentTimestamp = DateTime.UtcNow;
@@ -156,7 +155,7 @@ namespace MQTTnet.Client
if (keepAliveInterval != TimeSpan.Zero) if (keepAliveInterval != TimeSpan.Zero)
{ {
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(backgroundCancellationToken), backgroundCancellationToken); _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(mqttClientAliveToken), mqttClientAliveToken);
} }
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting); CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
@@ -173,7 +172,7 @@ namespace MQTTnet.Client
} }
catch (Exception exception) catch (Exception exception)
{ {
_disconnectReason = MqttClientDisconnectReason.UnspecifiedError; _disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError;
_logger.Error(exception, "Error while connecting with server."); _logger.Error(exception, "Error while connecting with server.");
@@ -193,6 +192,7 @@ namespace MQTTnet.Client
ThrowIfDisposed(); ThrowIfDisposed();
var clientWasConnected = IsConnected; var clientWasConnected = IsConnected;
if (DisconnectIsPendingOrFinished()) if (DisconnectIsPendingOrFinished())
{ {
return; return;
@@ -200,14 +200,34 @@ namespace MQTTnet.Client
try try
{ {
_disconnectReason = MqttClientDisconnectReason.NormalDisconnection; if (!clientWasConnected)
{
ThrowNotConnected();
}
_disconnectReason = (int)options.Reason;
_cleanDisconnectInitiated = true; _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); var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
if (cancellationToken.CanBeCanceled)
{
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false); await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
} }
else
{
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
await SendAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
}
}
} }
finally finally
{ {
@@ -239,6 +259,11 @@ namespace MQTTnet.Client
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotConnected(); ThrowIfNotConnected();
if (Options.ValidateFeatures)
{
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion);
}
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage); var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
switch (applicationMessage.QualityOfServiceLevel) switch (applicationMessage.QualityOfServiceLevel)
@@ -296,13 +321,29 @@ namespace MQTTnet.Client
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic); MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
} }
if (Options.ValidateFeatures)
{
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
}
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotConnected(); ThrowIfNotConnected();
var subscribePacket = MqttPacketFactories.Subscribe.Create(options); var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false); MqttSubAckPacket subAckPacket;
if (cancellationToken.CanBeCanceled)
{
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
}
else
{
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, timeout.Token).ConfigureAwait(false);
}
}
return _clientSubscribeResultFactory.Create(subscribePacket, subAckPacket); return _clientSubscribeResultFactory.Create(subscribePacket, subAckPacket);
} }
@@ -314,13 +355,34 @@ namespace MQTTnet.Client
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
} }
foreach (var topicFilter in options.TopicFilters)
{
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter);
}
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotConnected(); ThrowIfNotConnected();
if (Options.ValidateFeatures)
{
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
}
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options); var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false); MqttUnsubAckPacket unsubAckPacket;
if (cancellationToken.CanBeCanceled)
{
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
}
else
{
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, timeout.Token).ConfigureAwait(false);
}
}
return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket); return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket);
} }
@@ -376,7 +438,7 @@ namespace MQTTnet.Client
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false); var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
var clientConnectResultFactory = new MqttClientConnectResultFactory(); var clientConnectResultFactory = new MqttClientConnectResultFactory();
result = clientConnectResultFactory.Create(connAckPacket, options.ProtocolVersion); result = clientConnectResultFactory.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -397,12 +459,12 @@ namespace MQTTnet.Client
{ {
try try
{ {
_backgroundCancellationTokenSource?.Cancel(false); _mqttClientAlive?.Cancel(false);
} }
finally finally
{ {
_backgroundCancellationTokenSource?.Dispose(); _mqttClientAlive?.Dispose();
_backgroundCancellationTokenSource = null; _mqttClientAlive = null;
_publishPacketReceiverQueue?.Dispose(); _publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = null; _publishPacketReceiverQueue = null;
@@ -420,6 +482,26 @@ namespace MQTTnet.Client
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand); return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand);
} }
async Task<MqttClientConnectResult> 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<MqttPublishPacket>();
_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) async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
{ {
TryInitiateDisconnect(); TryInitiateDisconnect();
@@ -447,9 +529,9 @@ namespace MQTTnet.Client
{ {
_packetDispatcher.Dispose(new MqttClientDisconnectedException(exception)); _packetDispatcher.Dispose(new MqttClientDisconnectedException(exception));
var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender); var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
var publishPacketReceiverTask = WaitForTaskAsync(_publishPacketReceiverTask, sender); var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender); var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger);
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false); await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
} }
@@ -465,14 +547,13 @@ namespace MQTTnet.Client
_logger.Info("Disconnected."); _logger.Info("Disconnected.");
var eventArgs = new MqttClientDisconnectedEventArgs var eventArgs = new MqttClientDisconnectedEventArgs(
{ clientWasConnected,
ClientWasConnected = clientWasConnected, connectResult,
Exception = exception, (MqttClientDisconnectReason)_disconnectReason,
ConnectResult = connectResult, _disconnectReasonString,
Reason = _disconnectReason, _disconnectUserProperties,
ReasonString = _disconnectReasonString exception);
};
// This handler must be executed in a new thread because otherwise a dead lock may happen // This handler must be executed in a new thread because otherwise a dead lock may happen
// when trying to reconnect in that handler etc. // when trying to reconnect in that handler etc.
@@ -535,7 +616,6 @@ namespace MQTTnet.Client
{ {
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket); var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket); var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
return eventArgs; return eventArgs;
@@ -554,8 +634,9 @@ namespace MQTTnet.Client
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket) Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
{ {
_disconnectReason = (MqttClientDisconnectReason)disconnectPacket.ReasonCode; _disconnectReason = (int)disconnectPacket.ReasonCode;
_disconnectReasonString = disconnectPacket.ReasonString; _disconnectReasonString = disconnectPacket.ReasonString;
_disconnectUserProperties = disconnectPacket.UserProperties;
// Also dispatch disconnect to waiting threads to generate a proper exception. // Also dispatch disconnect to waiting threads to generate a proper exception.
_packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket)); _packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket));
@@ -617,7 +698,6 @@ namespace MQTTnet.Client
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var pubAckPacket = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false); var pubAckPacket = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(pubAckPacket); return _clientPublishResultFactory.Create(pubAckPacket);
} }
@@ -693,7 +773,7 @@ namespace MQTTnet.Client
{ {
if (IsConnected) if (IsConnected)
{ {
throw new MqttProtocolViolationException(message); throw new InvalidOperationException(message);
} }
} }
@@ -701,17 +781,40 @@ namespace MQTTnet.Client
{ {
if (!IsConnected) if (!IsConnected)
{ {
throw new MqttCommunicationException("The client is not connected."); ThrowNotConnected();
} }
} }
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);
}
}
static void ThrowNotConnected()
{
throw new MqttCommunicationException("The client is not connected.");
}
void TryInitiateDisconnect() void TryInitiateDisconnect()
{ {
lock (_disconnectLock) lock (_disconnectLock)
{ {
try try
{ {
_backgroundCancellationTokenSource?.Cancel(false); _mqttClientAlive?.Cancel(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -910,36 +1013,5 @@ namespace MQTTnet.Client
_logger.Verbose("Stopped sending keep alive packets."); _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)
{
}
}
} }
} }
+37 -6
View File
@@ -14,20 +14,28 @@ namespace MQTTnet.Client
{ {
public static class MqttClientExtensions 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<MqttUserProperty> userProperties = null,
CancellationToken cancellationToken = default)
{ {
if (client == null) if (client == null)
{ {
throw new ArgumentNullException(nameof(client)); throw new ArgumentNullException(nameof(client));
} }
return client.DisconnectAsync( var disconnectOptions = new MqttClientDisconnectOptions
new MqttClientDisconnectOptions
{ {
Reason = reason, Reason = reason,
ReasonString = reasonString ReasonString = reasonString,
}, SessionExpiryInterval = sessionExpiryInterval,
CancellationToken.None); UserProperties = userProperties
};
return client.DisconnectAsync(disconnectOptions, cancellationToken);
} }
public static Task<MqttClientPublishResult> PublishBinaryAsync( public static Task<MqttClientPublishResult> PublishBinaryAsync(
@@ -128,6 +136,29 @@ namespace MQTTnet.Client
return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken); return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken);
} }
public static async Task<bool> 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<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default) public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{ {
if (client == null) if (client == null)
@@ -14,16 +14,24 @@ namespace MQTTnet.Client
{ {
/// <summary> /// <summary>
/// Gets or sets the authentication data. /// Gets or sets the authentication data.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public byte[] AuthenticationData { get; set; } public byte[] AuthenticationData { get; set; }
/// <summary> /// <summary>
/// Gets or sets the authentication method. /// Gets or sets the authentication method.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string AuthenticationMethod { get; set; } public string AuthenticationMethod { get; set; }
/// <summary>
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
/// will close the connection when receiving such packets. If such a service is used this flag must
/// be set to _false_.
/// </summary>
public bool AllowPacketFragmentation { get; set; } = true;
public IMqttClientChannelOptions ChannelOptions { get; set; } public IMqttClientChannelOptions ChannelOptions { get; set; }
/// <summary> /// <summary>
@@ -58,6 +66,10 @@ namespace MQTTnet.Client
/// </summary> /// </summary>
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15); public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
/// <summary>
/// Gets or sets the maximum packet size.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public uint MaximumPacketSize { get; set; } public uint MaximumPacketSize { get; set; }
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311; public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
@@ -65,24 +77,26 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets or sets the receive maximum. /// Gets or sets the receive maximum.
/// This gives the maximum length of the receive messages. /// This gives the maximum length of the receive messages.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public ushort ReceiveMaximum { get; set; } public ushort ReceiveMaximum { get; set; }
/// <summary> /// <summary>
/// Gets or sets the request problem information. /// Gets or sets the request problem information.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public bool RequestProblemInformation { get; set; } = true; public bool RequestProblemInformation { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets the request response information. /// Gets or sets the request response information.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public bool RequestResponseInformation { get; set; } public bool RequestResponseInformation { get; set; }
/// <summary> /// <summary>
/// Gets or sets the session expiry interval. /// Gets or sets the session expiry interval.
/// The time after a session expires when it's not actively used. /// The time after a session expires when it's not actively used.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public uint SessionExpiryInterval { get; set; } public uint SessionExpiryInterval { get; set; }
@@ -95,6 +109,7 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets or sets the topic alias maximum. /// Gets or sets the topic alias maximum.
/// This gives the maximum length of the topic alias. /// This gives the maximum length of the topic alias.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public ushort TopicAliasMaximum { get; set; } public ushort TopicAliasMaximum { get; set; }
@@ -103,8 +118,10 @@ namespace MQTTnet.Client
/// client. /// client.
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated /// If successful, this means that loop detection will be more effective and that retained messages will be propagated
/// correctly. /// correctly.
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect /// <remarks>
/// properly. /// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not
/// connect properly.
/// </remarks>
/// </summary> /// </summary>
public bool TryPrivate { get; set; } = true; public bool TryPrivate { get; set; } = true;
@@ -115,28 +132,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 /// 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. /// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept. /// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public List<MqttUserProperty> UserProperties { get; set; } public List<MqttUserProperty> UserProperties { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool ValidateFeatures { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets the content type of the will message. /// Gets or sets the content type of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string WillContentType { get; set; } public string WillContentType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the correlation data of the will message. /// Gets or sets the correlation data of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public byte[] WillCorrelationData { get; set; } public byte[] WillCorrelationData { get; set; }
/// <summary> /// <summary>
/// Gets or sets the will delay interval. /// Gets or sets the will delay interval.
/// This is the time between the client disconnect and the time the will message will be sent. /// This is the time between the client disconnect and the time the will message will be sent.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public uint WillDelayInterval { get; set; } public uint WillDelayInterval { get; set; }
/// <summary> /// <summary>
/// Gets or sets the message expiry interval of the will message. /// Gets or sets the message expiry interval of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public uint WillMessageExpiryInterval { get; set; } public uint WillMessageExpiryInterval { get; set; }
@@ -147,8 +177,9 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets or sets the payload format indicator of the will message. /// Gets or sets the payload format indicator of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; } public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified;
/// <summary> /// <summary>
/// Gets or sets the QoS level of the will message. /// Gets or sets the QoS level of the will message.
@@ -157,6 +188,7 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets or sets the response topic of the will message. /// Gets or sets the response topic of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string WillResponseTopic { get; set; } public string WillResponseTopic { get; set; }
@@ -172,6 +204,7 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets or sets the user properties of the will message. /// Gets or sets the user properties of the will message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public List<MqttUserProperty> WillUserProperties { get; set; } public List<MqttUserProperty> WillUserProperties { get; set; }
@@ -46,7 +46,7 @@ namespace MQTTnet.Client
Certificates = _tlsParameters.Certificates?.ToList(), Certificates = _tlsParameters.Certificates?.ToList(),
#endif #endif
#if NETCOREAPP3_1 || NET5_0_OR_GREATER #if NETCOREAPP3_1_OR_GREATER
ApplicationProtocols = _tlsParameters.ApplicationProtocols, ApplicationProtocols = _tlsParameters.ApplicationProtocols,
#endif #endif
}; };
@@ -74,6 +74,8 @@ namespace MQTTnet.Client
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions; _options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
MqttClientOptionsValidator.ThrowIfNotSupported(_options);
return _options; return _options;
} }
@@ -84,12 +86,24 @@ namespace MQTTnet.Client
return this; return this;
} }
/// <summary>
/// Clean session is used in MQTT versions below 5.0.0. It is the same as setting "CleanStart".
/// </summary>
public MqttClientOptionsBuilder WithCleanSession(bool value = true) public MqttClientOptionsBuilder WithCleanSession(bool value = true)
{ {
_options.CleanSession = value; _options.CleanSession = value;
return this; return this;
} }
/// <summary>
/// Clean start is used in MQTT versions 5.0.0 and higher. It is the same as setting "CleanSession".
/// </summary>
public MqttClientOptionsBuilder WithCleanStart(bool value = true)
{
_options.CleanSession = value;
return this;
}
public MqttClientOptionsBuilder WithClientId(string value) public MqttClientOptionsBuilder WithClientId(string value)
{ {
_options.ClientId = value; _options.ClientId = value;
@@ -186,6 +200,18 @@ namespace MQTTnet.Client
return WithKeepAlivePeriod(TimeSpan.Zero); return WithKeepAlivePeriod(TimeSpan.Zero);
} }
/// <summary>
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
/// will close the connection when receiving such packets. If such a service is used this flag must
/// be set to _true_.
/// </summary>
public MqttClientOptionsBuilder WithoutPacketFragmentation()
{
_options.AllowPacketFragmentation = false;
return this;
}
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value) public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
{ {
if (value == MqttProtocolVersion.Unknown) if (value == MqttProtocolVersion.Unknown)
@@ -337,16 +363,6 @@ namespace MQTTnet.Client
public MqttClientOptionsBuilder WithUserProperty(string name, string value) 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) if (_options.UserProperties == null)
{ {
_options.UserProperties = new List<MqttUserProperty>(); _options.UserProperties = new List<MqttUserProperty>();
@@ -381,6 +397,18 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
{
_options.WillContentType = willContentType;
return this;
}
public MqttClientOptionsBuilder WithWillCorrelationData(byte[] willCorrelationData)
{
_options.WillCorrelationData = willCorrelationData;
return this;
}
public MqttClientOptionsBuilder WithWillDelayInterval(uint willDelayInterval) public MqttClientOptionsBuilder WithWillDelayInterval(uint willDelayInterval)
{ {
_options.WillDelayInterval = willDelayInterval; _options.WillDelayInterval = willDelayInterval;
@@ -404,45 +432,33 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator)
{
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator;
return this;
}
public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel) public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel)
{ {
_options.WillQualityOfServiceLevel = willQualityOfServiceLevel; _options.WillQualityOfServiceLevel = willQualityOfServiceLevel;
return this; return this;
} }
public MqttClientOptionsBuilder WithWillTopic(string willTopic)
{
_options.WillTopic = willTopic;
return this;
}
public MqttClientOptionsBuilder WithWillRetain(bool willRetain = true)
{
_options.WillRetain = willRetain;
return this;
}
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
{
_options.WillContentType = willContentType;
return this;
}
public MqttClientOptionsBuilder WithWillCorrelationData(byte[] willCorrelationData)
{
_options.WillCorrelationData = willCorrelationData;
return this;
}
public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic) public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic)
{ {
_options.WillResponseTopic = willResponseTopic; _options.WillResponseTopic = willResponseTopic;
return this; return this;
} }
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator) public MqttClientOptionsBuilder WithWillRetain(bool willRetain = true)
{ {
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator; _options.WillRetain = willRetain;
return this;
}
public MqttClientOptionsBuilder WithWillTopic(string willTopic)
{
_options.WillTopic = willTopic;
return this; return this;
} }
@@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Security;
using System.Security.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
@@ -16,7 +15,7 @@ namespace MQTTnet.Client
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; } public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; }
#if NET48 || NETCOREAPP3_1 || NET5 || NET6 #if NET48 || NETCOREAPP3_1_OR_GREATER
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13; public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
#else #else
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/; public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
@@ -28,8 +27,8 @@ namespace MQTTnet.Client
public IEnumerable<X509Certificate> Certificates { get; set; } public IEnumerable<X509Certificate> Certificates { get; set; }
#endif #endif
#if NETCOREAPP3_1 || NET5_0_OR_GREATER #if NETCOREAPP3_1_OR_GREATER
public List<SslApplicationProtocol> ApplicationProtocols { get;set; } public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; }
#endif #endif
public bool AllowUntrustedCertificates { get; set; } public bool AllowUntrustedCertificates { get; set; }
@@ -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.");
}
}
}
@@ -29,13 +29,13 @@ namespace MQTTnet.Client
public List<X509Certificate> Certificates { get; set; } public List<X509Certificate> Certificates { get; set; }
#endif #endif
#if NETCOREAPP3_1 || NET5_0_OR_GREATER #if NETCOREAPP3_1_OR_GREATER
public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; } public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; }
public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; } public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; }
#endif #endif
#if NET48 || NETCOREAPP3_1 || NET5 || NET6 #if NET48 || NETCOREAPP3_1_OR_GREATER
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13; public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
#else #else
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/; public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
@@ -9,9 +9,18 @@ namespace MQTTnet.Client
{ {
public sealed class MqttClientPublishResult public sealed class MqttClientPublishResult
{ {
public MqttClientPublishResult(ushort? packetIdentifier, MqttClientPublishReasonCode reasonCode, string reasonString, IReadOnlyCollection<MqttUserProperty> userProperties)
{
PacketIdentifier = packetIdentifier;
ReasonCode = reasonCode;
ReasonString = reasonString;
UserProperties = userProperties;
}
/// <summary> /// <summary>
/// Returns if the overall status of the publish is a success. This can be the reason code _Success_ or /// Returns if the overall status of the publish is a success. This can be the reason code _Success_ or
/// _NoMatchingSubscribers_. _NoMatchingSubscribers_ usually indicates only that no other client is interested in the topic but overall transmit /// _NoMatchingSubscribers_. _NoMatchingSubscribers_ usually indicates only that no other client is interested in the
/// topic but overall transmit
/// to the server etc. was a success. /// to the server etc. was a success.
/// </summary> /// </summary>
public bool IsSuccess => ReasonCode == MqttClientPublishReasonCode.Success || ReasonCode == MqttClientPublishReasonCode.NoMatchingSubscribers; public bool IsSuccess => ReasonCode == MqttClientPublishReasonCode.Success || ReasonCode == MqttClientPublishReasonCode.NoMatchingSubscribers;
@@ -19,19 +28,19 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets the packet identifier which was used for this publish. /// Gets the packet identifier which was used for this publish.
/// </summary> /// </summary>
public ushort? PacketIdentifier { get; set; } public ushort? PacketIdentifier { get; }
/// <summary> /// <summary>
/// Gets or sets the reason code. /// Gets or sets the reason code.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttClientPublishReasonCode ReasonCode { get; set; } = MqttClientPublishReasonCode.Success; public MqttClientPublishReasonCode ReasonCode { get; } = MqttClientPublishReasonCode.Success;
/// <summary> /// <summary>
/// Gets or sets the reason string. /// Gets or sets the reason string.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string ReasonString { get; set; } public string ReasonString { get; }
/// <summary> /// <summary>
/// Gets or sets the user properties. /// Gets or sets the user properties.
@@ -40,8 +49,8 @@ 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 /// 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. /// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept. /// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal set; } public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
} }
} }
@@ -10,25 +10,23 @@ namespace MQTTnet.Client
{ {
public sealed class MqttClientPublishResultFactory public sealed class MqttClientPublishResultFactory
{ {
static readonly MqttClientPublishResult EmptySuccessResult = new MqttClientPublishResult();
static readonly IReadOnlyCollection<MqttUserProperty> EmptyUserProperties = new List<MqttUserProperty>(); static readonly IReadOnlyCollection<MqttUserProperty> EmptyUserProperties = new List<MqttUserProperty>();
static readonly MqttClientPublishResult AtMostOnceSuccessResult = new MqttClientPublishResult(null, MqttClientPublishReasonCode.Success, null, EmptyUserProperties);
public MqttClientPublishResult Create(MqttPubAckPacket pubAckPacket) public MqttClientPublishResult Create(MqttPubAckPacket pubAckPacket)
{ {
// QoS 0 has no response. So we treat it as a success always. // QoS 0 has no response. So we treat it as a success always.
if (pubAckPacket == null) if (pubAckPacket == null)
{ {
return EmptySuccessResult; return AtMostOnceSuccessResult;
} }
var result = new MqttClientPublishResult var result = new MqttClientPublishResult(
{ pubAckPacket.PacketIdentifier,
// Both enums have the same values. So it can be easily converted. // Both enums have the same values. So it can be easily converted.
ReasonCode = (MqttClientPublishReasonCode)(int)pubAckPacket.ReasonCode, (MqttClientPublishReasonCode)(int)pubAckPacket.ReasonCode,
PacketIdentifier = pubAckPacket.PacketIdentifier, pubAckPacket.ReasonString,
ReasonString = pubAckPacket.ReasonString, pubAckPacket.UserProperties ?? EmptyUserProperties);
UserProperties = pubAckPacket.UserProperties ?? EmptyUserProperties
};
return result; return result;
} }
@@ -37,43 +35,28 @@ namespace MQTTnet.Client
{ {
if (pubRecPacket == null || pubCompPacket == null) if (pubRecPacket == null || pubCompPacket == null)
{ {
return new MqttClientPublishResult var packetIdentifier = pubRecPacket?.PacketIdentifier ?? pubCompPacket?.PacketIdentifier;
{ return new MqttClientPublishResult(packetIdentifier, MqttClientPublishReasonCode.ImplementationSpecificError, null, EmptyUserProperties);
ReasonCode = MqttClientPublishReasonCode.UnspecifiedError
};
} }
MqttClientPublishResult result;
// The PUBCOMP is the last packet in QoS 2. So we use the results from that instead of PUBREC. // The PUBCOMP is the last packet in QoS 2. So we use the results from that instead of PUBREC.
if (pubCompPacket.ReasonCode == MqttPubCompReasonCode.PacketIdentifierNotFound) if (pubCompPacket.ReasonCode == MqttPubCompReasonCode.PacketIdentifierNotFound)
{ {
result = new MqttClientPublishResult return new MqttClientPublishResult(
{ pubCompPacket.PacketIdentifier,
PacketIdentifier = pubCompPacket.PacketIdentifier, MqttClientPublishReasonCode.UnspecifiedError,
ReasonCode = MqttClientPublishReasonCode.UnspecifiedError, pubCompPacket.ReasonString,
ReasonString = pubCompPacket.ReasonString, pubCompPacket.UserProperties ?? EmptyUserProperties);
UserProperties = pubCompPacket.UserProperties ?? EmptyUserProperties
};
return result;
} }
result = new MqttClientPublishResult var reasonCode = MqttClientPublishReasonCode.Success;
{
PacketIdentifier = pubCompPacket.PacketIdentifier,
ReasonCode = MqttClientPublishReasonCode.Success,
ReasonString = pubCompPacket.ReasonString,
UserProperties = pubCompPacket.UserProperties ?? EmptyUserProperties
};
if (pubRecPacket.ReasonCode != MqttPubRecReasonCode.Success) if (pubRecPacket.ReasonCode != MqttPubRecReasonCode.Success)
{ {
// Both enums share the same values. // Both enums share the same values.
result.ReasonCode = (MqttClientPublishReasonCode)pubRecPacket.ReasonCode; reasonCode = (MqttClientPublishReasonCode)pubRecPacket.ReasonCode;
} }
return result; return new MqttClientPublishResult(pubCompPacket.PacketIdentifier, reasonCode, null, pubCompPacket.UserProperties ?? EmptyUserProperties);
} }
} }
} }
@@ -9,27 +9,31 @@ namespace MQTTnet.Client
{ {
public sealed class MqttClientSubscribeOptions public sealed class MqttClientSubscribeOptions
{ {
/// <summary>
/// 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.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public uint SubscriptionIdentifier { get; set; }
/// <summary> /// <summary>
/// Gets or sets a list of topic filters the client wants to subscribe to. /// Gets or sets a list of topic filters the client wants to subscribe to.
/// Topic filters can include regular topics or wild cards. /// Topic filters can include regular topics or wild cards.
/// </summary> /// </summary>
public List<MqttTopicFilter> TopicFilters { get; set; } = new List<MqttTopicFilter>(); public List<MqttTopicFilter> TopicFilters { get; set; } = new List<MqttTopicFilter>();
/// <summary>
/// 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.
/// </summary>
public uint SubscriptionIdentifier { get; set; }
/// <summary> /// <summary>
/// Gets or sets the user properties. /// 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. /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
/// 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. /// 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. /// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public List<MqttUserProperty> UserProperties { get; set; } public List<MqttUserProperty> UserProperties { get; set; }
} }
@@ -14,26 +14,9 @@ namespace MQTTnet.Client
{ {
readonly MqttClientSubscribeOptions _subscribeOptions = new MqttClientSubscribeOptions(); readonly MqttClientSubscribeOptions _subscribeOptions = new MqttClientSubscribeOptions();
/// <summary> public MqttClientSubscribeOptions Build()
/// Adds the user property to the subscribe options.
/// Hint: MQTT 5 feature only.
/// </summary>
/// <param name="name">The property name.</param>
/// <param name="value">The property value.</param>
/// <returns>A new instance of the <see cref="MqttApplicationMessageBuilder"/> class.</returns>
public MqttClientSubscribeOptionsBuilder WithUserProperty(string name, string value)
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); return _subscribeOptions;
if (value == null) throw new ArgumentNullException(nameof(value));
if (_subscribeOptions.UserProperties == null)
{
_subscribeOptions.UserProperties = new List<MqttUserProperty>();
}
_subscribeOptions.UserProperties.Add(new MqttUserProperty(name, value));
return this;
} }
public MqttClientSubscribeOptionsBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier) public MqttClientSubscribeOptionsBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier)
@@ -54,7 +37,8 @@ namespace MQTTnet.Client
bool retainAsPublished = false, bool retainAsPublished = false,
MqttRetainHandling retainHandling = MqttRetainHandling.SendAtSubscribe) MqttRetainHandling retainHandling = MqttRetainHandling.SendAtSubscribe)
{ {
return WithTopicFilter(new MqttTopicFilter return WithTopicFilter(
new MqttTopicFilter
{ {
Topic = topic, Topic = topic,
QualityOfServiceLevel = qualityOfServiceLevel, QualityOfServiceLevel = qualityOfServiceLevel,
@@ -66,7 +50,10 @@ namespace MQTTnet.Client
public MqttClientSubscribeOptionsBuilder WithTopicFilter(Action<MqttTopicFilterBuilder> topicFilterBuilder) public MqttClientSubscribeOptionsBuilder WithTopicFilter(Action<MqttTopicFilterBuilder> topicFilterBuilder)
{ {
if (topicFilterBuilder == null) throw new ArgumentNullException(nameof(topicFilterBuilder)); if (topicFilterBuilder == null)
{
throw new ArgumentNullException(nameof(topicFilterBuilder));
}
var internalTopicFilterBuilder = new MqttTopicFilterBuilder(); var internalTopicFilterBuilder = new MqttTopicFilterBuilder();
topicFilterBuilder(internalTopicFilterBuilder); topicFilterBuilder(internalTopicFilterBuilder);
@@ -76,14 +63,20 @@ namespace MQTTnet.Client
public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilterBuilder topicFilterBuilder) 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()); return WithTopicFilter(topicFilterBuilder.Build());
} }
public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter) 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) if (_subscribeOptions.TopicFilters == null)
{ {
@@ -95,9 +88,20 @@ namespace MQTTnet.Client
return this; return this;
} }
public MqttClientSubscribeOptions Build() /// <summary>
/// Adds the user property to the subscribe options.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttClientSubscribeOptionsBuilder WithUserProperty(string name, string value)
{ {
return _subscribeOptions; if (_subscribeOptions.UserProperties == null)
{
_subscribeOptions.UserProperties = new List<MqttUserProperty>();
}
_subscribeOptions.UserProperties.Add(new MqttUserProperty(name, value));
return this;
} }
} }
} }
@@ -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.");
}
}
}
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using MQTTnet.Packets; using MQTTnet.Packets;
@@ -9,23 +10,34 @@ namespace MQTTnet.Client
{ {
public sealed class MqttClientSubscribeResult public sealed class MqttClientSubscribeResult
{ {
public IReadOnlyCollection<MqttClientSubscribeResultItem> Items { get; internal set; } public MqttClientSubscribeResult(ushort packetIdentifier, IReadOnlyCollection<MqttClientSubscribeResultItem> items, string reasonString, IReadOnlyCollection<MqttUserProperty> userProperties)
{
PacketIdentifier = packetIdentifier;
Items = items ?? throw new ArgumentNullException(nameof(items));
ReasonString = reasonString;
UserProperties = userProperties ?? throw new ArgumentNullException(nameof(userProperties));
}
/// <summary>
/// Gets the result for every topic filter item.
/// </summary>
public IReadOnlyCollection<MqttClientSubscribeResultItem> Items { get; }
/// <summary> /// <summary>
/// Gets the user properties which were part of the SUBACK packet. /// Gets the user properties which were part of the SUBACK packet.
/// MQTTv5 only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal set; } public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
/// <summary> /// <summary>
/// Gets the reason string. /// Gets the reason string.
/// MQTTv5 only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string ReasonString { get; internal set; } public string ReasonString { get; }
/// <summary> /// <summary>
/// Gets the packet identifier which was used. /// Gets the packet identifier which was used.
/// </summary> /// </summary>
public ushort PacketIdentifier { get; internal set; } public ushort PacketIdentifier { get; }
} }
} }
@@ -16,14 +16,20 @@ namespace MQTTnet.Client
public MqttClientSubscribeResult Create(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket) public MqttClientSubscribeResult Create(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket)
{ {
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); if (subscribePacket == null)
if (subAckPacket == null) throw new ArgumentNullException(nameof(subAckPacket)); {
throw new ArgumentNullException(nameof(subscribePacket));
}
if (subAckPacket == null)
{
throw new ArgumentNullException(nameof(subAckPacket));
}
// MQTTv5.0.0 handling. // MQTTv5.0.0 handling.
if (subAckPacket.ReasonCodes.Any() && subAckPacket.ReasonCodes.Count != subscribePacket.TopicFilters.Count) if (subAckPacket.ReasonCodes.Any() && subAckPacket.ReasonCodes.Count != subscribePacket.TopicFilters.Count)
{ {
throw new MqttProtocolViolationException( throw new MqttProtocolViolationException("The reason codes are not matching the topic filters [MQTT-3.9.3-1].");
"The reason codes are not matching the topic filters [MQTT-3.9.3-1].");
} }
var items = new List<MqttClientSubscribeResultItem>(); var items = new List<MqttClientSubscribeResultItem>();
@@ -32,26 +38,13 @@ namespace MQTTnet.Client
items.Add(CreateSubscribeResultItem(i, subscribePacket, subAckPacket)); items.Add(CreateSubscribeResultItem(i, subscribePacket, subAckPacket));
} }
var result = new MqttClientSubscribeResult return new MqttClientSubscribeResult(subAckPacket.PacketIdentifier, items, subAckPacket.ReasonString, subAckPacket.UserProperties ?? EmptyUserProperties);
{
PacketIdentifier = subAckPacket.PacketIdentifier,
ReasonString = subAckPacket.ReasonString,
UserProperties = subAckPacket.UserProperties ?? EmptyUserProperties,
Items = items
};
return result;
} }
static MqttClientSubscribeResultItem CreateSubscribeResultItem(int index, MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket) static MqttClientSubscribeResultItem CreateSubscribeResultItem(int index, MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket)
{ {
var resultCode = (MqttClientSubscribeResultCode) subAckPacket.ReasonCodes[index]; var resultCode = (MqttClientSubscribeResultCode)subAckPacket.ReasonCodes[index];
return new MqttClientSubscribeResultItem(subscribePacket.TopicFilters[index], resultCode);
return new MqttClientSubscribeResultItem
{
TopicFilter = subscribePacket.TopicFilters[index],
ResultCode = resultCode,
};
} }
} }
} }
@@ -2,22 +2,29 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public sealed class MqttClientSubscribeResultItem public sealed class MqttClientSubscribeResultItem
{ {
public MqttClientSubscribeResultItem(MqttTopicFilter topicFilter, MqttClientSubscribeResultCode resultCode)
{
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
ResultCode = resultCode;
}
/// <summary> /// <summary>
/// Gets or sets the topic filter. /// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards. /// The topic filter can contain topics and wildcards.
/// </summary> /// </summary>
public MqttTopicFilter TopicFilter { get; internal set; } public MqttTopicFilter TopicFilter { get; }
/// <summary> /// <summary>
/// Gets or sets the result code. /// Gets or sets the result code.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public MqttClientSubscribeResultCode ResultCode { get; internal set; } public MqttClientSubscribeResultCode ResultCode { get; }
} }
} }
@@ -17,11 +17,13 @@ namespace MQTTnet.Client
/// <summary> /// <summary>
/// Gets or sets the user properties. /// 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. /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
/// 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. /// 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. /// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only. /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public List<MqttUserProperty> UserProperties { get; set; } = new List<MqttUserProperty>(); public List<MqttUserProperty> UserProperties { get; set; }
} }
} }
@@ -8,48 +8,21 @@ using MQTTnet.Packets;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public class MqttClientUnsubscribeOptionsBuilder public sealed class MqttClientUnsubscribeOptionsBuilder
{ {
private readonly MqttClientUnsubscribeOptions _unsubscribeOptions = new MqttClientUnsubscribeOptions(); readonly MqttClientUnsubscribeOptions _unsubscribeOptions = new MqttClientUnsubscribeOptions();
/// <summary> public MqttClientUnsubscribeOptions Build()
/// Adds the user property to the unsubscribe options.
/// Hint: MQTT 5 feature only.
/// </summary>
/// <param name="name">The property name.</param>
/// <param name="value">The property value.</param>
/// <returns>A new instance of the <see cref="MqttClientUnsubscribeOptionsBuilder"/> class.</returns>
public MqttClientUnsubscribeOptionsBuilder WithUserProperty(string name, string value)
{ {
if (name is null) throw new ArgumentNullException(nameof(name)); return _unsubscribeOptions;
if (value is null) throw new ArgumentNullException(nameof(value));
return WithUserProperty(new MqttUserProperty(name, value));
}
/// <summary>
/// Adds the user property to the unsubscribe options.
/// Hint: MQTT 5 feature only.
/// </summary>
/// <param name="userProperty">The user property.</param>
/// <returns>A new instance of the <see cref="MqttClientUnsubscribeOptionsBuilder"/> class.</returns>
public MqttClientUnsubscribeOptionsBuilder WithUserProperty(MqttUserProperty userProperty)
{
if (userProperty is null) throw new ArgumentNullException(nameof(userProperty));
if (_unsubscribeOptions.UserProperties is null)
{
_unsubscribeOptions.UserProperties = new List<MqttUserProperty>();
}
_unsubscribeOptions.UserProperties.Add(userProperty);
return this;
} }
public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(string topic) 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) if (_unsubscribeOptions.TopicFilters is null)
{ {
@@ -63,14 +36,42 @@ namespace MQTTnet.Client
public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter) 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); return WithTopicFilter(topicFilter.Topic);
} }
public MqttClientUnsubscribeOptions Build() /// <summary>
/// Adds the user property to the unsubscribe options.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttClientUnsubscribeOptionsBuilder WithUserProperty(string name, string value)
{ {
return _unsubscribeOptions; return WithUserProperty(new MqttUserProperty(name, value));
}
/// <summary>
/// Adds the user property to the unsubscribe options.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttClientUnsubscribeOptionsBuilder WithUserProperty(MqttUserProperty userProperty)
{
if (userProperty is null)
{
throw new ArgumentNullException(nameof(userProperty));
}
if (_unsubscribeOptions.UserProperties is null)
{
_unsubscribeOptions.UserProperties = new List<MqttUserProperty>();
}
_unsubscribeOptions.UserProperties.Add(userProperty);
return this;
} }
} }
} }
@@ -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.");
}
}
}
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using MQTTnet.Packets; using MQTTnet.Packets;
@@ -9,23 +10,38 @@ namespace MQTTnet.Client
{ {
public sealed class MqttClientUnsubscribeResult public sealed class MqttClientUnsubscribeResult
{ {
public IReadOnlyCollection<MqttClientUnsubscribeResultItem> Items { get; internal set; } public MqttClientUnsubscribeResult(
ushort packetIdentifier,
IReadOnlyCollection<MqttClientUnsubscribeResultItem> items,
string reasonString,
IReadOnlyCollection<MqttUserProperty> userProperties)
{
PacketIdentifier = packetIdentifier;
Items = items ?? throw new ArgumentNullException(nameof(items));
ReasonString = reasonString;
UserProperties = userProperties ?? throw new ArgumentNullException(nameof(userProperties));
}
/// <summary> /// <summary>
/// Gets the user properties which were part of the UNSUBACK packet. /// Gets the result for every topic filter item.
/// MQTTv5 only.
/// </summary> /// </summary>
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal set; } public IReadOnlyCollection<MqttClientUnsubscribeResultItem> Items { get; }
/// <summary>
/// Gets the reason string.
/// MQTTv5 only.
/// </summary>
public string ReasonString { get; internal set; }
/// <summary> /// <summary>
/// Gets the packet identifier which was used. /// Gets the packet identifier which was used.
/// </summary> /// </summary>
public ushort PacketIdentifier { get; internal set; } public ushort PacketIdentifier { get; }
/// <summary>
/// Gets the reason string.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString { get; }
/// <summary>
/// Gets the user properties which were part of the UNSUBACK packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; set; }
} }
} }
@@ -15,14 +15,20 @@ namespace MQTTnet.Client
public MqttClientUnsubscribeResult Create(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket) public MqttClientUnsubscribeResult Create(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket)
{ {
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); if (unsubscribePacket == null)
if (unsubAckPacket == null) throw new ArgumentNullException(nameof(unsubAckPacket)); {
throw new ArgumentNullException(nameof(unsubscribePacket));
}
if (unsubAckPacket == null)
{
throw new ArgumentNullException(nameof(unsubAckPacket));
}
// MQTTv3.1.1 has no reason code at all! // MQTTv3.1.1 has no reason code at all!
if (unsubAckPacket.ReasonCodes != null && unsubAckPacket.ReasonCodes.Count != unsubscribePacket.TopicFilters.Count) if (unsubAckPacket.ReasonCodes != null && unsubAckPacket.ReasonCodes.Count != unsubscribePacket.TopicFilters.Count)
{ {
throw new MqttProtocolViolationException( throw new MqttProtocolViolationException("The return codes are not matching the topic filters [MQTT-3.9.3-1].");
"The return codes are not matching the topic filters [MQTT-3.9.3-1].");
} }
var items = new List<MqttClientUnsubscribeResultItem>(); var items = new List<MqttClientUnsubscribeResultItem>();
@@ -31,15 +37,7 @@ namespace MQTTnet.Client
items.Add(CreateUnsubscribeResultItem(i, unsubscribePacket, unsubAckPacket)); items.Add(CreateUnsubscribeResultItem(i, unsubscribePacket, unsubAckPacket));
} }
var result = new MqttClientUnsubscribeResult return new MqttClientUnsubscribeResult(unsubscribePacket.PacketIdentifier, items, unsubAckPacket.ReasonString, unsubAckPacket.UserProperties ?? EmptyUserProperties);
{
PacketIdentifier = unsubAckPacket.PacketIdentifier,
ReasonString = unsubAckPacket.ReasonString,
UserProperties = unsubAckPacket.UserProperties ?? EmptyUserProperties,
Items = items
};
return result;
} }
static MqttClientUnsubscribeResultItem CreateUnsubscribeResultItem(int index, MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket) static MqttClientUnsubscribeResultItem CreateUnsubscribeResultItem(int index, MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket)
@@ -49,14 +47,10 @@ namespace MQTTnet.Client
if (unsubAckPacket.ReasonCodes != null) if (unsubAckPacket.ReasonCodes != null)
{ {
// MQTTv3.1.1 has no reason code and no return code!. // MQTTv3.1.1 has no reason code and no return code!.
resultCode = (MqttClientUnsubscribeResultCode) unsubAckPacket.ReasonCodes[index]; resultCode = (MqttClientUnsubscribeResultCode)unsubAckPacket.ReasonCodes[index];
} }
return new MqttClientUnsubscribeResultItem return new MqttClientUnsubscribeResultItem(unsubscribePacket.TopicFilters[index], resultCode);
{
TopicFilter = unsubscribePacket.TopicFilters[index],
ResultCode = resultCode
};
} }
} }
} }
@@ -2,20 +2,28 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Client namespace MQTTnet.Client
{ {
public sealed class MqttClientUnsubscribeResultItem public sealed class MqttClientUnsubscribeResultItem
{ {
public MqttClientUnsubscribeResultItem(string topicFilter, MqttClientUnsubscribeResultCode resultCode)
{
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
ResultCode = resultCode;
}
/// <summary>
/// Gets or sets the result code.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttClientUnsubscribeResultCode ResultCode { get; }
/// <summary> /// <summary>
/// Gets or sets the topic filter. /// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards. /// The topic filter can contain topics and wildcards.
/// </summary> /// </summary>
public string TopicFilter { get; internal set; } public string TopicFilter { get; }
/// <summary>
/// Gets or sets the result code.
/// Hint: MQTT 5 feature only.
/// </summary>
public MqttClientUnsubscribeResultCode ResultCode { get; internal set; }
} }
} }
@@ -8,8 +8,14 @@ namespace MQTTnet.Diagnostics
{ {
public sealed class InspectMqttPacketEventArgs : EventArgs public sealed class InspectMqttPacketEventArgs : EventArgs
{ {
public MqttPacketFlowDirection Direction { get; internal set; } public InspectMqttPacketEventArgs(MqttPacketFlowDirection direction, byte[] buffer)
{
Direction = direction;
Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
}
public byte[] Buffer { get; set; } public byte[] Buffer { get; }
public MqttPacketFlowDirection Direction { get; }
} }
} }
@@ -30,6 +30,8 @@ namespace MQTTnet.Diagnostics
return "net5.0"; return "net5.0";
#elif NET6_0 #elif NET6_0
return "net6.0"; return "net6.0";
#elif NET7_0
return "net7.0";
#endif #endif
} }
} }
@@ -19,7 +19,7 @@ namespace MQTTnet.Formatter
return new MqttApplicationMessage return new MqttApplicationMessage
{ {
Topic = publishPacket.Topic, Topic = publishPacket.Topic,
Payload = publishPacket.Payload, PayloadSegment = publishPacket.PayloadSegment,
QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, QualityOfServiceLevel = publishPacket.QualityOfServiceLevel,
Retain = publishPacket.Retain, Retain = publishPacket.Retain,
Dup = publishPacket.Dup, Dup = publishPacket.Dup,
@@ -39,6 +39,8 @@ namespace MQTTnet.Formatter
else else
{ {
packet.ReasonCode = (MqttDisconnectReasonCode)clientDisconnectOptions.Reason; packet.ReasonCode = (MqttDisconnectReasonCode)clientDisconnectOptions.Reason;
packet.UserProperties = clientDisconnectOptions.UserProperties;
packet.SessionExpiryInterval = clientDisconnectOptions.SessionExpiryInterval;
} }
return packet; return packet;
@@ -16,7 +16,7 @@ namespace MQTTnet.Formatter
return new MqttPublishPacket return new MqttPublishPacket
{ {
Topic = publishPacket.Topic, Topic = publishPacket.Topic,
Payload = publishPacket.Payload, PayloadSegment = publishPacket.PayloadSegment,
Retain = publishPacket.Retain, Retain = publishPacket.Retain,
QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, QualityOfServiceLevel = publishPacket.QualityOfServiceLevel,
Dup = publishPacket.Dup, Dup = publishPacket.Dup,
@@ -36,7 +36,7 @@ namespace MQTTnet.Formatter
var packet = new MqttPublishPacket var packet = new MqttPublishPacket
{ {
Topic = applicationMessage.Topic, Topic = applicationMessage.Topic,
Payload = applicationMessage.Payload, PayloadSegment = applicationMessage.PayloadSegment,
QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel,
Retain = applicationMessage.Retain, Retain = applicationMessage.Retain,
Dup = applicationMessage.Dup, Dup = applicationMessage.Dup,
@@ -65,10 +65,16 @@ namespace MQTTnet.Formatter
throw new MqttProtocolViolationException("The CONNECT packet contains no will message (WillFlag)."); throw new MqttProtocolViolationException("The CONNECT packet contains no will message (WillFlag).");
} }
ArraySegment<byte> willMessageBuffer = default;
if (connectPacket.WillMessage?.Length > 0)
{
willMessageBuffer = new ArraySegment<byte>(connectPacket.WillMessage);
}
var packet = new MqttPublishPacket var packet = new MqttPublishPacket
{ {
Topic = connectPacket.WillTopic, Topic = connectPacket.WillTopic,
Payload = connectPacket.WillMessage, PayloadSegment = willMessageBuffer,
QualityOfServiceLevel = connectPacket.WillQoS, QualityOfServiceLevel = connectPacket.WillQoS,
Retain = connectPacket.WillRetain, Retain = connectPacket.WillRetain,
ContentType = connectPacket.WillContentType, ContentType = connectPacket.WillContentType,
@@ -103,9 +103,10 @@ namespace MQTTnet.Formatter.V3
var remainingLength = (uint)(_bufferWriter.Length - 5); var remainingLength = (uint)(_bufferWriter.Length - 5);
var publishPacket = packet as MqttPublishPacket; var publishPacket = packet as MqttPublishPacket;
if (publishPacket?.Payload != null) var payloadSegment = publishPacket?.PayloadSegment;
if (payloadSegment != null)
{ {
remainingLength += (uint)publishPacket.Payload.Length; remainingLength += (uint)payloadSegment.Value.Count;
} }
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
@@ -119,16 +120,11 @@ namespace MQTTnet.Formatter.V3
_bufferWriter.WriteVariableByteInteger(remainingLength); _bufferWriter.WriteVariableByteInteger(remainingLength);
var buffer = _bufferWriter.GetBuffer(); var buffer = _bufferWriter.GetBuffer();
var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset); var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset);
if (publishPacket?.Payload != null) return payloadSegment == null
{ ? new MqttPacketBuffer(firstSegment)
var payloadSegment = new ArraySegment<byte>(publishPacket.Payload, 0, publishPacket.Payload.Length); : new MqttPacketBuffer(firstSegment, payloadSegment.Value);
return new MqttPacketBuffer(firstSegment, payloadSegment);
}
return new MqttPacketBuffer(firstSegment);
} }
MqttPacket DecodeConnAckPacket(ArraySegment<byte> body) MqttPacket DecodeConnAckPacket(ArraySegment<byte> body)
@@ -282,7 +278,7 @@ namespace MQTTnet.Formatter.V3
if (!_bufferReader.EndOfStream) if (!_bufferReader.EndOfStream)
{ {
packet.Payload = _bufferReader.ReadRemainingData(); packet.PayloadSegment = new ArraySegment<byte>(_bufferReader.ReadRemainingData());
} }
return packet; return packet;
@@ -528,7 +528,7 @@ namespace MQTTnet.Formatter.V5
if (!_bufferReader.EndOfStream) if (!_bufferReader.EndOfStream)
{ {
packet.Payload = _bufferReader.ReadRemainingData(); packet.PayloadSegment = new ArraySegment<byte>(_bufferReader.ReadRemainingData());
} }
return packet; return packet;
@@ -37,9 +37,11 @@ namespace MQTTnet.Formatter.V5
var remainingLength = (uint)_bufferWriter.Length - 5; var remainingLength = (uint)_bufferWriter.Length - 5;
var publishPacket = packet as MqttPublishPacket; var publishPacket = packet as MqttPublishPacket;
if (publishPacket?.Payload != null) var payloadSegment = publishPacket?.PayloadSegment;
if (payloadSegment != null)
{ {
remainingLength += (uint)publishPacket.Payload.Length; remainingLength += (uint)payloadSegment.Value.Count;
} }
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
@@ -53,16 +55,11 @@ namespace MQTTnet.Formatter.V5
_bufferWriter.WriteVariableByteInteger(remainingLength); _bufferWriter.WriteVariableByteInteger(remainingLength);
var buffer = _bufferWriter.GetBuffer(); var buffer = _bufferWriter.GetBuffer();
var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset); var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset);
if (publishPacket?.Payload != null) return payloadSegment == null
{ ? new MqttPacketBuffer(firstSegment)
var payloadSegment = new ArraySegment<byte>(publishPacket.Payload, 0, publishPacket.Payload.Length); : new MqttPacketBuffer(firstSegment, payloadSegment.Value);
return new MqttPacketBuffer(firstSegment, payloadSegment);
}
return new MqttPacketBuffer(firstSegment);
} }
byte EncodeAuthPacket(MqttAuthPacket packet) byte EncodeAuthPacket(MqttAuthPacket packet)
@@ -72,8 +72,8 @@ namespace MQTTnet.Implementations
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval) as int? ?? 0; get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, value); set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, value);
#else #else
get { 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 net5.0."); } set { throw new NotSupportedException("TcpKeepAliveInterval requires at least netcoreapp3.0."); }
#endif #endif
} }
@@ -83,8 +83,8 @@ namespace MQTTnet.Implementations
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount) as int? ?? 0; get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, value); set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, value);
#else #else
get { 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 net5.0."); } set { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least netcoreapp3.0."); }
#endif #endif
} }
@@ -94,8 +94,8 @@ namespace MQTTnet.Implementations
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime) as int? ?? 0; get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, value); set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, value);
#else #else
get { 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 net5.0."); } set { throw new NotSupportedException("TcpKeepAliveTime requires at least netcoreapp3.0."); }
#endif #endif
} }
@@ -155,7 +155,7 @@ namespace MQTTnet.Implementations
} }
catch (ObjectDisposedException) 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; return null;
} }
} }
@@ -181,17 +181,18 @@ namespace MQTTnet.Implementations
try try
{ {
#if NET5_0_OR_GREATER #if NETCOREAPP3_0_OR_GREATER
if (_networkStream != null) if (_networkStream != null)
{ {
await _networkStream.DisposeAsync().ConfigureAwait(false); await _networkStream.DisposeAsync().ConfigureAwait(false);
} }
await _socket.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
#else #else
_networkStream?.Dispose(); _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 // Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(_socketDisposeAction)) using (cancellationToken.Register(_socketDisposeAction))
{ {
@@ -220,7 +221,7 @@ namespace MQTTnet.Implementations
} }
catch (ObjectDisposedException) 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(); cancellationToken.ThrowIfCancellationRequested();
} }
} }
@@ -247,38 +248,44 @@ namespace MQTTnet.Implementations
_socket.Listen(connectionBacklog); _socket.Listen(connectionBacklog);
} }
#if NET452 || NET461
public async Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags) public async Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
{ {
try try
{ {
#if NET452 || NET461
return await Task.Factory.FromAsync(SocketWrapper.BeginReceive, _socket.EndReceive, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false); 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) 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; return -1;
} }
} }
#else
public Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
{
return _socket.ReceiveAsync(buffer, socketFlags);
}
#endif
#if NET452 || NET461
public async Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags) public async Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
{ {
try try
{ {
#if NET452 || NET461
await Task.Factory.FromAsync(SocketWrapper.BeginSend, _socket.EndSend, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false); 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) 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<byte> buffer, SocketFlags socketFlags)
{
return _socket.SendAsync(buffer, socketFlags);
}
#endif
#if NET452 || NET461 #if NET452 || NET461
sealed class SocketWrapper sealed class SocketWrapper
@@ -40,7 +40,12 @@ namespace MQTTnet.Implementations
var bufferWriter = new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax); var bufferWriter = new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax);
var packetFormatterAdapter = new MqttPacketFormatterAdapter(options.ProtocolVersion, bufferWriter); var packetFormatterAdapter = new MqttPacketFormatterAdapter(options.ProtocolVersion, bufferWriter);
return new MqttChannelAdapter(channel, packetFormatterAdapter, packetInspector, logger);
return new MqttChannelAdapter(channel, packetFormatterAdapter, logger)
{
AllowPacketFragmentation = options.AllowPacketFragmentation,
PacketInspector = packetInspector
};
} }
} }
} }
@@ -103,7 +103,7 @@ namespace MQTTnet.Implementations
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback); var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
try try
{ {
#if NETCOREAPP3_1 || NET5_0_OR_GREATER #if NETCOREAPP3_1_OR_GREATER
var sslOptions = new SslClientAuthenticationOptions var sslOptions = new SslClientAuthenticationOptions
{ {
ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols, ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols,
@@ -127,7 +127,7 @@ namespace MQTTnet.Implementations
} }
catch catch
{ {
#if NETSTANDARD2_1 || NETCOREAPP3_1 || NET5_0_OR_GREATER #if NETSTANDARD2_1 || NETCOREAPP3_1_OR_GREATER
await sslStream.DisposeAsync().ConfigureAwait(false); await sslStream.DisposeAsync().ConfigureAwait(false);
#else #else
sslStream.Dispose(); sslStream.Dispose();

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