mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be0c75439c | ||
|
|
9295626503 | ||
|
|
18614d4e81 | ||
|
|
560e80455f | ||
|
|
a97e88be29 | ||
|
|
3b5667c882 | ||
|
|
0790e54794 | ||
|
|
94f9fac2bd | ||
|
|
8ad86928f1 | ||
|
|
d8e5936d45 | ||
|
|
caa717544b | ||
|
|
66b77f8d6d | ||
|
|
ab5264c8ca | ||
|
|
db12309bae | ||
|
|
2953e0dcc5 | ||
|
|
1b18ac9fe3 | ||
|
|
2cdad6622a | ||
|
|
7601d11a7a | ||
|
|
0bb4c6ef09 | ||
|
|
449624fd72 | ||
|
|
99f4f46062 | ||
|
|
b08d11c069 | ||
|
|
ac41e838ec | ||
|
|
836ddf3bbc | ||
|
|
0e815c626c | ||
|
|
4b7d035e9e | ||
|
|
cb3c44f48d | ||
|
|
8b3b9d33de | ||
|
|
59d99509c8 | ||
|
|
2933b76237 | ||
|
|
36c6d8974e | ||
|
|
d6cab76728 | ||
|
|
b4f740f338 | ||
|
|
a004de25ac | ||
|
|
d0584413ca | ||
|
|
ba57537108 | ||
|
|
6dcb110737 | ||
|
|
b599a832da | ||
|
|
9cbd5dd778 | ||
|
|
ed31c51237 | ||
|
|
e01707d25e | ||
|
|
73f118fe1c | ||
|
|
0234918f30 | ||
|
|
c90ba7e061 | ||
|
|
2aa4219838 | ||
|
|
b98417ab39 | ||
|
|
ead58dd606 | ||
|
|
a93e935585 | ||
|
|
aeb07e880e | ||
|
|
e0ce1f309e | ||
|
|
d74c81c3d1 | ||
|
|
2fa7200fb3 | ||
|
|
9f7d15e1b1 | ||
|
|
fdf00f1b18 | ||
|
|
abe7bbda15 | ||
|
|
795139d78f | ||
|
|
c7390ef0d7 | ||
|
|
8a926a3db3 | ||
|
|
7d5e49b44c | ||
|
|
555c3eac7a |
@@ -1,9 +1,4 @@
|
||||
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
|
||||
* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593).
|
||||
* [Core] Performance improvements and reduced memory usage due to improved buffer copies (#1599, thanks to @xljiulang).
|
||||
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
|
||||
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
|
||||
* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
|
||||
* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang).
|
||||
* [Server] Add support for interception of will messages (#1613).
|
||||
* [Server] Fixed wrong topic filter detections (#1615, thanks to @logicaloud).
|
||||
* [Client] Fixed _PlatformNotSupportedException_ when using Blazor (#1755, thanks to @Nickztar).
|
||||
* [Server] Fixed _NullReferenceException_ in retained messages management (#1762, thanks to @logicaloud).
|
||||
* [Server] Exposed new option which allows disabling packet fragmentation (#1753).
|
||||
* [Server] Expired sessions will no longer be used when a client connects (#1756).
|
||||
|
||||
@@ -3,7 +3,7 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
VERSION: "4.1.4.${{github.run_number}}"
|
||||
VERSION: "4.2.1.${{github.run_number}}"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -22,6 +22,7 @@ jobs:
|
||||
dotnet-version: |
|
||||
3.1.x
|
||||
6.0.x
|
||||
7.0.x
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
|
||||
@@ -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
|
||||
* 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
|
||||
|
||||
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
|
||||
|
||||
@@ -16,6 +16,28 @@ namespace MQTTnet.Samples.Client;
|
||||
|
||||
public static class Client_Connection_Samples
|
||||
{
|
||||
public static async Task Clean_Disconnect()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
|
||||
* to the server and close the connection afterwards.
|
||||
*
|
||||
* See sample _Connect_Client_ for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
|
||||
// connection is closed in a "not clean" way. See MQTT specification for more details.
|
||||
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client()
|
||||
{
|
||||
/*
|
||||
@@ -48,6 +70,32 @@ public static class Client_Connection_Samples
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
@@ -232,7 +280,7 @@ public static class Client_Connection_Samples
|
||||
|
||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||
// Using a reason code requires MQTT version 5.0.0!
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 is only supported in METTv5.0.0+.
|
||||
* This is only supported in MQTTv5.0.0+.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using MQTTnet;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
@@ -176,4 +177,4 @@ public static class Client_Subscribe_Samples
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public static class PackageInspection_Samples
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
mqttClient.InspectPackage += OnInspectPackage;
|
||||
mqttClient.InspectPacketAsync += OnInspectPacket;
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
+18
-1
@@ -4,7 +4,13 @@
|
||||
|
||||
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();
|
||||
|
||||
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
|
||||
@@ -16,7 +22,11 @@ foreach (var sampleClass in sampleClasses)
|
||||
index++;
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write("Please choose sample class (press Enter to continue): ");
|
||||
Console.ResetColor();
|
||||
|
||||
var input = Console.ReadLine();
|
||||
var selectedIndex = int.Parse(input ?? "0");
|
||||
var selectedSampleClass = sampleClasses[selectedIndex];
|
||||
@@ -29,12 +39,19 @@ foreach (var sampleMethod in sampleMethods)
|
||||
index++;
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write("Please choose sample (press Enter to continue): ");
|
||||
Console.ResetColor();
|
||||
|
||||
input = Console.ReadLine();
|
||||
selectedIndex = int.Parse(input ?? "0");
|
||||
var selectedSampleMethod = sampleMethods[selectedIndex];
|
||||
|
||||
Console.WriteLine();
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine("Executing sample...");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
|
||||
try
|
||||
|
||||
@@ -17,7 +17,7 @@ using MQTTnet.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()
|
||||
{
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Text.Json;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
@@ -32,7 +34,10 @@ public static class Server_Retained_Messages_Samples
|
||||
{
|
||||
try
|
||||
{
|
||||
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
|
||||
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
|
||||
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
|
||||
|
||||
eventArgs.LoadedRetainedMessages = retainedMessages;
|
||||
Console.WriteLine("Retained messages loaded.");
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
@@ -56,7 +61,9 @@ public static class Server_Retained_Messages_Samples
|
||||
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
||||
// of retained messages must be provided in the loaded event.
|
||||
|
||||
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
|
||||
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
|
||||
|
||||
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
|
||||
await File.WriteAllBytesAsync(storePath, buffer);
|
||||
Console.WriteLine("Retained messages saved.");
|
||||
}
|
||||
@@ -79,4 +86,59 @@ public static class Server_Retained_Messages_Samples
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
|
||||
sealed class MqttRetainedMessageModel
|
||||
{
|
||||
public string? ContentType { get; set; }
|
||||
public byte[]? CorrelationData { get; set; }
|
||||
public byte[]? Payload { get; set; }
|
||||
public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; }
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
||||
public string? ResponseTopic { get; set; }
|
||||
public string? Topic { get; set; }
|
||||
public List<MqttUserProperty>? UserProperties { get; set; }
|
||||
|
||||
public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
||||
return new MqttRetainedMessageModel
|
||||
{
|
||||
Topic = message.Topic,
|
||||
|
||||
// Create a copy of the buffer from the payload segment because
|
||||
// it cannot be serialized and deserialized with the JSON serializer.
|
||||
Payload = message.PayloadSegment.ToArray(),
|
||||
UserProperties = message.UserProperties,
|
||||
ResponseTopic = message.ResponseTopic,
|
||||
CorrelationData = message.CorrelationData,
|
||||
ContentType = message.ContentType,
|
||||
PayloadFormatIndicator = message.PayloadFormatIndicator,
|
||||
QualityOfServiceLevel = message.QualityOfServiceLevel
|
||||
|
||||
// Other properties like "Retain" are not if interest in the storage.
|
||||
// That's why a custom model makes sense.
|
||||
};
|
||||
}
|
||||
|
||||
public MqttApplicationMessage ToApplicationMessage()
|
||||
{
|
||||
return new MqttApplicationMessage
|
||||
{
|
||||
Topic = Topic,
|
||||
PayloadSegment = new ArraySegment<byte>(Payload ?? Array.Empty<byte>()),
|
||||
PayloadFormatIndicator = PayloadFormatIndicator,
|
||||
ResponseTopic = ResponseTopic,
|
||||
CorrelationData = CorrelationData,
|
||||
ContentType = ContentType,
|
||||
UserProperties = UserProperties,
|
||||
QualityOfServiceLevel = QualityOfServiceLevel,
|
||||
Dup = false,
|
||||
Retain = true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
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();
|
||||
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 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);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
||||
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
||||
}
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
public override ValueTask DisposeAsync()
|
||||
#else
|
||||
public Task DisposeAsync()
|
||||
@@ -57,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
|
||||
_socket?.Dispose();
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
return base.DisposeAsync();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
|
||||
@@ -51,11 +51,11 @@
|
||||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
||||
</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" />
|
||||
</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" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -211,8 +211,17 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
var buffer = PacketFormatterAdapter.Encode(packet);
|
||||
|
||||
WritePacketBuffer(_output, buffer);
|
||||
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (buffer.Payload.Count == 0)
|
||||
{
|
||||
// zero copy
|
||||
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
|
||||
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePacketBuffer(_output, buffer);
|
||||
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
BytesSent += buffer.Length;
|
||||
}
|
||||
|
||||
@@ -2,38 +2,34 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
IMqttNetLogger _logger = new MqttNetNullLogger();
|
||||
|
||||
IMqttNetLogger _logger = MqttNetNullLogger.Instance;
|
||||
|
||||
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)
|
||||
{
|
||||
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
|
||||
if (webSocket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(webSocket));
|
||||
}
|
||||
|
||||
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
|
||||
|
||||
@@ -48,7 +44,7 @@ namespace MQTTnet.AspNetCore
|
||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
||||
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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
|
||||
}
|
||||
|
||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
@@ -26,6 +26,10 @@
|
||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
||||
|
||||
@@ -30,17 +30,17 @@ namespace MQTTnet.Benchmarks
|
||||
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteBinaryData(_demoPayload);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(0x01);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteVariableByteInteger(5647382);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteVariableByteInteger(8574489);
|
||||
writer.WriteBinaryData(_demoPayload);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinaryData(_demoPayload);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
|
||||
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
||||
}
|
||||
@@ -83,11 +83,11 @@ namespace MQTTnet.Benchmarks
|
||||
writer.WriteVariableByteInteger(5647382);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteVariableByteInteger(8574589);
|
||||
writer.WriteBinaryData(_demoPayload);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinaryData(_demoPayload);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
|
||||
writer.Reset(0);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
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++)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</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;net48</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
|
||||
|
||||
@@ -353,13 +353,13 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
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.");
|
||||
|
||||
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");
|
||||
|
||||
List<MqttTopicFilter> topicFilters = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (_reconnectSubscriptions.Any())
|
||||
{
|
||||
var topicFilters = new List<MqttTopicFilter>();
|
||||
topicFilters = new List<MqttTopicFilter>();
|
||||
|
||||
foreach (var sub in _reconnectSubscriptions)
|
||||
{
|
||||
@@ -479,7 +481,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
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);
|
||||
|
||||
//clear because these worked, maybe the subscribe below will fail, only report those
|
||||
removedSubscriptions.Clear();
|
||||
}
|
||||
|
||||
if (addedSubscriptions != null && addedSubscriptions.Any())
|
||||
@@ -607,7 +612,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
|
||||
await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
|
||||
}
|
||||
|
||||
@@ -2,17 +2,33 @@
|
||||
// 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.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
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));
|
||||
|
||||
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 List<string> AddedSubscriptions { get; }
|
||||
public List<string> RemovedSubscriptions { get; }
|
||||
}
|
||||
}
|
||||
@@ -12,22 +12,37 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
|
||||
{
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
if (mqttClient == null)
|
||||
{
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
||||
}
|
||||
|
||||
|
||||
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
|
||||
}
|
||||
|
||||
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
|
||||
{
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
||||
}
|
||||
|
||||
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory _)
|
||||
{
|
||||
return new ManagedMqttClientOptionsBuilder();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</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;net48</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -17,9 +19,9 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
readonly IMqttClient _mqttClient;
|
||||
readonly MqttRpcClientOptions _options;
|
||||
|
||||
|
||||
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
|
||||
|
||||
|
||||
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
@@ -67,6 +69,11 @@ namespace MQTTnet.Extensions.Rpc
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
}
|
||||
|
||||
if (!_mqttClient.IsConnected)
|
||||
{
|
||||
throw new MqttClientDisconnectedException(null);
|
||||
}
|
||||
|
||||
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
|
||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
||||
|
||||
@@ -83,11 +90,14 @@ namespace MQTTnet.Extensions.Rpc
|
||||
throw new MqttProtocolViolationException("RPC response topic is empty.");
|
||||
}
|
||||
|
||||
var requestMessage = new MqttApplicationMessageBuilder().WithTopic(requestTopic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||
.WithResponseTopic(responseTopic)
|
||||
.Build();
|
||||
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
|
||||
|
||||
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
requestMessageBuilder.WithResponseTopic(responseTopic);
|
||||
}
|
||||
|
||||
var requestMessage = requestMessageBuilder.Build();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -115,7 +125,6 @@ namespace MQTTnet.Extensions.Rpc
|
||||
finally
|
||||
{
|
||||
_waitingCalls.TryRemove(responseTopic, out _);
|
||||
|
||||
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +136,8 @@ namespace MQTTnet.Extensions.Rpc
|
||||
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.
|
||||
eventArgs.IsHandled = true;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</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;net48</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
|
||||
|
||||
@@ -27,8 +27,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
return new MqttChannelAdapter(
|
||||
new MqttTcpChannel(options),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||
packetInspector,
|
||||
logger);
|
||||
logger)
|
||||
{
|
||||
PacketInspector = packetInspector
|
||||
};
|
||||
}
|
||||
|
||||
case MqttClientWebSocketOptions webSocketOptions:
|
||||
@@ -36,8 +38,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
return new MqttChannelAdapter(
|
||||
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||
packetInspector,
|
||||
logger);
|
||||
logger)
|
||||
{
|
||||
PacketInspector = packetInspector
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -33,9 +33,18 @@ namespace MQTTnet.TestApp
|
||||
|
||||
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($"+ 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($"+ Retain = {e.ApplicationMessage.Retain}");
|
||||
Console.WriteLine();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<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>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace MQTTnet.TestApp
|
||||
return new MqttApplicationMessage
|
||||
{
|
||||
Topic = "A/B/C",
|
||||
Payload = Encoding.UTF8.GetBytes(Payload),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(Payload)),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
||||
};
|
||||
}
|
||||
@@ -229,7 +229,7 @@ namespace MQTTnet.TestApp
|
||||
var message = new MqttApplicationMessage
|
||||
{
|
||||
Topic = "A/B/C",
|
||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
||||
};
|
||||
|
||||
@@ -280,7 +280,7 @@ namespace MQTTnet.TestApp
|
||||
var message = new MqttApplicationMessage
|
||||
{
|
||||
Topic = "A/B/C",
|
||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
||||
};
|
||||
|
||||
@@ -331,7 +331,7 @@ namespace MQTTnet.TestApp
|
||||
var message = new MqttApplicationMessage
|
||||
{
|
||||
Topic = "A/B/C",
|
||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Diagnostics;
|
||||
@@ -100,7 +101,7 @@ namespace MQTTnet.TestApp
|
||||
{
|
||||
// Replace the payload with the timestamp. But also extending a JSON
|
||||
// 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")
|
||||
@@ -143,10 +144,16 @@ namespace MQTTnet.TestApp
|
||||
|
||||
mqttServer.InterceptingPublishAsync += e =>
|
||||
{
|
||||
MqttNetConsoleLogger.PrintToConsole(
|
||||
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'",
|
||||
ConsoleColor.Magenta);
|
||||
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);
|
||||
}
|
||||
|
||||
MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ using MQTTnet.LowLevelClient;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class LowLevelMqttClient_Tests : BaseTestClass
|
||||
+40
-8
@@ -17,11 +17,43 @@ using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class ManagedMqttClient_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Expose_Custom_Connection_Error()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
server.ValidatingConnectionAsync += args =>
|
||||
{
|
||||
args.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
|
||||
|
||||
MqttClientDisconnectedEventArgs disconnectedArgs = null;
|
||||
managedClient.DisconnectedAsync += args =>
|
||||
{
|
||||
disconnectedArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var clientOptions = testEnvironment.Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(testEnvironment.CreateDefaultClientOptions()).Build();
|
||||
await managedClient.StartAsync(clientOptions);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsNotNull(disconnectedArgs);
|
||||
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, disconnectedArgs.ConnectResult.ResultCode);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Receive_While_Not_Cleanly_Disconnected()
|
||||
{
|
||||
@@ -164,7 +196,7 @@ namespace MQTTnet.Tests.Client
|
||||
.Build();
|
||||
|
||||
var dyingClient = testEnvironment.CreateClient();
|
||||
var dyingManagedClient = new ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
|
||||
var dyingManagedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
|
||||
|
||||
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
|
||||
|
||||
@@ -199,7 +231,7 @@ namespace MQTTnet.Tests.Client
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
|
||||
|
||||
var connected = GetConnectedTask(managedClient);
|
||||
@@ -226,7 +258,7 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
|
||||
var storage = new ManagedMqttClientTestStorage();
|
||||
|
||||
@@ -439,7 +471,7 @@ namespace MQTTnet.Tests.Client
|
||||
}
|
||||
}
|
||||
|
||||
async Task<ManagedMqttClient> CreateManagedClientAsync(
|
||||
async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> CreateManagedClientAsync(
|
||||
TestEnvironment testEnvironment,
|
||||
IMqttClient underlyingClient = null,
|
||||
TimeSpan? connectionCheckInterval = null,
|
||||
@@ -456,7 +488,7 @@ namespace MQTTnet.Tests.Client
|
||||
// at connection check intervals
|
||||
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
|
||||
|
||||
var managedClient = new ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
|
||||
var connected = GetConnectedTask(managedClient);
|
||||
|
||||
@@ -473,7 +505,7 @@ namespace MQTTnet.Tests.Client
|
||||
/// name="managedClient" />
|
||||
/// has connected
|
||||
/// </summary>
|
||||
Task GetConnectedTask(ManagedMqttClient managedClient)
|
||||
Task GetConnectedTask(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient)
|
||||
{
|
||||
var connected = new TaskCompletionSource<bool>();
|
||||
|
||||
@@ -495,7 +527,7 @@ namespace MQTTnet.Tests.Client
|
||||
/// name="expectedNumberOfMessages" />
|
||||
/// have been received
|
||||
/// </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 result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
|
||||
@@ -0,0 +1,245 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Extensions.Rpc;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Tests.Clients.MqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClient_Connection_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Connect_Disconnect_Connect()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
await client.ConnectAsync(clientOptions);
|
||||
await client.DisconnectAsync();
|
||||
await client.ConnectAsync(clientOptions);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public async Task Connect_Multiple_Times_Should_Fail()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
await client.ConnectAsync(clientOptions);
|
||||
await client.ConnectAsync(clientOptions);
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Return_Non_Success()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
server.ValidatingConnectionAsync += args =>
|
||||
{
|
||||
args.ResponseUserProperties = new List<MqttUserProperty>
|
||||
{
|
||||
new MqttUserProperty("Property", "Value")
|
||||
};
|
||||
|
||||
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
|
||||
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().WithoutThrowOnNonSuccessfulConnectResponse().Build());
|
||||
|
||||
Assert.IsNotNull(response);
|
||||
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
|
||||
Assert.AreEqual(response.UserProperties[0].Name, "Property");
|
||||
Assert.AreEqual(response.UserProperties[0].Value, "Value");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
||||
public async Task Rpc_Client_Not_Connected()
|
||||
{
|
||||
var factory = new MqttFactory();
|
||||
using (var client = factory.CreateMqttClient())
|
||||
{
|
||||
var rpcClient = factory.CreateMqttRpcClient(client);
|
||||
await rpcClient.ExecuteAsync("aaa", new byte[] { 1, 2, 3 }, MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Tests.Clients.MqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClient_Publish_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Acknowledge_After_Disconnect_Should_Fail()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
await client.SubscribeAsync("TEST", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
var flag = false;
|
||||
|
||||
client.ApplicationMessageReceivedAsync += async args =>
|
||||
{
|
||||
try
|
||||
{
|
||||
args.AutoAcknowledge = false;
|
||||
|
||||
await client.DisconnectAsync();
|
||||
await args.AcknowledgeAsync();
|
||||
|
||||
flag = true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
await server.InjectApplicationMessageAsync(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TEST"
|
||||
});
|
||||
|
||||
SpinWait.SpinUntil(() => flag, TimeSpan.FromSeconds(10));
|
||||
|
||||
Assert.IsTrue(flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-58
@@ -21,7 +21,7 @@ using MQTTnet.Tests.Mockups;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.MqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClient_Tests : BaseTestClass
|
||||
@@ -69,64 +69,8 @@ namespace MQTTnet.Tests.Client
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(5)).Build());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(OperationCanceledException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_IP()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
|
||||
{
|
||||
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));
|
||||
//Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
|
||||
}
|
||||
|
||||
await LongTestDelay(); // disconnected handler is called async
|
||||
Assert.IsTrue(disconnectHandlerCalled);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Event_Contains_Exception()
|
||||
+2
-3
@@ -6,12 +6,11 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Extensions;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients
|
||||
{
|
||||
[TestClass]
|
||||
public class MqttClientOptionsBuilder_Tests
|
||||
public sealed class MqttClientOptionsBuilder_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public void WithConnectionUri_Credential_Test()
|
||||
@@ -30,7 +30,7 @@ namespace MQTTnet.Tests.Diagnostics
|
||||
|
||||
var packets = new List<string>();
|
||||
|
||||
mqttClient.InspectPackage += eventArgs =>
|
||||
mqttClient.InspectPacketAsync += eventArgs =>
|
||||
{
|
||||
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
|
||||
return CompletedTask.Instance;
|
||||
|
||||
@@ -1,98 +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;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class Extension_Tests
|
||||
{
|
||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||
[TestMethod]
|
||||
public async Task TimeoutAfter()
|
||||
{
|
||||
await MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct),
|
||||
TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||
}
|
||||
|
||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||
[TestMethod]
|
||||
public async Task TimeoutAfterWithResult()
|
||||
{
|
||||
await MqttTaskTimeout.WaitAsync(
|
||||
ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct),
|
||||
TimeSpan.FromMilliseconds(100), CancellationToken.None);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TimeoutAfterWithInnerException()
|
||||
{
|
||||
try
|
||||
{
|
||||
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
|
||||
{
|
||||
var iis = new int[0];
|
||||
iis[1] = 0;
|
||||
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
|
||||
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.IsTrue(e is IndexOutOfRangeException);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TimeoutAfterWithInnerExceptionWithResult()
|
||||
{
|
||||
try
|
||||
{
|
||||
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
|
||||
{
|
||||
var iis = new int[0];
|
||||
iis[1] = 0;
|
||||
return iis[0];
|
||||
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
|
||||
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.IsTrue(e is IndexOutOfRangeException);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TimeoutAfterMemoryUsage()
|
||||
{
|
||||
var initialMemory = GC.GetTotalMemory(true);
|
||||
|
||||
var tasks = Enumerable.Range(0, 100000)
|
||||
.Select(i =>
|
||||
{
|
||||
return MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct),
|
||||
TimeSpan.FromMinutes(1), CancellationToken.None);
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
AssertIsLess(initialMemory + 2_000_000, GC.GetTotalMemory(true));
|
||||
}
|
||||
|
||||
static void AssertIsLess(long bound, long actual)
|
||||
{
|
||||
if (bound < actual)
|
||||
{
|
||||
Assert.Fail($"value must be less than {bound:N0} but is {actual:N0}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
PacketIdentifier = 123,
|
||||
Dup = true,
|
||||
Retain = true,
|
||||
Payload = Encoding.ASCII.GetBytes("HELLO"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
Topic = "A/B/C"
|
||||
};
|
||||
@@ -318,10 +318,9 @@ namespace MQTTnet.Tests.Formatter
|
||||
var publishPacket = new MqttPublishPacket
|
||||
{
|
||||
Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
|
||||
Payload = payload
|
||||
PayloadSegment = new ArraySegment<byte>(payload)
|
||||
};
|
||||
|
||||
|
||||
var serializationHelper = new MqttPacketSerializationHelper();
|
||||
|
||||
var buffer = serializationHelper.Encode(publishPacket);
|
||||
@@ -329,17 +328,17 @@ namespace MQTTnet.Tests.Formatter
|
||||
|
||||
Assert.IsNotNull(publishPacketCopy);
|
||||
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.
|
||||
publishPacket.Payload = Encoding.UTF8.GetBytes("MQTT");
|
||||
publishPacket.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("MQTT"));
|
||||
|
||||
buffer = serializationHelper.Encode(publishPacket);
|
||||
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
|
||||
|
||||
Assert.IsNotNull(publishPacketCopy2);
|
||||
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
|
||||
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy2.Payload);
|
||||
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy2.PayloadSegment.ToArray());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -463,7 +462,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
PacketIdentifier = 123,
|
||||
Dup = true,
|
||||
Retain = true,
|
||||
Payload = Encoding.ASCII.GetBytes("HELLO"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
Topic = "A/B/C"
|
||||
};
|
||||
@@ -567,7 +566,6 @@ namespace MQTTnet.Tests.Formatter
|
||||
using (var adapter = new MqttChannelAdapter(
|
||||
channel,
|
||||
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
|
||||
null,
|
||||
new MqttNetEventLogger()))
|
||||
{
|
||||
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
@@ -583,19 +581,12 @@ namespace MQTTnet.Tests.Formatter
|
||||
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] 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();
|
||||
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
|
||||
{
|
||||
var writer = bufferWriter ?? WriterFactory();
|
||||
@@ -604,7 +595,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Exceptions;
|
||||
@@ -76,7 +78,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod]
|
||||
public void Serialize_Full_MqttConnAckPacket_V310()
|
||||
{
|
||||
var connAckPacket = new MqttConnAckPacket
|
||||
@@ -296,7 +298,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
PacketIdentifier = 123,
|
||||
Dup = true,
|
||||
Retain = true,
|
||||
Payload = Encoding.ASCII.GetBytes("Payload"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
Topic = "Topic",
|
||||
ResponseTopic = "/Response",
|
||||
@@ -320,7 +322,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
||||
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
||||
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.Topic, deserialized.Topic);
|
||||
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.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Formatter;
|
||||
@@ -257,7 +259,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
PacketIdentifier = 123,
|
||||
Dup = true,
|
||||
Retain = true,
|
||||
Payload = Encoding.ASCII.GetBytes("Payload"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
Topic = "Topic",
|
||||
ResponseTopic = "/Response",
|
||||
@@ -281,7 +283,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
||||
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
||||
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.Topic, deserialized.Topic);
|
||||
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Tests.Formatter
|
||||
@@ -47,23 +48,32 @@ namespace MQTTnet.Tests.Formatter
|
||||
Assert.AreEqual(1234U, reader.ReadVariableByteInteger());
|
||||
Assert.AreEqual(9876U, reader.ReadVariableByteInteger());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||
public void Throw_If_String_Too_Long()
|
||||
{
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
|
||||
writer.WriteString(string.Empty.PadLeft(65536));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Write_And_Read_Multiple_Times()
|
||||
{
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteBinaryData(new byte[1234]);
|
||||
writer.WriteBinary(new byte[1234]);
|
||||
writer.WriteByte(0x01);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteVariableByteInteger(5647382);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteVariableByteInteger(8574489);
|
||||
writer.WriteBinaryData(new byte[48]);
|
||||
writer.WriteBinary(new byte[48]);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinaryData(new byte[3]);
|
||||
writer.WriteBinary(new byte[3]);
|
||||
|
||||
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0;net7.0</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
|
||||
@@ -51,10 +51,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
public void AssertReceivedCountEquals(int expectedCount)
|
||||
{
|
||||
lock (_receivedEventArgs)
|
||||
{
|
||||
Assert.AreEqual(expectedCount, _receivedEventArgs.Count);
|
||||
}
|
||||
Assert.AreEqual(expectedCount, Count);
|
||||
}
|
||||
|
||||
public string GeneratePayloadSequence()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@@ -24,10 +25,9 @@ namespace MQTTnet.Tests.Mockups
|
||||
public sealed class TestEnvironment : IDisposable
|
||||
{
|
||||
readonly List<string> _clientErrors = new List<string>();
|
||||
readonly List<IMqttClient> _clients = new List<IMqttClient>();
|
||||
|
||||
readonly ConcurrentBag<IMqttClient> _clients = new ConcurrentBag<IMqttClient>();
|
||||
readonly List<Exception> _exceptions = new List<Exception>();
|
||||
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
|
||||
readonly ConcurrentBag<ILowLevelMqttClient> _lowLevelClients = new ConcurrentBag<ILowLevelMqttClient>();
|
||||
readonly MqttProtocolVersion _protocolVersion;
|
||||
readonly List<string> _serverErrors = new List<string>();
|
||||
|
||||
@@ -65,9 +65,12 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
if (e.LogMessage.Level == MqttNetLogLevel.Error)
|
||||
{
|
||||
lock (_clientErrors)
|
||||
if (!IgnoreClientLogErrors)
|
||||
{
|
||||
_clientErrors.Add(e.LogMessage.ToString());
|
||||
lock (_clientErrors)
|
||||
{
|
||||
_clientErrors.Add(e.LogMessage.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -212,41 +215,45 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
public IMqttClient CreateClient()
|
||||
{
|
||||
lock (_clients)
|
||||
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance;
|
||||
|
||||
var client = Factory.CreateMqttClient(logger);
|
||||
client.ConnectingAsync += e =>
|
||||
{
|
||||
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : new MqttNetNullLogger();
|
||||
|
||||
var client = Factory.CreateMqttClient(logger);
|
||||
_clients.Add(client);
|
||||
|
||||
client.ConnectingAsync += e =>
|
||||
if (TestContext != null)
|
||||
{
|
||||
if (TestContext != null)
|
||||
var clientOptions = e.ClientOptions;
|
||||
var existingClientId = clientOptions.ClientId;
|
||||
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
|
||||
{
|
||||
var clientOptions = e.ClientOptions;
|
||||
var existingClientId = clientOptions.ClientId;
|
||||
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
|
||||
{
|
||||
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
|
||||
}
|
||||
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
|
||||
}
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
_clients.Add(client);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
public MqttClientOptions CreateDefaultClientOptions()
|
||||
{
|
||||
return CreateDefaultClientOptionsBuilder().Build();
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
|
||||
{
|
||||
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort).WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
|
||||
}
|
||||
|
||||
public ILowLevelMqttClient CreateLowLevelClient()
|
||||
{
|
||||
lock (_clients)
|
||||
{
|
||||
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
|
||||
_lowLevelClients.Add(client);
|
||||
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
|
||||
_lowLevelClients.Add(client);
|
||||
|
||||
return client;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
public MqttServer CreateServer(MqttServerOptions options)
|
||||
@@ -373,13 +380,16 @@ namespace MQTTnet.Tests.Mockups
|
||||
}
|
||||
}
|
||||
|
||||
lock (_clientErrors)
|
||||
if (!IgnoreClientLogErrors)
|
||||
{
|
||||
if (!IgnoreClientLogErrors && _clientErrors.Count > 0)
|
||||
lock (_clientErrors)
|
||||
{
|
||||
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
|
||||
Console.WriteLine(message);
|
||||
throw new Exception(message);
|
||||
if (_clientErrors.Count > 0)
|
||||
{
|
||||
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
|
||||
Console.WriteLine(message);
|
||||
throw new Exception(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 adapter = new MqttChannelAdapter(channel, formatterAdapter, null, MqttNetNullLogger.Instance);
|
||||
var adapter = new MqttChannelAdapter(channel, formatterAdapter, MqttNetNullLogger.Instance);
|
||||
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ namespace MQTTnet.Tests
|
||||
writer.WriteString("1234567890");
|
||||
Assert.AreEqual(10 + 2, writer.Length);
|
||||
|
||||
writer.WriteBinaryData(new byte[300]);
|
||||
writer.WriteBinary(new byte[300]);
|
||||
Assert.AreEqual(300 + 2 + 12, writer.Length);
|
||||
|
||||
writer.WriteBinaryData(new byte[5000]);
|
||||
writer.WriteBinary(new byte[5000]);
|
||||
Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,13 @@ using MQTTnet.Protocol;
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class MqttTopicValidator_Tests
|
||||
public sealed class MqttTopicValidator_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Valid_Topic()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||
public void Invalid_Topic_Plus()
|
||||
public void Invalid_Topic_Empty()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
|
||||
MqttTopicValidator.ThrowIfInvalid(string.Empty);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -33,9 +27,15 @@ namespace MQTTnet.Tests
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||
public void Invalid_Topic_Empty()
|
||||
public void Invalid_Topic_Plus()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid(string.Empty);
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Valid_Topic()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -598,8 +598,7 @@ namespace MQTTnet.Tests.Server
|
||||
// c
|
||||
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
await LongTestDelay();
|
||||
var flow = string.Join(string.Empty, events);
|
||||
Assert.AreEqual("c", flow);
|
||||
|
||||
@@ -607,10 +606,8 @@ namespace MQTTnet.Tests.Server
|
||||
// Connect client with same client ID. Should disconnect existing client.
|
||||
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
await LongTestDelay();
|
||||
flow = string.Join(string.Empty, events);
|
||||
|
||||
Assert.AreEqual("cdc", flow);
|
||||
|
||||
c2.ApplicationMessageReceivedAsync += e =>
|
||||
@@ -628,27 +625,27 @@ namespace MQTTnet.Tests.Server
|
||||
// r
|
||||
await c2.PublishStringAsync("topic");
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
await LongTestDelay();
|
||||
flow = string.Join(string.Empty, events);
|
||||
Assert.AreEqual("cdcr", flow);
|
||||
|
||||
// nothing
|
||||
|
||||
Assert.AreEqual(false, c1.IsConnected);
|
||||
await c1.DisconnectAsync();
|
||||
await c1.TryDisconnectAsync();
|
||||
Assert.AreEqual(false, c1.IsConnected);
|
||||
|
||||
await Task.Delay(500);
|
||||
await LongTestDelay();
|
||||
|
||||
// d
|
||||
Assert.AreEqual(true, c2.IsConnected);
|
||||
await c2.DisconnectAsync();
|
||||
|
||||
await Task.Delay(500);
|
||||
await LongTestDelay();
|
||||
|
||||
await server.StopAsync();
|
||||
|
||||
await LongTestDelay();
|
||||
flow = string.Join(string.Empty, events);
|
||||
Assert.AreEqual("cdcrd", flow);
|
||||
}
|
||||
@@ -852,11 +849,11 @@ namespace MQTTnet.Tests.Server
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await Task.Delay(100);
|
||||
|
||||
// Stopping the server should disconnect the connection with the client
|
||||
// which, it turn, will fire the disconnected event at the client.
|
||||
await server.StopAsync();
|
||||
|
||||
await Task.Delay(100);
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, disconnectCalled);
|
||||
}
|
||||
@@ -954,7 +951,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
client.InspectPackage += e =>
|
||||
client.InspectPacketAsync += e =>
|
||||
{
|
||||
if (e.Buffer.Length > 0)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttRetainedMessageManager_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task MqttRetainedMessageManager_GetUndefinedTopic()
|
||||
{
|
||||
var logger = new Mockups.TestLogger();
|
||||
var eventContainer = new MQTTnet.Server.MqttServerEventContainer();
|
||||
var retainedMessagesManager = new MQTTnet.Server.MqttRetainedMessagesManager(eventContainer, logger);
|
||||
var task = retainedMessagesManager.GetMessage("undefined");
|
||||
Assert.IsNotNull(task, "Task should not be null");
|
||||
var result = await task;
|
||||
Assert.IsNull(result, "Null result expected");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,14 +221,17 @@ namespace MQTTnet.Tests.Server
|
||||
var eventContainer = new MqttServerEventContainer();
|
||||
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
|
||||
|
||||
var session = new MqttSession("", false, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
|
||||
var session = new MqttSession(new MqttConnectPacket
|
||||
{
|
||||
ClientId = ""
|
||||
}, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
|
||||
|
||||
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
@@ -27,5 +29,118 @@ namespace MQTTnet.Tests.Server
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,28 @@ namespace MQTTnet.Tests.Server
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Do_Not_Use_Expired_Session()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
await testEnvironment.StartServer(o => o.WithPersistentSessions());
|
||||
|
||||
var c1 = await testEnvironment.ConnectClient(o => o.WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3));
|
||||
|
||||
// Kill the client connection and ensure that the session will stay there but is expired after 3 seconds!
|
||||
c1.Dispose();
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(6));
|
||||
|
||||
c1 = testEnvironment.CreateClient();
|
||||
var options = testEnvironment.CreateDefaultClientOptionsBuilder().WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3).Build();
|
||||
|
||||
var connectResult = await c1.ConnectAsync(options);
|
||||
Assert.AreEqual(false, connectResult.IsSessionPresent);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Fire_Deleted_Event()
|
||||
{
|
||||
@@ -121,7 +143,7 @@ namespace MQTTnet.Tests.Server
|
||||
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(MqttProtocolVersion.V310)]
|
||||
[DataRow(MqttProtocolVersion.V311)]
|
||||
@@ -134,16 +156,12 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion);
|
||||
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(10)).WithProtocolVersion(protocolVersion);
|
||||
|
||||
var hasReceive = false;
|
||||
|
||||
void OnReceive()
|
||||
{
|
||||
if (!hasReceive)
|
||||
{
|
||||
hasReceive = true;
|
||||
}
|
||||
hasReceive = true;
|
||||
}
|
||||
|
||||
// Try to connect 50 clients at the same time.
|
||||
@@ -152,7 +170,7 @@ namespace MQTTnet.Tests.Server
|
||||
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
|
||||
Assert.AreEqual(1, connectedClients.Count);
|
||||
|
||||
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
|
||||
@@ -164,7 +182,7 @@ namespace MQTTnet.Tests.Server
|
||||
Assert.AreEqual(true, hasReceive);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
||||
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
||||
@@ -355,8 +373,8 @@ namespace MQTTnet.Tests.Server
|
||||
onReceive();
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
|
||||
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
|
||||
{
|
||||
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace MQTTnet.Tests.Server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public async Task Deny_Invalid_Topic()
|
||||
{
|
||||
@@ -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]
|
||||
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
||||
public async Task Disconnect_While_Subscribing()
|
||||
@@ -105,7 +128,7 @@ namespace MQTTnet.Tests.Server
|
||||
await client.SubscribeAsync("#");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public async Task Enqueue_Message_After_Subscription()
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Server
|
||||
var topics = t.Value;
|
||||
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);
|
||||
++currentValue;
|
||||
@@ -96,7 +96,7 @@ namespace MQTTnet.Tests.Server
|
||||
{
|
||||
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");
|
||||
}
|
||||
@@ -110,7 +110,7 @@ namespace MQTTnet.Tests.Server
|
||||
{
|
||||
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");
|
||||
}
|
||||
@@ -150,7 +150,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
// 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");
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
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");
|
||||
|
||||
@@ -269,7 +269,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
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");
|
||||
|
||||
@@ -319,7 +319,7 @@ namespace MQTTnet.Tests.Server
|
||||
var 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");
|
||||
|
||||
@@ -354,7 +354,7 @@ namespace MQTTnet.Tests.Server
|
||||
var l3 = "prop1";
|
||||
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");
|
||||
|
||||
@@ -447,7 +447,7 @@ namespace MQTTnet.Tests.Server
|
||||
var l3 = "prop1";
|
||||
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");
|
||||
|
||||
@@ -475,7 +475,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
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("Topic: " + topic);
|
||||
@@ -501,7 +501,7 @@ namespace MQTTnet.Tests.Server
|
||||
{
|
||||
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);
|
||||
if (result.IsSubscribed)
|
||||
@@ -557,7 +557,7 @@ namespace MQTTnet.Tests.Server
|
||||
var eventContainer = new MqttServerEventContainer();
|
||||
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
|
||||
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
|
||||
_clientSession = new MqttSession(clientId, false, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
|
||||
_clientSession = new MqttSession(new MqttConnectPacket{ ClientId = clientId }, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
|
||||
|
||||
List<string> topicsToSubscribe;
|
||||
|
||||
|
||||
@@ -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.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
{
|
||||
@@ -28,5 +32,28 @@ namespace MQTTnet.Tests.Server
|
||||
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));
|
||||
|
||||
ulong topicHash;
|
||||
ulong topicHashMask;
|
||||
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);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
|
||||
MqttTopicHash.Calculate(filter, out var filterTopicHash, out var filterTopicHashMask, out _);
|
||||
|
||||
if (expectedResult == MqttTopicFilterCompareResult.IsMatch)
|
||||
{
|
||||
|
||||
@@ -27,19 +27,15 @@ namespace MQTTnet.Adapter
|
||||
readonly byte[] _fixedHeaderBuffer = new byte[2];
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
|
||||
readonly MqttPacketInspector _packetInspector;
|
||||
|
||||
readonly byte[] _singleByteBuffer = new byte[1];
|
||||
|
||||
readonly AsyncLock _syncRoot = new AsyncLock();
|
||||
|
||||
long _bytesReceived;
|
||||
long _bytesSent;
|
||||
Statistics _statistics; // mutable struct, don't make readonly!
|
||||
|
||||
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));
|
||||
_packetInspector = packetInspector;
|
||||
|
||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||
|
||||
@@ -51,9 +47,9 @@ namespace MQTTnet.Adapter
|
||||
_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;
|
||||
|
||||
@@ -65,6 +61,10 @@ namespace MQTTnet.Adapter
|
||||
|
||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||
|
||||
public MqttPacketInspector PacketInspector { get; set; }
|
||||
|
||||
public bool AllowPacketFragmentation { get; set; } = true;
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -127,7 +127,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
try
|
||||
{
|
||||
_packetInspector?.BeginReceivePacket();
|
||||
PacketInspector?.BeginReceivePacket();
|
||||
|
||||
ReceivedMqttPacket receivedPacket;
|
||||
var receivedPacketTask = ReceiveAsync(cancellationToken);
|
||||
@@ -145,9 +145,9 @@ namespace MQTTnet.Adapter
|
||||
return null;
|
||||
}
|
||||
|
||||
_packetInspector?.EndReceivePacket();
|
||||
PacketInspector?.EndReceivePacket();
|
||||
|
||||
Interlocked.Add(ref _bytesSent, receivedPacket.TotalLength);
|
||||
Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength);
|
||||
|
||||
if (PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||
{
|
||||
@@ -183,8 +183,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
public void ResetStatistics()
|
||||
{
|
||||
Interlocked.Exchange(ref _bytesReceived, 0L);
|
||||
Interlocked.Exchange(ref _bytesSent, 0L);
|
||||
_statistics.Reset();
|
||||
}
|
||||
|
||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
@@ -202,21 +201,21 @@ namespace MQTTnet.Adapter
|
||||
try
|
||||
{
|
||||
var packetBuffer = PacketFormatterAdapter.Encode(packet);
|
||||
_packetInspector?.BeginSendPacket(packetBuffer);
|
||||
PacketInspector?.BeginSendPacket(packetBuffer);
|
||||
|
||||
_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.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)
|
||||
{
|
||||
@@ -275,7 +274,7 @@ namespace MQTTnet.Adapter
|
||||
return 0;
|
||||
}
|
||||
|
||||
_packetInspector?.FillReceiveBuffer(_singleByteBuffer);
|
||||
PacketInspector?.FillReceiveBuffer(_singleByteBuffer);
|
||||
|
||||
encodedByte = _singleByteBuffer[0];
|
||||
|
||||
@@ -314,7 +313,7 @@ namespace MQTTnet.Adapter
|
||||
{
|
||||
return ReadFixedHeaderResult.ConnectionClosed;
|
||||
}
|
||||
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ReadFixedHeaderResult.Canceled;
|
||||
@@ -328,7 +327,7 @@ namespace MQTTnet.Adapter
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
|
||||
_packetInspector?.FillReceiveBuffer(buffer);
|
||||
PacketInspector?.FillReceiveBuffer(buffer);
|
||||
|
||||
var hasRemainingLength = buffer[1] != 0;
|
||||
if (!hasRemainingLength)
|
||||
@@ -412,7 +411,7 @@ namespace MQTTnet.Adapter
|
||||
bodyOffset += readBytes;
|
||||
} while (bodyOffset < bodyLength);
|
||||
|
||||
_packetInspector?.FillReceiveBuffer(body);
|
||||
PacketInspector?.FillReceiveBuffer(body);
|
||||
|
||||
var bodySegment = new ArraySegment<byte>(body, 0, bodyLength);
|
||||
return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength);
|
||||
@@ -459,5 +458,17 @@ namespace MQTTnet.Adapter
|
||||
|
||||
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
|
||||
{
|
||||
var eventArgs = new InspectMqttPacketEventArgs
|
||||
{
|
||||
Buffer = buffer,
|
||||
Direction = direction
|
||||
};
|
||||
|
||||
var eventArgs = new InspectMqttPacketEventArgs(direction, buffer);
|
||||
_asyncEvent.InvokeAsync(eventArgs).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication result.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
}
|
||||
|
||||
@@ -2,20 +2,35 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the reason code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the reason code.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientDisconnectReason Reason { get; set; } = MqttClientDisconnectReason.NormalDisconnection;
|
||||
public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason string.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the reason string.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
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,32 +2,62 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectOptionsBuilder
|
||||
{
|
||||
MqttClientDisconnectReason _reason = MqttClientDisconnectReason.NormalDisconnection;
|
||||
MqttClientDisconnectOptionsReason _reason = MqttClientDisconnectOptionsReason.NormalDisconnection;
|
||||
string _reasonString;
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
|
||||
{
|
||||
_reasonString = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectReason value)
|
||||
{
|
||||
_reason = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
uint _sessionExpiryInterval;
|
||||
List<MqttUserProperty> _userProperties;
|
||||
|
||||
public MqttClientDisconnectOptions Build()
|
||||
{
|
||||
return new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = _reason,
|
||||
ReasonString = _reasonString
|
||||
ReasonString = _reasonString,
|
||||
UserProperties = _userProperties,
|
||||
SessionExpiryInterval = _sessionExpiryInterval
|
||||
};
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectOptionsReason value)
|
||||
{
|
||||
_reason = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
|
||||
{
|
||||
_reasonString = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithSessionExpiryInterval(uint value)
|
||||
{
|
||||
_sessionExpiryInterval = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> userProperties)
|
||||
{
|
||||
_userProperties = userProperties;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
if (_userProperties == null)
|
||||
{
|
||||
_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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public bool ClientWasConnected { get; internal set; }
|
||||
public MqttClientDisconnectedEventArgs(
|
||||
bool clientWasConnected,
|
||||
MqttClientConnectResult connectResult,
|
||||
MqttClientDisconnectReason reason,
|
||||
string reasonString,
|
||||
List<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>
|
||||
/// Gets the authentication result.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets the authentication result.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; internal set; }
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
|
||||
public Exception Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the reason.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientDisconnectReason Reason { get; internal set; }
|
||||
|
||||
public string ReasonString { get; internal set; }
|
||||
public MqttClientDisconnectReason Reason { get; }
|
||||
|
||||
public string ReasonString { get; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
|
||||
|
||||
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
||||
|
||||
event Func<InspectMqttPacketEventArgs, Task> InspectPackage;
|
||||
event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
|
||||
|
||||
bool IsConnected { get; }
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Client.Internal
|
||||
{
|
||||
public sealed class MqttClientEvents
|
||||
{
|
||||
public AsyncEvent<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceivedEvent { get; } = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
|
||||
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
|
||||
public AsyncEvent<MqttClientConnectingEventArgs> ConnectingEvent { get; } = new AsyncEvent<MqttClientConnectingEventArgs>();
|
||||
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
|
||||
public AsyncEvent<InspectMqttPacketEventArgs> InspectPacketEvent { get; } = new AsyncEvent<InspectMqttPacketEventArgs>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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.Internal
|
||||
{
|
||||
public static class MqttClientResultFactory
|
||||
{
|
||||
public static readonly MqttClientConnectResultFactory ConnectResult = new MqttClientConnectResultFactory();
|
||||
public static readonly MqttClientPublishResultFactory PublishResult = new MqttClientPublishResultFactory();
|
||||
public static readonly MqttClientSubscribeResultFactory SubscribeResult = new MqttClientSubscribeResultFactory();
|
||||
public static readonly MqttClientUnsubscribeResultFactory UnsubscribeResult = new MqttClientUnsubscribeResultFactory();
|
||||
}
|
||||
}
|
||||
+352
-233
@@ -3,9 +3,11 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Client.Internal;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
@@ -19,30 +21,29 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClient : Disposable, IMqttClient
|
||||
{
|
||||
readonly IMqttClientAdapterFactory _adapterFactory;
|
||||
readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
|
||||
|
||||
readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory();
|
||||
readonly MqttClientSubscribeResultFactory _clientSubscribeResultFactory = new MqttClientSubscribeResultFactory();
|
||||
readonly MqttClientUnsubscribeResultFactory _clientUnsubscribeResultFactory = new MqttClientUnsubscribeResultFactory();
|
||||
|
||||
readonly AsyncEvent<MqttClientConnectedEventArgs> _connectedEvent = new AsyncEvent<MqttClientConnectedEventArgs>();
|
||||
readonly AsyncEvent<MqttClientConnectingEventArgs> _connectingEvent = new AsyncEvent<MqttClientConnectingEventArgs>();
|
||||
readonly AsyncEvent<MqttClientDisconnectedEventArgs> _disconnectedEvent = new AsyncEvent<MqttClientDisconnectedEventArgs>();
|
||||
readonly object _disconnectLock = new object();
|
||||
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
|
||||
readonly MqttClientEvents _events = new MqttClientEvents();
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
|
||||
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||
readonly IMqttNetLogger _rootLogger;
|
||||
|
||||
IMqttChannelAdapter _adapter;
|
||||
CancellationTokenSource _backgroundCancellationTokenSource;
|
||||
|
||||
bool _cleanDisconnectInitiated;
|
||||
volatile int _connectionStatus;
|
||||
MqttClientDisconnectReason _disconnectReason;
|
||||
|
||||
// The value for this field can be set from two different enums.
|
||||
// They contain the same values but the set is reduced in one case.
|
||||
int _disconnectReason;
|
||||
string _disconnectReasonString;
|
||||
List<MqttUserProperty> _disconnectUserProperties;
|
||||
|
||||
Task _keepAlivePacketsSenderTask;
|
||||
DateTime _lastPacketSentTimestamp;
|
||||
|
||||
CancellationTokenSource _mqttClientAlive;
|
||||
MqttPacketDispatcher _packetDispatcher;
|
||||
Task _packetReceiverTask;
|
||||
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
|
||||
@@ -57,52 +58,42 @@ namespace MQTTnet.Client
|
||||
|
||||
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
||||
{
|
||||
add => _applicationMessageReceivedEvent.AddHandler(value);
|
||||
remove => _applicationMessageReceivedEvent.RemoveHandler(value);
|
||||
add => _events.ApplicationMessageReceivedEvent.AddHandler(value);
|
||||
remove => _events.ApplicationMessageReceivedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
|
||||
{
|
||||
add => _connectedEvent.AddHandler(value);
|
||||
remove => _connectedEvent.RemoveHandler(value);
|
||||
add => _events.ConnectedEvent.AddHandler(value);
|
||||
remove => _events.ConnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync
|
||||
{
|
||||
add => _connectingEvent.AddHandler(value);
|
||||
remove => _connectingEvent.RemoveHandler(value);
|
||||
add => _events.ConnectingEvent.AddHandler(value);
|
||||
remove => _events.ConnectingEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
|
||||
{
|
||||
add => _disconnectedEvent.AddHandler(value);
|
||||
remove => _disconnectedEvent.RemoveHandler(value);
|
||||
add => _events.DisconnectedEvent.AddHandler(value);
|
||||
remove => _events.DisconnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<InspectMqttPacketEventArgs, Task> InspectPackage
|
||||
public event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync
|
||||
{
|
||||
add => _inspectPacketEvent.AddHandler(value);
|
||||
remove => _inspectPacketEvent.RemoveHandler(value);
|
||||
add => _events.InspectPacketEvent.AddHandler(value);
|
||||
remove => _events.InspectPacketEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
|
||||
public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
|
||||
|
||||
public MqttClientOptions Options { get; private set; }
|
||||
|
||||
public async Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.ChannelOptions == null)
|
||||
{
|
||||
throw new ArgumentException("ChannelOptions are not set.");
|
||||
}
|
||||
|
||||
ThrowIfOptionsInvalid(options);
|
||||
ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
|
||||
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connecting, MqttClientConnectionStatus.Disconnected) != MqttClientConnectionStatus.Disconnected)
|
||||
@@ -116,33 +107,40 @@ namespace MQTTnet.Client
|
||||
{
|
||||
Options = options;
|
||||
|
||||
if (_connectingEvent.HasHandlers)
|
||||
if (_events.ConnectingEvent.HasHandlers)
|
||||
{
|
||||
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
|
||||
await _events.ConnectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
|
||||
_packetIdentifierProvider.Reset();
|
||||
_packetDispatcher = new MqttPacketDispatcher();
|
||||
|
||||
_backgroundCancellationTokenSource = new CancellationTokenSource();
|
||||
var backgroundCancellationToken = _backgroundCancellationTokenSource.Token;
|
||||
_mqttClientAlive = new CancellationTokenSource();
|
||||
var mqttClientAliveToken = _mqttClientAlive.Token;
|
||||
|
||||
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_inspectPacketEvent, _rootLogger), _rootLogger);
|
||||
_adapter = adapter;
|
||||
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_events.InspectPacketEvent, _rootLogger), _rootLogger);
|
||||
_adapter = adapter ?? throw new InvalidOperationException("The adapter factory did not provide an adapter.");
|
||||
|
||||
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
_logger.Verbose("Trying to connect with server '{0}'.", options.ChannelOptions);
|
||||
await adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
_logger.Verbose("Connection with server established.");
|
||||
connectResult = await ConnectInternal(adapter, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fall back to the general timeout specified in the options if the user passed
|
||||
// CancellationToken.None or similar.
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
connectResult = await ConnectInternal(adapter, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
|
||||
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
|
||||
|
||||
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
|
||||
|
||||
connectResult = await AuthenticateAsync(options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
|
||||
{
|
||||
_logger.Warning("Connecting failed: {0}", connectResult.ResultCode);
|
||||
return connectResult;
|
||||
}
|
||||
|
||||
_lastPacketSentTimestamp = DateTime.UtcNow;
|
||||
@@ -150,34 +148,35 @@ namespace MQTTnet.Client
|
||||
var keepAliveInterval = Options.KeepAlivePeriod;
|
||||
if (connectResult.ServerKeepAlive > 0)
|
||||
{
|
||||
_logger.Info($"Using keep alive value ({connectResult.ServerKeepAlive}) sent from the server.");
|
||||
_logger.Info($"Using keep alive value ({connectResult.ServerKeepAlive}) sent from the server");
|
||||
keepAliveInterval = TimeSpan.FromSeconds(connectResult.ServerKeepAlive);
|
||||
}
|
||||
|
||||
if (keepAliveInterval != TimeSpan.Zero)
|
||||
{
|
||||
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(backgroundCancellationToken), backgroundCancellationToken);
|
||||
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessages(mqttClientAliveToken), mqttClientAliveToken);
|
||||
}
|
||||
|
||||
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
|
||||
|
||||
_logger.Info("Connected.");
|
||||
_logger.Info("Connected");
|
||||
|
||||
if (_connectedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
|
||||
await _connectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
await OnConnected(connectResult).ConfigureAwait(false);
|
||||
|
||||
return connectResult;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_disconnectReason = MqttClientDisconnectReason.UnspecifiedError;
|
||||
if (exception is MqttConnectingFailedException connectingFailedException)
|
||||
{
|
||||
connectResult = connectingFailedException.Result;
|
||||
}
|
||||
|
||||
_logger.Error(exception, "Error while connecting with server.");
|
||||
_disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError;
|
||||
|
||||
await DisconnectInternalAsync(null, exception, connectResult).ConfigureAwait(false);
|
||||
_logger.Error(exception, "Error while connecting with server");
|
||||
|
||||
await DisconnectInternal(null, exception, connectResult).ConfigureAwait(false);
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -193,6 +192,7 @@ namespace MQTTnet.Client
|
||||
ThrowIfDisposed();
|
||||
|
||||
var clientWasConnected = IsConnected;
|
||||
|
||||
if (DisconnectIsPendingOrFinished())
|
||||
{
|
||||
return;
|
||||
@@ -200,18 +200,38 @@ namespace MQTTnet.Client
|
||||
|
||||
try
|
||||
{
|
||||
_disconnectReason = MqttClientDisconnectReason.NormalDisconnection;
|
||||
if (!clientWasConnected)
|
||||
{
|
||||
ThrowNotConnected();
|
||||
}
|
||||
|
||||
_disconnectReason = (int)options.Reason;
|
||||
_cleanDisconnectInitiated = true;
|
||||
|
||||
if (clientWasConnected)
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
|
||||
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
||||
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
// Sending the DISCONNECT may fail due to connection issues. The resulting exception
|
||||
// must be throw to let the caller know that the disconnect is not a clean one.
|
||||
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
await Send(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
await Send(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DisconnectCoreAsync(null, null, null, clientWasConnected).ConfigureAwait(false);
|
||||
await DisconnectCore(null, null, null, clientWasConnected).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,13 +239,13 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
|
||||
await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false);
|
||||
await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,6 +259,11 @@ namespace MQTTnet.Client
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
|
||||
|
||||
switch (applicationMessage.QualityOfServiceLevel)
|
||||
@@ -249,11 +274,11 @@ namespace MQTTnet.Client
|
||||
}
|
||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||
{
|
||||
return PublishAtLeastOnceAsync(publishPacket, cancellationToken);
|
||||
return PublishAtLeastOnce(publishPacket, cancellationToken);
|
||||
}
|
||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||
{
|
||||
return PublishExactlyOnceAsync(publishPacket, cancellationToken);
|
||||
return PublishExactlyOnce(publishPacket, cancellationToken);
|
||||
}
|
||||
default:
|
||||
{
|
||||
@@ -281,7 +306,7 @@ namespace MQTTnet.Client
|
||||
UserProperties = data.UserProperties
|
||||
};
|
||||
|
||||
return SendAsync(authPacket, cancellationToken);
|
||||
return Send(authPacket, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default)
|
||||
@@ -296,15 +321,31 @@ namespace MQTTnet.Client
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
|
||||
}
|
||||
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
|
||||
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
MqttSubAckPacket subAckPacket;
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
subAckPacket = await Request<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
subAckPacket = await Request<MqttSubAckPacket>(subscribePacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return _clientSubscribeResultFactory.Create(subscribePacket, subAckPacket);
|
||||
return MqttClientResultFactory.SubscribeResult.Create(subscribePacket, subAckPacket);
|
||||
}
|
||||
|
||||
public async Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default)
|
||||
@@ -314,15 +355,36 @@ namespace MQTTnet.Client
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
foreach (var topicFilter in options.TopicFilters)
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter);
|
||||
}
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
|
||||
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
MqttUnsubAckPacket unsubAckPacket;
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
unsubAckPacket = await Request<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
unsubAckPacket = await Request<MqttUnsubAckPacket>(unsubscribePacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket);
|
||||
return MqttClientResultFactory.UnsubscribeResult.Create(unsubscribePacket, unsubAckPacket);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -346,7 +408,7 @@ namespace MQTTnet.Client
|
||||
if (!eventArgs.ProcessingFailed)
|
||||
{
|
||||
var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs);
|
||||
return SendAsync(pubAckPacket, cancellationToken);
|
||||
return Send(pubAckPacket, cancellationToken);
|
||||
}
|
||||
}
|
||||
else if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
|
||||
@@ -354,7 +416,7 @@ namespace MQTTnet.Client
|
||||
if (!eventArgs.ProcessingFailed)
|
||||
{
|
||||
var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs);
|
||||
return SendAsync(pubRecPacket, cancellationToken);
|
||||
return Send(pubRecPacket, cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -365,27 +427,51 @@ namespace MQTTnet.Client
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
async Task<MqttClientConnectResult> AuthenticateAsync(MqttClientOptions options, CancellationToken cancellationToken)
|
||||
async Task<MqttClientConnectResult> Authenticate(IMqttChannelAdapter channelAdapter, MqttClientOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
MqttClientConnectResult result;
|
||||
|
||||
try
|
||||
{
|
||||
var connectPacket = MqttPacketFactories.Connect.Create(options);
|
||||
await Send(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||
var receivedPacket = await Receive(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var clientConnectResultFactory = new MqttClientConnectResultFactory();
|
||||
result = clientConnectResultFactory.Create(connAckPacket, options.ProtocolVersion);
|
||||
if (receivedPacket is MqttConnAckPacket connAckPacket)
|
||||
{
|
||||
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
else if (receivedPacket is MqttAuthPacket)
|
||||
{
|
||||
throw new NotSupportedException("Extended authentication handler is not yet supported");
|
||||
}
|
||||
else if (receivedPacket == null)
|
||||
{
|
||||
throw new MqttCommunicationException("Connection closed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket}).");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
|
||||
}
|
||||
|
||||
if (result.ResultCode != MqttClientConnectResultCode.Success)
|
||||
// This is no feature. It is basically a backward compatibility option and should be removed in the future.
|
||||
// The client should not throw any exception if the transport layer connection was successful and the server
|
||||
// did send a proper ACK packet with a non success response.
|
||||
if (options.ThrowOnNonSuccessfulConnectResponse)
|
||||
{
|
||||
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
|
||||
_logger.Warning(
|
||||
"Client will now throw an _MqttConnectingFailedException_. This is obsolete and will be removed in the future. Consider setting _ThrowOnNonSuccessfulResponseFromServer=False_ in client options.");
|
||||
|
||||
if (result.ResultCode != MqttClientConnectResultCode.Success)
|
||||
{
|
||||
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Verbose("Authenticated MQTT connection with server established.");
|
||||
@@ -397,19 +483,19 @@ namespace MQTTnet.Client
|
||||
{
|
||||
try
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Cancel(false);
|
||||
_mqttClientAlive?.Cancel(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Dispose();
|
||||
_backgroundCancellationTokenSource = null;
|
||||
_mqttClientAlive?.Dispose();
|
||||
_mqttClientAlive = null;
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = null;
|
||||
|
||||
_adapter?.Dispose();
|
||||
_adapter = null;
|
||||
|
||||
|
||||
_packetDispatcher?.Dispose();
|
||||
_packetDispatcher = null;
|
||||
}
|
||||
@@ -420,7 +506,31 @@ namespace MQTTnet.Client
|
||||
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand);
|
||||
}
|
||||
|
||||
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
|
||||
async Task<MqttClientConnectResult> ConnectInternal(IMqttChannelAdapter channelAdapter, 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>();
|
||||
|
||||
var connectResult = await Authenticate(channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
if (connectResult.ResultCode == MqttClientConnectResultCode.Success)
|
||||
{
|
||||
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
|
||||
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(backgroundCancellationToken), backgroundCancellationToken);
|
||||
}
|
||||
|
||||
return connectResult;
|
||||
}
|
||||
}
|
||||
|
||||
async Task DisconnectCore(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
|
||||
{
|
||||
TryInitiateDisconnect();
|
||||
|
||||
@@ -445,11 +555,11 @@ namespace MQTTnet.Client
|
||||
|
||||
try
|
||||
{
|
||||
_packetDispatcher.Dispose(new MqttClientDisconnectedException(exception));
|
||||
|
||||
var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender);
|
||||
var publishPacketReceiverTask = WaitForTaskAsync(_publishPacketReceiverTask, sender);
|
||||
var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender);
|
||||
_packetDispatcher?.Dispose(new MqttClientDisconnectedException(exception));
|
||||
|
||||
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
|
||||
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
|
||||
var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger);
|
||||
|
||||
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
|
||||
}
|
||||
@@ -465,28 +575,27 @@ namespace MQTTnet.Client
|
||||
|
||||
_logger.Info("Disconnected.");
|
||||
|
||||
var eventArgs = new MqttClientDisconnectedEventArgs
|
||||
{
|
||||
ClientWasConnected = clientWasConnected,
|
||||
Exception = exception,
|
||||
ConnectResult = connectResult,
|
||||
Reason = _disconnectReason,
|
||||
ReasonString = _disconnectReasonString
|
||||
};
|
||||
var eventArgs = new MqttClientDisconnectedEventArgs(
|
||||
clientWasConnected,
|
||||
connectResult,
|
||||
(MqttClientDisconnectReason)_disconnectReason,
|
||||
_disconnectReasonString,
|
||||
_disconnectUserProperties,
|
||||
exception);
|
||||
|
||||
// This handler must be executed in a new thread because otherwise a dead lock may happen
|
||||
// when trying to reconnect in that handler etc.
|
||||
Task.Run(() => _disconnectedEvent.InvokeAsync(eventArgs)).RunInBackground(_logger);
|
||||
Task.Run(() => _events.DisconnectedEvent.InvokeAsync(eventArgs)).RunInBackground(_logger);
|
||||
}
|
||||
}
|
||||
|
||||
Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult)
|
||||
Task DisconnectInternal(Task sender, Exception exception, MqttClientConnectResult connectResult)
|
||||
{
|
||||
var clientWasConnected = IsConnected;
|
||||
|
||||
if (!DisconnectIsPendingOrFinished())
|
||||
{
|
||||
return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected);
|
||||
return DisconnectCore(sender, exception, connectResult, clientWasConnected);
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
@@ -531,16 +640,26 @@ namespace MQTTnet.Client
|
||||
}
|
||||
}
|
||||
|
||||
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
|
||||
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessage(MqttPublishPacket publishPacket)
|
||||
{
|
||||
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
|
||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
|
||||
|
||||
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
await _events.ApplicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
|
||||
return eventArgs;
|
||||
}
|
||||
|
||||
Task OnConnected(MqttClientConnectResult connectResult)
|
||||
{
|
||||
if (_events.ConnectedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
|
||||
return _events.ConnectedEvent.InvokeAsync(eventArgs);
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket)
|
||||
{
|
||||
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
|
||||
@@ -554,13 +673,14 @@ namespace MQTTnet.Client
|
||||
|
||||
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
|
||||
{
|
||||
_disconnectReason = (MqttClientDisconnectReason)disconnectPacket.ReasonCode;
|
||||
_disconnectReason = (int)disconnectPacket.ReasonCode;
|
||||
_disconnectReasonString = disconnectPacket.ReasonString;
|
||||
_disconnectUserProperties = disconnectPacket.UserProperties;
|
||||
|
||||
// Also dispatch disconnect to waiting threads to generate a proper exception.
|
||||
_packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket));
|
||||
|
||||
return DisconnectInternalAsync(_packetReceiverTask, null, null);
|
||||
return DisconnectInternal(_packetReceiverTask, null, null);
|
||||
}
|
||||
|
||||
async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken)
|
||||
@@ -576,7 +696,7 @@ namespace MQTTnet.Client
|
||||
}
|
||||
|
||||
var publishPacket = publishPacketDequeueResult.Item;
|
||||
var eventArgs = await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false);
|
||||
var eventArgs = await HandleReceivedApplicationMessage(publishPacket).ConfigureAwait(false);
|
||||
|
||||
if (eventArgs.AutoAcknowledge)
|
||||
{
|
||||
@@ -600,7 +720,7 @@ namespace MQTTnet.Client
|
||||
// The packet is unknown. Probably due to a restart of the client.
|
||||
// So wen send this to the server to trigger a full resend of the message.
|
||||
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
|
||||
return SendAsync(pubRelPacket, cancellationToken);
|
||||
return Send(pubRelPacket, cancellationToken);
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
@@ -609,40 +729,115 @@ namespace MQTTnet.Client
|
||||
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||
return SendAsync(pubCompPacket, cancellationToken);
|
||||
return Send(pubCompPacket, cancellationToken);
|
||||
}
|
||||
|
||||
async Task<MqttClientPublishResult> PublishAtLeastOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
async Task<MqttClientPublishResult> PublishAtLeastOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var pubAckPacket = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return _clientPublishResultFactory.Create(pubAckPacket);
|
||||
var pubAckPacket = await Request<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
return MqttClientResultFactory.PublishResult.Create(pubAckPacket);
|
||||
}
|
||||
|
||||
async Task<MqttClientPublishResult> PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
|
||||
await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
await Send(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return _clientPublishResultFactory.Create(null);
|
||||
return MqttClientResultFactory.PublishResult.Create(null);
|
||||
}
|
||||
|
||||
async Task<MqttClientPublishResult> PublishExactlyOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
async Task<MqttClientPublishResult> PublishExactlyOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
var pubRecPacket = await Request<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||
|
||||
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
|
||||
var pubCompPacket = await Request<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return _clientPublishResultFactory.Create(pubRecPacket, pubCompPacket);
|
||||
return MqttClientResultFactory.PublishResult.Create(pubRecPacket, pubCompPacket);
|
||||
}
|
||||
|
||||
async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
|
||||
async Task<MqttPacket> Receive(CancellationToken cancellationToken)
|
||||
{
|
||||
var packetTask = _adapter.ReceivePacketAsync(cancellationToken);
|
||||
|
||||
MqttPacket packet;
|
||||
if (packetTask.IsCompleted)
|
||||
{
|
||||
packet = packetTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
packet = await packetTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
async Task ReceivePacketsLoop(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Verbose("Start receiving packets.");
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var packet = await Receive(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet == null)
|
||||
{
|
||||
await DisconnectInternal(_packetReceiverTask, null, null).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await TryProcessReceivedPacket(packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (_cleanDisconnectInitiated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception is AggregateException aggregateException)
|
||||
{
|
||||
exception = aggregateException.GetBaseException();
|
||||
}
|
||||
|
||||
if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
else if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning(exception, "Communication error while receiving packets.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(exception, "Error while receiving packets.");
|
||||
}
|
||||
|
||||
_packetDispatcher.FailAll(exception);
|
||||
|
||||
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose("Stopped receiving packets.");
|
||||
}
|
||||
}
|
||||
|
||||
async Task<TResponsePacket> Request<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -656,7 +851,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
try
|
||||
{
|
||||
await SendAsync(requestPacket, cancellationToken).ConfigureAwait(false);
|
||||
await Send(requestPacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -680,12 +875,13 @@ namespace MQTTnet.Client
|
||||
}
|
||||
}
|
||||
|
||||
Task SendAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
Task Send(MqttPacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
_lastPacketSentTimestamp = DateTime.UtcNow;
|
||||
|
||||
//ThrowIfNotConnected();
|
||||
return _adapter.SendPacketAsync(packet, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -693,7 +889,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (IsConnected)
|
||||
{
|
||||
throw new MqttProtocolViolationException(message);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,17 +897,40 @@ namespace MQTTnet.Client
|
||||
{
|
||||
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()
|
||||
{
|
||||
lock (_disconnectLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Cancel(false);
|
||||
_mqttClientAlive?.Cancel(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -720,7 +939,7 @@ namespace MQTTnet.Client
|
||||
}
|
||||
}
|
||||
|
||||
async Task TryProcessReceivedPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
async Task TryProcessReceivedPacket(MqttPacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -781,80 +1000,11 @@ namespace MQTTnet.Client
|
||||
|
||||
_packetDispatcher.FailAll(exception);
|
||||
|
||||
await DisconnectInternalAsync(_packetReceiverTask, exception, null).ConfigureAwait(false);
|
||||
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task TryReceivePacketsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Verbose("Start receiving packets.");
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
MqttPacket packet;
|
||||
var packetTask = _adapter.ReceivePacketAsync(cancellationToken);
|
||||
|
||||
if (packetTask.IsCompleted)
|
||||
{
|
||||
packet = packetTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
packet = await packetTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet == null)
|
||||
{
|
||||
await DisconnectInternalAsync(_packetReceiverTask, null, null).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (_cleanDisconnectInitiated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception is AggregateException aggregateException)
|
||||
{
|
||||
exception = aggregateException.GetBaseException();
|
||||
}
|
||||
|
||||
if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
else if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning(exception, "Communication error while receiving packets.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(exception, "Error while receiving packets.");
|
||||
}
|
||||
|
||||
_packetDispatcher.FailAll(exception);
|
||||
|
||||
await DisconnectInternalAsync(_packetReceiverTask, exception, null).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose("Stopped receiving packets.");
|
||||
}
|
||||
}
|
||||
|
||||
async Task TrySendKeepAliveMessagesAsync(CancellationToken cancellationToken)
|
||||
async Task TrySendKeepAliveMessages(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -903,43 +1053,12 @@ namespace MQTTnet.Client
|
||||
_logger.Error(exception, "Error exception while sending/receiving keep alive packets.");
|
||||
}
|
||||
|
||||
await DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
|
||||
await DisconnectInternal(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,20 +14,28 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public static class MqttClientExtensions
|
||||
{
|
||||
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
|
||||
public static Task DisconnectAsync(
|
||||
this IMqttClient client,
|
||||
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
|
||||
string reasonString = null,
|
||||
uint sessionExpiryInterval = 0,
|
||||
List<MqttUserProperty> userProperties = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
return client.DisconnectAsync(
|
||||
new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = reason,
|
||||
ReasonString = reasonString
|
||||
},
|
||||
CancellationToken.None);
|
||||
var disconnectOptions = new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = reason,
|
||||
ReasonString = reasonString,
|
||||
SessionExpiryInterval = sessionExpiryInterval,
|
||||
UserProperties = userProperties
|
||||
};
|
||||
|
||||
return client.DisconnectAsync(disconnectOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task<MqttClientPublishResult> PublishBinaryAsync(
|
||||
@@ -128,6 +136,29 @@ namespace MQTTnet.Client
|
||||
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)
|
||||
{
|
||||
if (client == null)
|
||||
|
||||
@@ -12,15 +12,23 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientOptions
|
||||
{
|
||||
/// <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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication data.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] AuthenticationData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication method.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string AuthenticationMethod { get; set; }
|
||||
|
||||
@@ -58,6 +66,10 @@ namespace MQTTnet.Client
|
||||
/// </summary>
|
||||
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 MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||
@@ -65,27 +77,34 @@ namespace MQTTnet.Client
|
||||
/// <summary>
|
||||
/// Gets or sets the receive maximum.
|
||||
/// This gives the maximum length of the receive messages.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public ushort ReceiveMaximum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request problem information.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestProblemInformation { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request response information.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestResponseInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session expiry interval.
|
||||
/// The time after a session expires when it's not actively used.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint SessionExpiryInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether an exception should be thrown when the server has sent a non success ACK packet.
|
||||
/// </summary>
|
||||
public bool ThrowOnNonSuccessfulConnectResponse { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout which will be applied at socket level and internal operations.
|
||||
/// The default value is the same as for sockets in .NET in general.
|
||||
@@ -95,6 +114,7 @@ namespace MQTTnet.Client
|
||||
/// <summary>
|
||||
/// Gets or sets the topic alias maximum.
|
||||
/// This gives the maximum length of the topic alias.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public ushort TopicAliasMaximum { get; set; }
|
||||
|
||||
@@ -103,8 +123,10 @@ namespace MQTTnet.Client
|
||||
/// client.
|
||||
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated
|
||||
/// correctly.
|
||||
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect
|
||||
/// properly.
|
||||
/// <remarks>
|
||||
/// 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>
|
||||
public bool TryPrivate { get; set; } = true;
|
||||
|
||||
@@ -115,28 +137,41 @@ namespace MQTTnet.Client
|
||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
|
||||
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||
/// The feature is very similar to the HTTP header concept.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets the content type of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string WillContentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the correlation data of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] WillCorrelationData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the will delay interval.
|
||||
/// 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>
|
||||
public uint WillDelayInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message expiry interval of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint WillMessageExpiryInterval { get; set; }
|
||||
|
||||
@@ -147,8 +182,9 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the payload format indicator of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; }
|
||||
public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the QoS level of the will message.
|
||||
@@ -157,6 +193,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the response topic of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string WillResponseTopic { get; set; }
|
||||
|
||||
@@ -172,6 +209,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> WillUserProperties { get; set; }
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// 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.
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace MQTTnet.Client
|
||||
Certificates = _tlsParameters.Certificates?.ToList(),
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
ApplicationProtocols = _tlsParameters.ApplicationProtocols,
|
||||
#endif
|
||||
};
|
||||
@@ -74,9 +74,21 @@ namespace MQTTnet.Client
|
||||
|
||||
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
|
||||
|
||||
MqttClientOptionsValidator.ThrowIfNotSupported(_options);
|
||||
|
||||
return _options;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The client will not throw an exception when the MQTT server responses with a non success ACK packet.
|
||||
/// This will become the default behavior in future versions of the library.
|
||||
/// </summary>
|
||||
public MqttClientOptionsBuilder WithoutThrowOnNonSuccessfulConnectResponse()
|
||||
{
|
||||
_options.ThrowOnNonSuccessfulConnectResponse = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
|
||||
{
|
||||
_options.AuthenticationMethod = method;
|
||||
@@ -84,12 +96,24 @@ namespace MQTTnet.Client
|
||||
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)
|
||||
{
|
||||
_options.CleanSession = value;
|
||||
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)
|
||||
{
|
||||
_options.ClientId = value;
|
||||
@@ -186,6 +210,18 @@ namespace MQTTnet.Client
|
||||
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)
|
||||
{
|
||||
if (value == MqttProtocolVersion.Unknown)
|
||||
@@ -264,7 +300,7 @@ namespace MQTTnet.Client
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
|
||||
{
|
||||
if (optionsBuilder == null)
|
||||
@@ -337,16 +373,6 @@ namespace MQTTnet.Client
|
||||
|
||||
public MqttClientOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
if (name is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (_options.UserProperties == null)
|
||||
{
|
||||
_options.UserProperties = new List<MqttUserProperty>();
|
||||
@@ -381,6 +407,18 @@ namespace MQTTnet.Client
|
||||
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)
|
||||
{
|
||||
_options.WillDelayInterval = willDelayInterval;
|
||||
@@ -392,27 +430,45 @@ namespace MQTTnet.Client
|
||||
_options.WillPayload = willPayload;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public MqttClientOptionsBuilder WithWillPayload(ArraySegment<byte> willPayload)
|
||||
{
|
||||
if (willPayload.Count == 0)
|
||||
{
|
||||
_options.WillPayload = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
_options.WillPayload = willPayload.ToArray();
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillPayload(string willPayload)
|
||||
{
|
||||
if (string.IsNullOrEmpty(willPayload))
|
||||
{
|
||||
return WithWillPayload((byte[])null);
|
||||
}
|
||||
|
||||
|
||||
_options.WillPayload = Encoding.UTF8.GetBytes(willPayload);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator)
|
||||
{
|
||||
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel)
|
||||
{
|
||||
_options.WillQualityOfServiceLevel = willQualityOfServiceLevel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillTopic(string willTopic)
|
||||
public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic)
|
||||
{
|
||||
_options.WillTopic = willTopic;
|
||||
_options.WillResponseTopic = willResponseTopic;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -421,38 +477,20 @@ namespace MQTTnet.Client
|
||||
_options.WillRetain = willRetain;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
|
||||
|
||||
public MqttClientOptionsBuilder WithWillTopic(string willTopic)
|
||||
{
|
||||
_options.WillContentType = willContentType;
|
||||
_options.WillTopic = willTopic;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillCorrelationData(byte[] willCorrelationData)
|
||||
{
|
||||
_options.WillCorrelationData = willCorrelationData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic)
|
||||
{
|
||||
_options.WillResponseTopic = willResponseTopic;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator)
|
||||
{
|
||||
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public MqttClientOptionsBuilder WithWillUserProperty(string name, string value)
|
||||
{
|
||||
if (_options.WillUserProperties == null)
|
||||
{
|
||||
_options.WillUserProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
|
||||
_options.WillUserProperties.Add(new MqttUserProperty(name, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
@@ -13,10 +12,10 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClientOptionsBuilderTlsParameters
|
||||
{
|
||||
public bool UseTls { 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;
|
||||
#else
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||
@@ -28,14 +27,14 @@ namespace MQTTnet.Client
|
||||
public IEnumerable<X509Certificate> Certificates { get; set; }
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
public List<SslApplicationProtocol> ApplicationProtocols { get;set; }
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; }
|
||||
#endif
|
||||
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
|
||||
public bool IgnoreCertificateChainErrors { get; set; }
|
||||
|
||||
public bool IgnoreCertificateRevocationErrors { 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
@@ -29,13 +30,23 @@ namespace MQTTnet.Client
|
||||
public List<X509Certificate> Certificates { get; set; }
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; }
|
||||
|
||||
public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; }
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
|
||||
|
||||
public CipherSuitesPolicy CipherSuitesPolicy { get; set; }
|
||||
|
||||
public EncryptionPolicy EncryptionPolicy { get; set; } = EncryptionPolicy.RequireEncryption;
|
||||
|
||||
public bool AllowRenegotiation { get; set; } = true;
|
||||
#endif
|
||||
|
||||
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
|
||||
/// <summary>
|
||||
/// Gets or sets the target host.
|
||||
/// If the value is null or empty the same host as the TCP socket host will be used.
|
||||
/// </summary>
|
||||
public string TargetHost { get; set; }
|
||||
|
||||
#if NET48 || NETCOREAPP3_1_OR_GREATER
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||
#else
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientWebSocketOptions : IMqttClientChannelOptions
|
||||
{
|
||||
public CookieContainer CookieContainer { get; set; }
|
||||
|
||||
|
||||
public ICredentials Credentials { get; set; }
|
||||
|
||||
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
|
||||
|
||||
public IDictionary<string, string> RequestHeaders { get; set; }
|
||||
@@ -18,12 +22,34 @@ namespace MQTTnet.Client
|
||||
public ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" };
|
||||
|
||||
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
||||
|
||||
|
||||
public string Uri { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Uri;
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
#if !WINDOWS_UWP
|
||||
/// <summary>
|
||||
/// Gets or sets the keep alive interval for the Web Socket connection.
|
||||
/// This is not related to the keep alive interval for the MQTT protocol.
|
||||
/// </summary>
|
||||
public TimeSpan KeepAliveInterval { get; set; } = WebSocket.DefaultKeepAliveInterval;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the default (system) credentials should be used when connecting via Web Socket connection.
|
||||
/// This is not related to the credentials which are used for the MQTT protocol.
|
||||
/// </summary>
|
||||
public bool UseDefaultCredentials { get; set; }
|
||||
#else
|
||||
/// <summary>
|
||||
/// Gets or sets the keep alive interval for the Web Socket connection.
|
||||
/// This is not related to the keep alive interval for the MQTT protocol.
|
||||
/// </summary>
|
||||
public TimeSpan KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(30);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ namespace MQTTnet.Client
|
||||
|
||||
public bool BypassOnLocal { get; set; }
|
||||
|
||||
public bool UseDefaultCredentials { get; set; }
|
||||
|
||||
public string[] BypassList { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,18 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientPublishResult
|
||||
{
|
||||
public MqttClientPublishResult(ushort? packetIdentifier, MqttClientPublishReasonCode reasonCode, string reasonString, IReadOnlyCollection<MqttUserProperty> userProperties)
|
||||
{
|
||||
PacketIdentifier = packetIdentifier;
|
||||
ReasonCode = reasonCode;
|
||||
ReasonString = reasonString;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool IsSuccess => ReasonCode == MqttClientPublishReasonCode.Success || ReasonCode == MqttClientPublishReasonCode.NoMatchingSubscribers;
|
||||
@@ -19,19 +28,19 @@ namespace MQTTnet.Client
|
||||
/// <summary>
|
||||
/// Gets the packet identifier which was used for this publish.
|
||||
/// </summary>
|
||||
public ushort? PacketIdentifier { get; set; }
|
||||
public ushort? PacketIdentifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientPublishReasonCode ReasonCode { get; set; } = MqttClientPublishReasonCode.Success;
|
||||
public MqttClientPublishReasonCode ReasonCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason string.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ReasonString { get; set; }
|
||||
public string ReasonString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||
/// The feature is very similar to the HTTP header concept.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal set; }
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -10,25 +10,23 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientPublishResultFactory
|
||||
{
|
||||
static readonly MqttClientPublishResult EmptySuccessResult = new MqttClientPublishResult();
|
||||
static readonly IReadOnlyCollection<MqttUserProperty> EmptyUserProperties = new List<MqttUserProperty>();
|
||||
static readonly MqttClientPublishResult AtMostOnceSuccessResult = new MqttClientPublishResult(null, MqttClientPublishReasonCode.Success, null, EmptyUserProperties);
|
||||
|
||||
public MqttClientPublishResult Create(MqttPubAckPacket pubAckPacket)
|
||||
{
|
||||
// QoS 0 has no response. So we treat it as a success always.
|
||||
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.
|
||||
ReasonCode = (MqttClientPublishReasonCode)(int)pubAckPacket.ReasonCode,
|
||||
PacketIdentifier = pubAckPacket.PacketIdentifier,
|
||||
ReasonString = pubAckPacket.ReasonString,
|
||||
UserProperties = pubAckPacket.UserProperties ?? EmptyUserProperties
|
||||
};
|
||||
(MqttClientPublishReasonCode)(int)pubAckPacket.ReasonCode,
|
||||
pubAckPacket.ReasonString,
|
||||
pubAckPacket.UserProperties ?? EmptyUserProperties);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -37,43 +35,28 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (pubRecPacket == null || pubCompPacket == null)
|
||||
{
|
||||
return new MqttClientPublishResult
|
||||
{
|
||||
ReasonCode = MqttClientPublishReasonCode.UnspecifiedError
|
||||
};
|
||||
var packetIdentifier = pubRecPacket?.PacketIdentifier ?? pubCompPacket?.PacketIdentifier;
|
||||
return new MqttClientPublishResult(packetIdentifier, MqttClientPublishReasonCode.ImplementationSpecificError, null, EmptyUserProperties);
|
||||
}
|
||||
|
||||
MqttClientPublishResult result;
|
||||
|
||||
// The PUBCOMP is the last packet in QoS 2. So we use the results from that instead of PUBREC.
|
||||
if (pubCompPacket.ReasonCode == MqttPubCompReasonCode.PacketIdentifierNotFound)
|
||||
{
|
||||
result = new MqttClientPublishResult
|
||||
{
|
||||
PacketIdentifier = pubCompPacket.PacketIdentifier,
|
||||
ReasonCode = MqttClientPublishReasonCode.UnspecifiedError,
|
||||
ReasonString = pubCompPacket.ReasonString,
|
||||
UserProperties = pubCompPacket.UserProperties ?? EmptyUserProperties
|
||||
};
|
||||
|
||||
return result;
|
||||
return new MqttClientPublishResult(
|
||||
pubCompPacket.PacketIdentifier,
|
||||
MqttClientPublishReasonCode.UnspecifiedError,
|
||||
pubCompPacket.ReasonString,
|
||||
pubCompPacket.UserProperties ?? EmptyUserProperties);
|
||||
}
|
||||
|
||||
result = new MqttClientPublishResult
|
||||
{
|
||||
PacketIdentifier = pubCompPacket.PacketIdentifier,
|
||||
ReasonCode = MqttClientPublishReasonCode.Success,
|
||||
ReasonString = pubCompPacket.ReasonString,
|
||||
UserProperties = pubCompPacket.UserProperties ?? EmptyUserProperties
|
||||
};
|
||||
|
||||
var reasonCode = MqttClientPublishReasonCode.Success;
|
||||
if (pubRecPacket.ReasonCode != MqttPubRecReasonCode.Success)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ namespace MQTTnet.Client
|
||||
|
||||
internal MqttPublishPacket PublishPacket { get; set; }
|
||||
|
||||
public Task AcknowledgeAsync(CancellationToken cancellationToken)
|
||||
public Task AcknowledgeAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_acknowledgeHandler == null)
|
||||
{
|
||||
|
||||
@@ -10,27 +10,31 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClientSubscribeOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a list of topic filters the client wants to subscribe to.
|
||||
/// Topic filters can include regular topics or wild cards.
|
||||
/// </summary>
|
||||
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.
|
||||
/// 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>
|
||||
/// Gets or sets the user properties.
|
||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
|
||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||
/// The feature is very similar to the HTTP header concept.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets a list of topic filters the client wants to subscribe to.
|
||||
/// Topic filters can include regular topics or wild cards.
|
||||
/// </summary>
|
||||
public List<MqttTopicFilter> TopicFilters { get; set; } = new List<MqttTopicFilter>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties.
|
||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||
/// packet.
|
||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
|
||||
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||
/// The feature is very similar to the HTTP header concept.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,26 +14,9 @@ namespace MQTTnet.Client
|
||||
{
|
||||
readonly MqttClientSubscribeOptions _subscribeOptions = new MqttClientSubscribeOptions();
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
public MqttClientSubscribeOptions Build()
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
if (value == null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
if (_subscribeOptions.UserProperties == null)
|
||||
{
|
||||
_subscribeOptions.UserProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
_subscribeOptions.UserProperties.Add(new MqttUserProperty(name, value));
|
||||
|
||||
return this;
|
||||
return _subscribeOptions;
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptionsBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier)
|
||||
@@ -42,7 +25,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
throw new MqttProtocolViolationException("Subscription identifier cannot be 0.");
|
||||
}
|
||||
|
||||
|
||||
_subscribeOptions.SubscriptionIdentifier = subscriptionIdentifier;
|
||||
return this;
|
||||
}
|
||||
@@ -54,19 +37,23 @@ namespace MQTTnet.Client
|
||||
bool retainAsPublished = false,
|
||||
MqttRetainHandling retainHandling = MqttRetainHandling.SendAtSubscribe)
|
||||
{
|
||||
return WithTopicFilter(new MqttTopicFilter
|
||||
{
|
||||
Topic = topic,
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
NoLocal = noLocal,
|
||||
RetainAsPublished = retainAsPublished,
|
||||
RetainHandling = retainHandling
|
||||
});
|
||||
return WithTopicFilter(
|
||||
new MqttTopicFilter
|
||||
{
|
||||
Topic = topic,
|
||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
||||
NoLocal = noLocal,
|
||||
RetainAsPublished = retainAsPublished,
|
||||
RetainHandling = retainHandling
|
||||
});
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptionsBuilder WithTopicFilter(Action<MqttTopicFilterBuilder> topicFilterBuilder)
|
||||
{
|
||||
if (topicFilterBuilder == null) throw new ArgumentNullException(nameof(topicFilterBuilder));
|
||||
if (topicFilterBuilder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilterBuilder));
|
||||
}
|
||||
|
||||
var internalTopicFilterBuilder = new MqttTopicFilterBuilder();
|
||||
topicFilterBuilder(internalTopicFilterBuilder);
|
||||
@@ -76,14 +63,20 @@ namespace MQTTnet.Client
|
||||
|
||||
public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilterBuilder topicFilterBuilder)
|
||||
{
|
||||
if (topicFilterBuilder == null) throw new ArgumentNullException(nameof(topicFilterBuilder));
|
||||
if (topicFilterBuilder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilterBuilder));
|
||||
}
|
||||
|
||||
return WithTopicFilter(topicFilterBuilder.Build());
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter)
|
||||
{
|
||||
if (topicFilter == null) throw new ArgumentNullException(nameof(topicFilter));
|
||||
if (topicFilter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilter));
|
||||
}
|
||||
|
||||
if (_subscribeOptions.TopicFilters == null)
|
||||
{
|
||||
@@ -95,9 +88,20 @@ namespace MQTTnet.Client
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientSubscribeOptions Build()
|
||||
/// <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.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
@@ -9,23 +10,34 @@ namespace MQTTnet.Client
|
||||
{
|
||||
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>
|
||||
/// Gets the user properties which were part of the SUBACK packet.
|
||||
/// MQTTv5 only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal set; }
|
||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason string.
|
||||
/// MQTTv5 only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ReasonString { get; internal set; }
|
||||
public string ReasonString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the packet identifier which was used.
|
||||
/// </summary>
|
||||
public ushort PacketIdentifier { get; internal set; }
|
||||
public ushort PacketIdentifier { get; }
|
||||
}
|
||||
}
|
||||
@@ -16,42 +16,35 @@ namespace MQTTnet.Client
|
||||
|
||||
public MqttClientSubscribeResult Create(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket)
|
||||
{
|
||||
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
|
||||
if (subAckPacket == null) throw new ArgumentNullException(nameof(subAckPacket));
|
||||
|
||||
if (subscribePacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subscribePacket));
|
||||
}
|
||||
|
||||
if (subAckPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subAckPacket));
|
||||
}
|
||||
|
||||
// MQTTv5.0.0 handling.
|
||||
if (subAckPacket.ReasonCodes.Any() && subAckPacket.ReasonCodes.Count != subscribePacket.TopicFilters.Count)
|
||||
{
|
||||
throw new MqttProtocolViolationException(
|
||||
"The reason codes are not matching the topic filters [MQTT-3.9.3-1].");
|
||||
throw new MqttProtocolViolationException("The reason codes are not matching the topic filters [MQTT-3.9.3-1].");
|
||||
}
|
||||
|
||||
|
||||
var items = new List<MqttClientSubscribeResultItem>();
|
||||
for (var i = 0; i < subscribePacket.TopicFilters.Count; i++)
|
||||
{
|
||||
items.Add(CreateSubscribeResultItem(i, subscribePacket, subAckPacket));
|
||||
}
|
||||
|
||||
var result = new MqttClientSubscribeResult
|
||||
{
|
||||
PacketIdentifier = subAckPacket.PacketIdentifier,
|
||||
ReasonString = subAckPacket.ReasonString,
|
||||
UserProperties = subAckPacket.UserProperties ?? EmptyUserProperties,
|
||||
Items = items
|
||||
};
|
||||
|
||||
return result;
|
||||
|
||||
return new MqttClientSubscribeResult(subAckPacket.PacketIdentifier, items, subAckPacket.ReasonString, subAckPacket.UserProperties ?? EmptyUserProperties);
|
||||
}
|
||||
|
||||
static MqttClientSubscribeResultItem CreateSubscribeResultItem(int index, MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket)
|
||||
{
|
||||
var resultCode = (MqttClientSubscribeResultCode) subAckPacket.ReasonCodes[index];
|
||||
|
||||
return new MqttClientSubscribeResultItem
|
||||
{
|
||||
TopicFilter = subscribePacket.TopicFilters[index],
|
||||
ResultCode = resultCode,
|
||||
};
|
||||
var resultCode = (MqttClientSubscribeResultCode)subAckPacket.ReasonCodes[index];
|
||||
return new MqttClientSubscribeResultItem(subscribePacket.TopicFilters[index], resultCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,22 +2,29 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientSubscribeResultItem
|
||||
{
|
||||
public MqttClientSubscribeResultItem(MqttTopicFilter topicFilter, MqttClientSubscribeResultCode resultCode)
|
||||
{
|
||||
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
|
||||
ResultCode = resultCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the topic filter.
|
||||
/// The topic filter can contain topics and wildcards.
|
||||
/// </summary>
|
||||
public MqttTopicFilter TopicFilter { get; internal set; }
|
||||
public MqttTopicFilter TopicFilter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the result code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientSubscribeResultCode ResultCode { get; internal set; }
|
||||
public MqttClientSubscribeResultCode ResultCode { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,18 +10,20 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClientUnsubscribeOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a list of topic filters the client wants to unsubscribe from.
|
||||
/// Topic filters can include regular topics or wild cards.
|
||||
/// Gets or sets a list of topic filters the client wants to unsubscribe from.
|
||||
/// Topic filters can include regular topics or wild cards.
|
||||
/// </summary>
|
||||
public List<string> TopicFilters { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties.
|
||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
|
||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||
/// The feature is very similar to the HTTP header concept.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the user properties.
|
||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||
/// packet.
|
||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
|
||||
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||
/// The feature is very similar to the HTTP header concept.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </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
|
||||
{
|
||||
public class MqttClientUnsubscribeOptionsBuilder
|
||||
public sealed class MqttClientUnsubscribeOptionsBuilder
|
||||
{
|
||||
private readonly MqttClientUnsubscribeOptions _unsubscribeOptions = new MqttClientUnsubscribeOptions();
|
||||
readonly MqttClientUnsubscribeOptions _unsubscribeOptions = new MqttClientUnsubscribeOptions();
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
public MqttClientUnsubscribeOptions Build()
|
||||
{
|
||||
if (name is null) throw new ArgumentNullException(nameof(name));
|
||||
if (value is null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
return WithUserProperty(new MqttUserProperty(name, value));
|
||||
}
|
||||
|
||||
/// <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;
|
||||
return _unsubscribeOptions;
|
||||
}
|
||||
|
||||
public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(string topic)
|
||||
{
|
||||
if (topic is null) throw new ArgumentNullException(nameof(topic));
|
||||
if (topic is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
|
||||
if (_unsubscribeOptions.TopicFilters is null)
|
||||
{
|
||||
@@ -63,14 +36,42 @@ namespace MQTTnet.Client
|
||||
|
||||
public MqttClientUnsubscribeOptionsBuilder WithTopicFilter(MqttTopicFilter topicFilter)
|
||||
{
|
||||
if (topicFilter is null) throw new ArgumentNullException(nameof(topicFilter));
|
||||
if (topicFilter is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilter));
|
||||
}
|
||||
|
||||
return WithTopicFilter(topicFilter.Topic);
|
||||
}
|
||||
|
||||
public MqttClientUnsubscribeOptions Build()
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user