mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 09:13:02 +08:00
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df0b268a08 | ||
|
|
addf812d58 | ||
|
|
533b8e22c2 | ||
|
|
3aaf101b3b | ||
|
|
449624fd72 | ||
|
|
99f4f46062 | ||
|
|
b08d11c069 | ||
|
|
ac41e838ec | ||
|
|
836ddf3bbc | ||
|
|
0e815c626c | ||
|
|
9d9076ba77 | ||
|
|
af1c151e8e | ||
|
|
4b7d035e9e | ||
|
|
cb3c44f48d | ||
|
|
8b3b9d33de | ||
|
|
59d99509c8 | ||
|
|
2933b76237 | ||
|
|
36c6d8974e | ||
|
|
d6cab76728 | ||
|
|
b4f740f338 | ||
|
|
202e299f65 | ||
|
|
a004de25ac | ||
|
|
129348c07b | ||
|
|
d0584413ca | ||
|
|
ba57537108 | ||
|
|
6dcb110737 | ||
|
|
b599a832da | ||
|
|
9cbd5dd778 | ||
|
|
ed31c51237 | ||
|
|
e01707d25e | ||
|
|
73f118fe1c | ||
|
|
0234918f30 | ||
|
|
c90ba7e061 |
@@ -1,8 +1,4 @@
|
|||||||
* [Core] Added net7.0 builds (#1635, thanks to @YAJeff).
|
* [Core] Add validation of maximum string lengths (#1718).
|
||||||
* [Client] Added support for passing MQTT v5 options (User properties etc.) for disconnects.
|
* [Client] Added overloads for setting packet payload and will payload (#1720).
|
||||||
* [Client] An internal exception is no longer caught silently when calling _DisconnectAsync_ to indicate that the disconnect is not clean (BREAKING CHANGE).
|
* [Client] The proper connect result is now exposed in the _Disconnected_ event when authentication fails (#1139).
|
||||||
* [Client] MQTTv5 features are now checked and an exception is thrown if they are used when using protocol version 3.1.1 and lower. These checks can be disabled in client options. (BREAKING CHANGE!).
|
* [Server] Improved performance by changing internal locking strategy for subscriptions (#1716, thanks to @zeheng).
|
||||||
* [Server] Exposed MQTT v5 sent properties from the affected client in _ClientDisconnectedAsync_ event.
|
|
||||||
* [Server] Fixed wrong client ID passed to _InterceptingUnsubscriptionEventArgs_ (#1631, thanks to @ghord).
|
|
||||||
* [Server] Exposed socket settings for TCP keep alive in TCP options (#1544).
|
|
||||||
* [Server] Exposed server session items at server level and allow custom session items for injected application messages (#1638).
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: CI
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERSION: "4.2.0.${{github.run_number}}"
|
VERSION: "4.2.1.${{github.run_number}}"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -50,6 +50,22 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
|
|||||||
* A custom message interceptor can be added which allows transforming or extending every received application message
|
* A custom message interceptor can be added which allows transforming or extending every received application message
|
||||||
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
|
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/MQTTnet/
|
||||||
|
|
||||||
|
Use these command in the Package Manager console to install MQTTnet manually:
|
||||||
|
```
|
||||||
|
Install-Package MQTTnet
|
||||||
|
```
|
||||||
|
|
||||||
|
Samples for using MQTTnet are part of this repository. For starters these samples are recommended:
|
||||||
|
|
||||||
|
- [Connect with a broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Connection_Samples.cs)
|
||||||
|
- [Subscribing to data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Subscribe_Samples.cs)
|
||||||
|
- [Publishing data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Publish_Samples.cs)
|
||||||
|
- [Host own broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Server/Server_Simple_Samples.cs)
|
||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
|
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
|
||||||
|
|||||||
@@ -70,6 +70,32 @@ public static class Client_Connection_Samples
|
|||||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
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()
|
public static async Task Connect_Client_Timeout()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public static class PackageInspection_Samples
|
|||||||
.WithTcpServer("broker.hivemq.com")
|
.WithTcpServer("broker.hivemq.com")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
mqttClient.InspectPackage += OnInspectPackage;
|
mqttClient.InspectPacketAsync += OnInspectPacket;
|
||||||
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ public static class PackageInspection_Samples
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Task OnInspectPackage(InspectMqttPacketEventArgs eventArgs)
|
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
|
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
|
||||||
{
|
{
|
||||||
|
|||||||
+18
-1
@@ -4,7 +4,13 @@
|
|||||||
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
Console.WriteLine("Welcome to MQTTnet samples!");
|
Console.BackgroundColor = ConsoleColor.White;
|
||||||
|
Console.BackgroundColor = ConsoleColor.Red;
|
||||||
|
Console.Title = "Samples - MQTTnet";
|
||||||
|
Console.WriteLine("----------------------------------------------");
|
||||||
|
Console.WriteLine("- Welcome to MQTTnet samples! -");
|
||||||
|
Console.WriteLine("----------------------------------------------");
|
||||||
|
Console.ResetColor();
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
|
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
|
||||||
@@ -16,7 +22,11 @@ foreach (var sampleClass in sampleClasses)
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
Console.Write("Please choose sample class (press Enter to continue): ");
|
Console.Write("Please choose sample class (press Enter to continue): ");
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
var input = Console.ReadLine();
|
var input = Console.ReadLine();
|
||||||
var selectedIndex = int.Parse(input ?? "0");
|
var selectedIndex = int.Parse(input ?? "0");
|
||||||
var selectedSampleClass = sampleClasses[selectedIndex];
|
var selectedSampleClass = sampleClasses[selectedIndex];
|
||||||
@@ -29,12 +39,19 @@ foreach (var sampleMethod in sampleMethods)
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
Console.Write("Please choose sample (press Enter to continue): ");
|
Console.Write("Please choose sample (press Enter to continue): ");
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
input = Console.ReadLine();
|
input = Console.ReadLine();
|
||||||
selectedIndex = int.Parse(input ?? "0");
|
selectedIndex = int.Parse(input ?? "0");
|
||||||
var selectedSampleMethod = sampleMethods[selectedIndex];
|
var selectedSampleMethod = sampleMethods[selectedIndex];
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
Console.WriteLine("Executing sample...");
|
Console.WriteLine("Executing sample...");
|
||||||
|
Console.ResetColor();
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ using MQTTnet.Server;
|
|||||||
|
|
||||||
namespace MQTTnet.Samples.Server;
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
public static class Server_Asp_Net_Samples
|
public static class Server_ASP_NET_Samples
|
||||||
{
|
{
|
||||||
public static Task Start_Server_With_WebSockets_Support()
|
public static Task Start_Server_With_WebSockets_Support()
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
|
public static class Server_TLS_Samples
|
||||||
|
{
|
||||||
|
public static async Task Run_Server_With_Self_Signed_Certificate()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample starts a simple MQTT server which will accept any TCP connection.
|
||||||
|
* It also has an encrypted connection using a self signed TLS certificate.
|
||||||
|
*
|
||||||
|
* See sample "Run_Minimal_Server" for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
|
// This certificate is self signed so that
|
||||||
|
var certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
|
||||||
|
|
||||||
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
|
||||||
|
|
||||||
|
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||||
|
{
|
||||||
|
await mqttServer.StartAsync();
|
||||||
|
|
||||||
|
Console.WriteLine("Press Enter to exit.");
|
||||||
|
Console.ReadLine();
|
||||||
|
|
||||||
|
// Stop and dispose the MQTT server if it is no longer needed!
|
||||||
|
await mqttServer.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509Certificate2 CreateSelfSignedCertificate(string oid)
|
||||||
|
{
|
||||||
|
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||||
|
sanBuilder.AddDnsName("localhost");
|
||||||
|
|
||||||
|
using (var rsa = RSA.Create())
|
||||||
|
{
|
||||||
|
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(
|
||||||
|
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||||
|
|
||||||
|
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
|
||||||
|
{
|
||||||
|
var pfxCertificate = new X509Certificate2(
|
||||||
|
certificate.Export(X509ContentType.Pfx),
|
||||||
|
(string)null!,
|
||||||
|
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||||
|
|
||||||
|
return pfxCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -103,7 +103,7 @@ namespace MQTTnet.AspNetCore.Tests
|
|||||||
connection.Transport = pipe;
|
connection.Transport = pipe;
|
||||||
var ctx = new MqttConnectionContext(serializer, connection);
|
var ctx = new MqttConnectionContext(serializer, connection);
|
||||||
|
|
||||||
await ctx.SendPacketAsync(new MqttPublishPacket { Payload = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false);
|
await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new ArraySegment<byte>(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
var readResult = await pipe.Send.Reader.ReadAsync();
|
var readResult = await pipe.Send.Reader.ReadAsync();
|
||||||
Assert.IsTrue(readResult.Buffer.Length > 20000);
|
Assert.IsTrue(readResult.Buffer.Length > 20000);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace MQTTnet.AspNetCore.Tests
|
|||||||
{
|
{
|
||||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||||
|
|
||||||
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", Payload = new byte[5]}).Join();
|
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", PayloadSegment = new byte[5]}).Join();
|
||||||
|
|
||||||
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
|
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
|
||||||
|
|
||||||
|
|||||||
@@ -2,38 +2,34 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Implementations;
|
|
||||||
using MQTTnet.Server;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore
|
||||||
{
|
{
|
||||||
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||||
{
|
{
|
||||||
IMqttNetLogger _logger = new MqttNetNullLogger();
|
IMqttNetLogger _logger = MqttNetNullLogger.Instance;
|
||||||
|
|
||||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||||
|
|
||||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync()
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
||||||
{
|
{
|
||||||
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
|
if (webSocket == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(webSocket));
|
||||||
|
}
|
||||||
|
|
||||||
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
|
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
|
||||||
|
|
||||||
@@ -48,7 +44,7 @@ namespace MQTTnet.AspNetCore
|
|||||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
||||||
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
|
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
|
||||||
|
|
||||||
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _logger))
|
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
|
||||||
{
|
{
|
||||||
await clientHandler(channelAdapter).ConfigureAwait(false);
|
await clientHandler(channelAdapter).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -60,8 +56,15 @@ namespace MQTTnet.AspNetCore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync()
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
var channel = new MemoryMqttChannel(_stream);
|
var channel = new MemoryMqttChannel(_stream);
|
||||||
|
|
||||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
|
||||||
}
|
}
|
||||||
|
|
||||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||||
|
|||||||
@@ -30,17 +30,17 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
var writer = new MqttBufferWriter(4096, 65535);
|
var writer = new MqttBufferWriter(4096, 65535);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
writer.WriteByte(0x01);
|
writer.WriteByte(0x01);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteVariableByteInteger(5647382);
|
writer.WriteVariableByteInteger(5647382);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteVariableByteInteger(8574489);
|
writer.WriteVariableByteInteger(8574489);
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
writer.WriteByte(2);
|
writer.WriteByte(2);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
|
|
||||||
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
||||||
}
|
}
|
||||||
@@ -83,11 +83,11 @@ namespace MQTTnet.Benchmarks
|
|||||||
writer.WriteVariableByteInteger(5647382);
|
writer.WriteVariableByteInteger(5647382);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteVariableByteInteger(8574589);
|
writer.WriteVariableByteInteger(8574589);
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
writer.WriteByte(2);
|
writer.WriteByte(2);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||||
writer.WriteBinaryData(_demoPayload);
|
writer.WriteBinary(_demoPayload);
|
||||||
|
|
||||||
writer.Reset(0);
|
writer.Reset(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
public void Deserialize_10000_Messages()
|
public void Deserialize_10000_Messages()
|
||||||
{
|
{
|
||||||
var channel = new BenchmarkMqttChannel(_serializedPacket);
|
var channel = new BenchmarkMqttChannel(_serializedPacket);
|
||||||
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
|
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
|
||||||
|
|
||||||
for (var i = 0; i < 10000; i++)
|
for (var i = 0; i < 10000; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -353,13 +353,13 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
|
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task HandleSubscriptionExceptionAsync(Exception exception)
|
async Task HandleSubscriptionExceptionAsync(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
|
||||||
{
|
{
|
||||||
_logger.Warning(exception, "Synchronizing subscriptions failed.");
|
_logger.Warning(exception, "Synchronizing subscriptions failed.");
|
||||||
|
|
||||||
if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
|
if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
|
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception, addedSubscriptions, removedSubscriptions)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,11 +457,13 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
{
|
{
|
||||||
_logger.Info("Publishing subscriptions at reconnect");
|
_logger.Info("Publishing subscriptions at reconnect");
|
||||||
|
|
||||||
|
List<MqttTopicFilter> topicFilters = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_reconnectSubscriptions.Any())
|
if (_reconnectSubscriptions.Any())
|
||||||
{
|
{
|
||||||
var topicFilters = new List<MqttTopicFilter>();
|
topicFilters = new List<MqttTopicFilter>();
|
||||||
|
|
||||||
foreach (var sub in _reconnectSubscriptions)
|
foreach (var sub in _reconnectSubscriptions)
|
||||||
{
|
{
|
||||||
@@ -479,7 +481,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
|
await HandleSubscriptionExceptionAsync(exception, topicFilters, null).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,6 +593,9 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
|
await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
|
||||||
|
|
||||||
|
//clear because these worked, maybe the subscribe below will fail, only report those
|
||||||
|
removedSubscriptions.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addedSubscriptions != null && addedSubscriptions.Any())
|
if (addedSubscriptions != null && addedSubscriptions.Any())
|
||||||
@@ -607,7 +612,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
|
await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
|
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
|
||||||
/// Amazon AWS limits this number to 8. The default is int.MaxValue.
|
/// Amazon Web Services (AWS) limits this number to 8. The default is int.MaxValue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
|
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,33 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MQTTnet.Packets;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.ManagedClient
|
namespace MQTTnet.Extensions.ManagedClient
|
||||||
{
|
{
|
||||||
public class ManagedProcessFailedEventArgs : EventArgs
|
public class ManagedProcessFailedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public ManagedProcessFailedEventArgs(Exception exception)
|
public ManagedProcessFailedEventArgs(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
|
||||||
{
|
{
|
||||||
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
||||||
|
|
||||||
|
if (addedSubscriptions != null)
|
||||||
|
{
|
||||||
|
AddedSubscriptions = new List<string>(addedSubscriptions.Select(item => item.Topic));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedSubscriptions != null)
|
||||||
|
{
|
||||||
|
RemovedSubscriptions = new List<string>(removedSubscriptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Exception Exception { get; }
|
public Exception Exception { get; }
|
||||||
|
|
||||||
|
public List<string> AddedSubscriptions { get; }
|
||||||
|
public List<string> RemovedSubscriptions { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,22 +12,37 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
{
|
{
|
||||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
|
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)
|
if (mqttClient == null)
|
||||||
{
|
{
|
||||||
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
|
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
|
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
if (factory == null)
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
{
|
||||||
|
throw new ArgumentNullException(nameof(factory));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory _)
|
||||||
|
{
|
||||||
|
return new ManagedMqttClientOptionsBuilder();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
@@ -119,7 +120,6 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_waitingCalls.TryRemove(responseTopic, out _);
|
_waitingCalls.TryRemove(responseTopic, out _);
|
||||||
|
|
||||||
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,8 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
|
var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
|
||||||
|
awaitable.TrySetResult(payloadBuffer);
|
||||||
|
|
||||||
// Set this message to handled to that other code can avoid execution etc.
|
// Set this message to handled to that other code can avoid execution etc.
|
||||||
eventArgs.IsHandled = true;
|
eventArgs.IsHandled = true;
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
return new MqttChannelAdapter(
|
return new MqttChannelAdapter(
|
||||||
new MqttTcpChannel(options),
|
new MqttTcpChannel(options),
|
||||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||||
packetInspector,
|
logger)
|
||||||
logger);
|
{
|
||||||
|
PacketInspector = packetInspector
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case MqttClientWebSocketOptions webSocketOptions:
|
case MqttClientWebSocketOptions webSocketOptions:
|
||||||
@@ -36,8 +38,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
return new MqttChannelAdapter(
|
return new MqttChannelAdapter(
|
||||||
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
||||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||||
packetInspector,
|
logger)
|
||||||
logger);
|
{
|
||||||
|
PacketInspector = packetInspector
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -33,9 +33,18 @@ namespace MQTTnet.TestApp
|
|||||||
|
|
||||||
client.ApplicationMessageReceivedAsync += e =>
|
client.ApplicationMessageReceivedAsync += e =>
|
||||||
{
|
{
|
||||||
|
var payloadText = string.Empty;
|
||||||
|
if (e.ApplicationMessage.PayloadSegment.Count > 0)
|
||||||
|
{
|
||||||
|
payloadText = Encoding.UTF8.GetString(
|
||||||
|
e.ApplicationMessage.PayloadSegment.Array,
|
||||||
|
e.ApplicationMessage.PayloadSegment.Offset,
|
||||||
|
e.ApplicationMessage.PayloadSegment.Count);
|
||||||
|
}
|
||||||
|
|
||||||
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
|
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
|
||||||
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
|
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
|
||||||
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}");
|
Console.WriteLine($"+ Payload = {payloadText}");
|
||||||
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
|
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
|
||||||
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
|
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ namespace MQTTnet.TestApp
|
|||||||
return new MqttApplicationMessage
|
return new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "A/B/C",
|
Topic = "A/B/C",
|
||||||
Payload = Encoding.UTF8.GetBytes(Payload),
|
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(Payload)),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,7 @@ namespace MQTTnet.TestApp
|
|||||||
var message = new MqttApplicationMessage
|
var message = new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "A/B/C",
|
Topic = "A/B/C",
|
||||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ namespace MQTTnet.TestApp
|
|||||||
var message = new MqttApplicationMessage
|
var message = new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "A/B/C",
|
Topic = "A/B/C",
|
||||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@ namespace MQTTnet.TestApp
|
|||||||
var message = new MqttApplicationMessage
|
var message = new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = "A/B/C",
|
Topic = "A/B/C",
|
||||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
@@ -100,7 +101,7 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
// Replace the payload with the timestamp. But also extending a JSON
|
// Replace the payload with the timestamp. But also extending a JSON
|
||||||
// based payload with the timestamp is a suitable use case.
|
// based payload with the timestamp is a suitable use case.
|
||||||
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"));
|
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.ApplicationMessage.Topic == "not_allowed_topic")
|
if (e.ApplicationMessage.Topic == "not_allowed_topic")
|
||||||
@@ -143,10 +144,16 @@ namespace MQTTnet.TestApp
|
|||||||
|
|
||||||
mqttServer.InterceptingPublishAsync += e =>
|
mqttServer.InterceptingPublishAsync += e =>
|
||||||
{
|
{
|
||||||
MqttNetConsoleLogger.PrintToConsole(
|
var payloadText = string.Empty;
|
||||||
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'",
|
if (e.ApplicationMessage.PayloadSegment.Count > 0)
|
||||||
ConsoleColor.Magenta);
|
{
|
||||||
|
payloadText = Encoding.UTF8.GetString(
|
||||||
|
e.ApplicationMessage.PayloadSegment.Array,
|
||||||
|
e.ApplicationMessage.PayloadSegment.Offset,
|
||||||
|
e.ApplicationMessage.PayloadSegment.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta);
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,38 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class ManagedMqttClient_Tests : BaseTestClass
|
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]
|
[TestMethod]
|
||||||
public async Task Receive_While_Not_Cleanly_Disconnected()
|
public async Task Receive_While_Not_Cleanly_Disconnected()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Clients.MqttClient
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class Extended_Authentication_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task ReAuthenticate()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
server.ValidatingConnectionAsync += async args =>
|
||||||
|
{
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
|
||||||
|
{
|
||||||
|
// Just do a simple custom authentication.
|
||||||
|
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, cancellationToken: timeout.Token);
|
||||||
|
var clientResponse = await args.ReceiveAuthenticationDataAsync(timeout.Token);
|
||||||
|
CollectionAssert.AreEqual(clientResponse.AuthenticationData, Encoding.ASCII.GetBytes("TOKEN"));
|
||||||
|
|
||||||
|
var userProperties = new MqttUserPropertiesBuilder().With("x", "y").Build();
|
||||||
|
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, cancellationToken: timeout.Token, userProperties: userProperties);
|
||||||
|
|
||||||
|
args.ReasonCode = MqttConnectReasonCode.Success;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
server.ClientReAuthenticatingAsync += args =>
|
||||||
|
{
|
||||||
|
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = testEnvironment.CreateClient();
|
||||||
|
client.ExtendedAuthenticationExchangeAsync += async args =>
|
||||||
|
{
|
||||||
|
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, Encoding.ASCII.GetBytes("TOKEN"));
|
||||||
|
|
||||||
|
var serverResponse = await args.ReceiveAuthenticationDataAsync(CancellationToken.None);
|
||||||
|
Assert.IsNotNull(serverResponse.UserProperties);
|
||||||
|
Assert.AreEqual("x", serverResponse.UserProperties[0].Name);
|
||||||
|
Assert.AreEqual("y", serverResponse.UserProperties[0].Value);
|
||||||
|
};
|
||||||
|
|
||||||
|
var clientOptions = testEnvironment.CreateDefaultClientOptionsBuilder().WithTimeout(TimeSpan.FromSeconds(10)).WithAuthentication("CUSTOM").Build();
|
||||||
|
await client.ConnectAsync(clientOptions);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
await client.ReAuthenticateAsync();
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
var pingResult = await client.TryPingAsync();
|
||||||
|
|
||||||
|
Assert.IsTrue(pingResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Use_Extended_Authentication()
|
||||||
|
{
|
||||||
|
var initialContextToken = Encoding.ASCII.GetBytes("initial context token");
|
||||||
|
var replyContextToken = Encoding.ASCII.GetBytes("reply context token");
|
||||||
|
var outcomeOfAuthentication = Encoding.ASCII.GetBytes("outcome of authentication");
|
||||||
|
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sample flow from RFC:
|
||||||
|
|
||||||
|
1. Client to Server CONNECT Authentication Method="GS2-KRB5"
|
||||||
|
2. Server to Client AUTH rc=0x18 Authentication Method="GS2-KRB5"
|
||||||
|
3. Client to Server AUTH rc=0x18 Authentication Method="GS2-KRB5" Authentication Data=initial context token
|
||||||
|
4. Server to Client AUTH rc=0x18 Authentication Method="GS2-KRB5" Authentication Data=reply context token
|
||||||
|
5. Client to Server AUTH rc=0x18 Authentication Method="GS2-KRB5"
|
||||||
|
6. Server to Client CONNACK rc=0 Authentication Method="GS2-KRB5" Authentication Data=outcome of authentication
|
||||||
|
*/
|
||||||
|
|
||||||
|
server.ValidatingConnectionAsync += async args =>
|
||||||
|
{
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
|
||||||
|
{
|
||||||
|
// 1.
|
||||||
|
if (args.AuthenticationMethod != "GS2-KRB5")
|
||||||
|
{
|
||||||
|
args.ReasonCode = MqttConnectReasonCode.BadAuthenticationMethod;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.
|
||||||
|
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, cancellationToken: timeout.Token);
|
||||||
|
|
||||||
|
// 3.
|
||||||
|
var clientResponse = await args.ReceiveAuthenticationDataAsync(timeout.Token);
|
||||||
|
CollectionAssert.AreEqual(clientResponse.AuthenticationData, initialContextToken);
|
||||||
|
Assert.AreEqual(MqttAuthenticateReasonCode.ContinueAuthentication, clientResponse.ReasonCode);
|
||||||
|
|
||||||
|
// 4.
|
||||||
|
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, replyContextToken, cancellationToken: timeout.Token);
|
||||||
|
|
||||||
|
// 5.
|
||||||
|
clientResponse = await args.ReceiveAuthenticationDataAsync(timeout.Token);
|
||||||
|
Assert.AreEqual(clientResponse.AuthenticationData, null);
|
||||||
|
Assert.AreEqual(MqttAuthenticateReasonCode.ContinueAuthentication, clientResponse.ReasonCode);
|
||||||
|
|
||||||
|
// 6.
|
||||||
|
args.ResponseAuthenticationData = outcomeOfAuthentication;
|
||||||
|
args.ReasonCode = MqttConnectReasonCode.Success;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = testEnvironment.CreateClient();
|
||||||
|
client.ExtendedAuthenticationExchangeAsync += async args =>
|
||||||
|
{
|
||||||
|
if (args.AuthenticationMethod != "GS2-KRB5")
|
||||||
|
{
|
||||||
|
Assert.Fail("Authentication method is wrong.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. THE FACT THAT THE SERVER SENDS THE AUTH PACKET WILL TRIGGER THIS EVENT SO THE INITIAL DATA IS IN THE EVENT ARGS!
|
||||||
|
Assert.AreEqual(MqttAuthenticateReasonCode.ContinueAuthentication, args.ReasonCode);
|
||||||
|
|
||||||
|
// 3.
|
||||||
|
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, initialContextToken);
|
||||||
|
|
||||||
|
// 4.
|
||||||
|
var serverResponse = await args.ReceiveAuthenticationDataAsync(CancellationToken.None);
|
||||||
|
CollectionAssert.AreEqual(serverResponse.AuthenticationData, replyContextToken);
|
||||||
|
Assert.AreEqual(MqttAuthenticateReasonCode.ContinueAuthentication, serverResponse.ReasonCode);
|
||||||
|
|
||||||
|
// 5.
|
||||||
|
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication);
|
||||||
|
};
|
||||||
|
|
||||||
|
var clientOptions = testEnvironment.CreateDefaultClientOptionsBuilder().WithTimeout(TimeSpan.FromSeconds(10)).WithAuthentication("GS2-KRB5").Build();
|
||||||
|
await client.ConnectAsync(clientOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,6 +99,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
// Perform a clean disconnect.
|
// Perform a clean disconnect.
|
||||||
await client.DisconnectAsync(disconnectOptions);
|
await client.DisconnectAsync(disconnectOptions);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.IsNotNull(eventArgs);
|
Assert.IsNotNull(eventArgs);
|
||||||
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
|
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
|
||||||
}
|
}
|
||||||
@@ -125,6 +127,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
// Perform a clean disconnect.
|
// Perform a clean disconnect.
|
||||||
await client.DisconnectAsync(disconnectOptions);
|
await client.DisconnectAsync(disconnectOptions);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.IsNotNull(eventArgs);
|
Assert.IsNotNull(eventArgs);
|
||||||
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
|
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
|
||||||
}
|
}
|
||||||
@@ -150,6 +154,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
|
|
||||||
// Perform a clean disconnect.
|
// Perform a clean disconnect.
|
||||||
await client.DisconnectAsync(disconnectOptions);
|
await client.DisconnectAsync(disconnectOptions);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.IsNotNull(eventArgs);
|
Assert.IsNotNull(eventArgs);
|
||||||
Assert.IsNotNull(eventArgs.UserProperties);
|
Assert.IsNotNull(eventArgs.UserProperties);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace MQTTnet.Tests.Diagnostics
|
|||||||
|
|
||||||
var packets = new List<string>();
|
var packets = new List<string>();
|
||||||
|
|
||||||
mqttClient.InspectPackage += eventArgs =>
|
mqttClient.InspectPacketAsync += eventArgs =>
|
||||||
{
|
{
|
||||||
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
|
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
PacketIdentifier = 123,
|
PacketIdentifier = 123,
|
||||||
Dup = true,
|
Dup = true,
|
||||||
Retain = true,
|
Retain = true,
|
||||||
Payload = Encoding.ASCII.GetBytes("HELLO"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||||
Topic = "A/B/C"
|
Topic = "A/B/C"
|
||||||
};
|
};
|
||||||
@@ -318,10 +318,9 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
var publishPacket = new MqttPublishPacket
|
var publishPacket = new MqttPublishPacket
|
||||||
{
|
{
|
||||||
Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
|
Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
|
||||||
Payload = payload
|
PayloadSegment = new ArraySegment<byte>(payload)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var serializationHelper = new MqttPacketSerializationHelper();
|
var serializationHelper = new MqttPacketSerializationHelper();
|
||||||
|
|
||||||
var buffer = serializationHelper.Encode(publishPacket);
|
var buffer = serializationHelper.Encode(publishPacket);
|
||||||
@@ -329,17 +328,17 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
|
|
||||||
Assert.IsNotNull(publishPacketCopy);
|
Assert.IsNotNull(publishPacketCopy);
|
||||||
Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic);
|
Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic);
|
||||||
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy.Payload);
|
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy.PayloadSegment.ToArray());
|
||||||
|
|
||||||
// Now modify the payload and test again.
|
// Now modify the payload and test again.
|
||||||
publishPacket.Payload = Encoding.UTF8.GetBytes("MQTT");
|
publishPacket.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("MQTT"));
|
||||||
|
|
||||||
buffer = serializationHelper.Encode(publishPacket);
|
buffer = serializationHelper.Encode(publishPacket);
|
||||||
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
|
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
|
||||||
|
|
||||||
Assert.IsNotNull(publishPacketCopy2);
|
Assert.IsNotNull(publishPacketCopy2);
|
||||||
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
|
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
|
||||||
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy2.Payload);
|
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy2.PayloadSegment.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -463,7 +462,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
PacketIdentifier = 123,
|
PacketIdentifier = 123,
|
||||||
Dup = true,
|
Dup = true,
|
||||||
Retain = true,
|
Retain = true,
|
||||||
Payload = Encoding.ASCII.GetBytes("HELLO"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||||
Topic = "A/B/C"
|
Topic = "A/B/C"
|
||||||
};
|
};
|
||||||
@@ -567,7 +566,6 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
using (var adapter = new MqttChannelAdapter(
|
using (var adapter = new MqttChannelAdapter(
|
||||||
channel,
|
channel,
|
||||||
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
|
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
|
||||||
null,
|
|
||||||
new MqttNetEventLogger()))
|
new MqttNetEventLogger()))
|
||||||
{
|
{
|
||||||
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
@@ -583,19 +581,12 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
|
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
|
||||||
{
|
{
|
||||||
var channel = new MemoryMqttChannel(buffer);
|
var channel = new MemoryMqttChannel(buffer);
|
||||||
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, null, new MqttNetEventLogger());
|
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, new MqttNetEventLogger());
|
||||||
|
|
||||||
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
return packetFormatterAdapter.ProtocolVersion;
|
return packetFormatterAdapter.ProtocolVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttBufferReader ReaderFactory(byte[] data)
|
|
||||||
{
|
|
||||||
var reader = new MqttBufferReader();
|
|
||||||
reader.SetBuffer(data, 0, data.Length);
|
|
||||||
return reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket
|
TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket
|
||||||
{
|
{
|
||||||
var writer = bufferWriter ?? WriterFactory();
|
var writer = bufferWriter ?? WriterFactory();
|
||||||
@@ -604,7 +595,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
|
|
||||||
using (var channel = new MemoryMqttChannel(buffer.Join().ToArray()))
|
using (var channel = new MemoryMqttChannel(buffer.Join().ToArray()))
|
||||||
{
|
{
|
||||||
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
|
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
|
||||||
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
@@ -296,7 +298,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
PacketIdentifier = 123,
|
PacketIdentifier = 123,
|
||||||
Dup = true,
|
Dup = true,
|
||||||
Retain = true,
|
Retain = true,
|
||||||
Payload = Encoding.ASCII.GetBytes("Payload"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||||
Topic = "Topic",
|
Topic = "Topic",
|
||||||
ResponseTopic = "/Response",
|
ResponseTopic = "/Response",
|
||||||
@@ -320,7 +322,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
||||||
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
||||||
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
|
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
|
||||||
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload);
|
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
|
||||||
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
|
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
|
||||||
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
|
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
|
||||||
Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1.
|
Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1.
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
@@ -257,7 +259,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
PacketIdentifier = 123,
|
PacketIdentifier = 123,
|
||||||
Dup = true,
|
Dup = true,
|
||||||
Retain = true,
|
Retain = true,
|
||||||
Payload = Encoding.ASCII.GetBytes("Payload"),
|
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
|
||||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||||
Topic = "Topic",
|
Topic = "Topic",
|
||||||
ResponseTopic = "/Response",
|
ResponseTopic = "/Response",
|
||||||
@@ -281,7 +283,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
||||||
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
||||||
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
|
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
|
||||||
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload);
|
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
|
||||||
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
|
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
|
||||||
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
|
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
|
||||||
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
|
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Formatter
|
namespace MQTTnet.Tests.Formatter
|
||||||
@@ -47,23 +48,32 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
Assert.AreEqual(1234U, reader.ReadVariableByteInteger());
|
Assert.AreEqual(1234U, reader.ReadVariableByteInteger());
|
||||||
Assert.AreEqual(9876U, 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]
|
[TestMethod]
|
||||||
public void Write_And_Read_Multiple_Times()
|
public void Write_And_Read_Multiple_Times()
|
||||||
{
|
{
|
||||||
var writer = new MqttBufferWriter(4096, 65535);
|
var writer = new MqttBufferWriter(4096, 65535);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteBinaryData(new byte[1234]);
|
writer.WriteBinary(new byte[1234]);
|
||||||
writer.WriteByte(0x01);
|
writer.WriteByte(0x01);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteVariableByteInteger(5647382);
|
writer.WriteVariableByteInteger(5647382);
|
||||||
writer.WriteString("A relative short string.");
|
writer.WriteString("A relative short string.");
|
||||||
writer.WriteVariableByteInteger(8574489);
|
writer.WriteVariableByteInteger(8574489);
|
||||||
writer.WriteBinaryData(new byte[48]);
|
writer.WriteBinary(new byte[48]);
|
||||||
writer.WriteByte(2);
|
writer.WriteByte(2);
|
||||||
writer.WriteByte(0x02);
|
writer.WriteByte(0x02);
|
||||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||||
writer.WriteBinaryData(new byte[3]);
|
writer.WriteBinary(new byte[3]);
|
||||||
|
|
||||||
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
||||||
|
|
||||||
|
|||||||
@@ -51,10 +51,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
public void AssertReceivedCountEquals(int expectedCount)
|
public void AssertReceivedCountEquals(int expectedCount)
|
||||||
{
|
{
|
||||||
lock (_receivedEventArgs)
|
Assert.AreEqual(expectedCount, Count);
|
||||||
{
|
|
||||||
Assert.AreEqual(expectedCount, _receivedEventArgs.Count);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GeneratePayloadSequence()
|
public string GeneratePayloadSequence()
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
|
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
|
||||||
{
|
{
|
||||||
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort);
|
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort).WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ILowLevelMqttClient CreateLowLevelClient()
|
public ILowLevelMqttClient CreateLowLevelClient()
|
||||||
@@ -277,8 +277,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
// Null is used when the client id is assigned from the server!
|
// Null is used when the client id is assigned from the server!
|
||||||
if (!string.IsNullOrEmpty(e.ClientId) && !e.ClientId.StartsWith(TestContext.TestName))
|
if (!string.IsNullOrEmpty(e.ClientId) && !e.ClientId.StartsWith(TestContext.TestName))
|
||||||
{
|
{
|
||||||
TrackException(new InvalidOperationException($"Invalid client ID used ({e.ClientId}). It must start with UnitTest name."));
|
Assert.Fail($"Client ID does not start with test name ({TestContext.TestName}).");
|
||||||
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,23 +291,12 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
{
|
{
|
||||||
foreach (var mqttClient in _clients)
|
foreach (var mqttClient in _clients)
|
||||||
{
|
{
|
||||||
try
|
mqttClient?.Dispose();
|
||||||
{
|
|
||||||
//mqttClient.DisconnectAsync().GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// This can happen when the test already disconnected the client.
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
mqttClient?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var lowLevelMqttClient in _lowLevelClients)
|
foreach (var lowLevelMqttClient in _lowLevelClients)
|
||||||
{
|
{
|
||||||
lowLevelMqttClient.Dispose();
|
lowLevelMqttClient?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ namespace MQTTnet.Tests
|
|||||||
{
|
{
|
||||||
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
|
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
|
||||||
|
|
||||||
var adapter = new MqttChannelAdapter(channel, formatterAdapter, null, MqttNetNullLogger.Instance);
|
var adapter = new MqttChannelAdapter(channel, formatterAdapter, MqttNetNullLogger.Instance);
|
||||||
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ namespace MQTTnet.Tests
|
|||||||
writer.WriteString("1234567890");
|
writer.WriteString("1234567890");
|
||||||
Assert.AreEqual(10 + 2, writer.Length);
|
Assert.AreEqual(10 + 2, writer.Length);
|
||||||
|
|
||||||
writer.WriteBinaryData(new byte[300]);
|
writer.WriteBinary(new byte[300]);
|
||||||
Assert.AreEqual(300 + 2 + 12, writer.Length);
|
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);
|
Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -954,7 +954,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
var client = testEnvironment.CreateClient();
|
var client = testEnvironment.CreateClient();
|
||||||
|
|
||||||
client.InspectPackage += e =>
|
client.InspectPacketAsync += e =>
|
||||||
{
|
{
|
||||||
if (e.Buffer.Length > 0)
|
if (e.Buffer.Length > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
|
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
|
||||||
{
|
{
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out _, out _);
|
MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
|
||||||
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
|
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Protocol;
|
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public sealed class Publish_Tests : BaseTestClass
|
|
||||||
{
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Return_Success_When_Subscribed()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var sender = await testEnvironment.ConnectClient();
|
|
||||||
var receiver = await testEnvironment.ConnectClient();
|
|
||||||
|
|
||||||
await receiver.SubscribeAsync("A");
|
|
||||||
|
|
||||||
// AtLeastOnce is required to get an ACK packet from the server.
|
|
||||||
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
|
||||||
|
|
||||||
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
|
|
||||||
|
|
||||||
Assert.AreEqual(true, publishResult.IsSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var sender = await testEnvironment.ConnectClient();
|
|
||||||
var receiver = await testEnvironment.ConnectClient();
|
|
||||||
|
|
||||||
await receiver.SubscribeAsync("A");
|
|
||||||
|
|
||||||
// AtLeastOnce is required to get an ACK packet from the server.
|
|
||||||
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
|
||||||
|
|
||||||
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
|
|
||||||
|
|
||||||
Assert.AreEqual(true, publishResult.IsSuccess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
@@ -27,5 +29,118 @@ namespace MQTTnet.Tests.Server
|
|||||||
await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
var receiver = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
await receiver.SubscribeAsync("A");
|
||||||
|
|
||||||
|
// AtLeastOnce is required to get an ACK packet from the server.
|
||||||
|
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
|
||||||
|
|
||||||
|
Assert.AreEqual(true, publishResult.IsSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Return_Success_When_Subscribed()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
var receiver = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
await receiver.SubscribeAsync("A");
|
||||||
|
|
||||||
|
// AtLeastOnce is required to get an ACK packet from the server.
|
||||||
|
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
|
||||||
|
|
||||||
|
Assert.AreEqual(true, publishResult.IsSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Intercept_Client_Enqueue()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
var receiver = await testEnvironment.ConnectClient();
|
||||||
|
await receiver.SubscribeAsync("A");
|
||||||
|
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||||
|
|
||||||
|
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
receivedMessages.AssertReceivedCountEquals(1);
|
||||||
|
|
||||||
|
server.InterceptingClientEnqueueAsync += e =>
|
||||||
|
{
|
||||||
|
e.AcceptEnqueue = false;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Do not increase because the internal enqueue to the target client is not accepted!
|
||||||
|
receivedMessages.AssertReceivedCountEquals(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Intercept_Client_Enqueue_Multiple_Clients_Subscribed_Messages_Are_Filtered()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
var receiverOne = await testEnvironment.ConnectClient(o => o.WithClientId("One"));
|
||||||
|
await receiverOne.SubscribeAsync("A");
|
||||||
|
var receivedMessagesOne = testEnvironment.CreateApplicationMessageHandler(receiverOne);
|
||||||
|
|
||||||
|
var receiverTwo = await testEnvironment.ConnectClient(o => o.WithClientId("Two"));
|
||||||
|
await receiverTwo.SubscribeAsync("A");
|
||||||
|
var receivedMessagesTwo = testEnvironment.CreateApplicationMessageHandler(receiverTwo);
|
||||||
|
|
||||||
|
var receiverThree = await testEnvironment.ConnectClient(o => o.WithClientId("Three"));
|
||||||
|
await receiverThree.SubscribeAsync("A");
|
||||||
|
var receivedMessagesThree = testEnvironment.CreateApplicationMessageHandler(receiverThree);
|
||||||
|
|
||||||
|
server.InterceptingClientEnqueueAsync += e =>
|
||||||
|
{
|
||||||
|
if (e.ReceiverClientId.Contains("Two")) e.AcceptEnqueue = false;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
receivedMessagesOne.AssertReceivedCountEquals(1);
|
||||||
|
receivedMessagesTwo.AssertReceivedCountEquals(0);
|
||||||
|
receivedMessagesThree.AssertReceivedCountEquals(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build();
|
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build();
|
||||||
|
|
||||||
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions));
|
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => client.ConnectAsync(clientOptions));
|
||||||
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
|
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Deny_Invalid_Topic()
|
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]
|
[TestMethod]
|
||||||
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
||||||
public async Task Disconnect_While_Subscribing()
|
public async Task Disconnect_While_Subscribing()
|
||||||
@@ -105,7 +128,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
await client.SubscribeAsync("#");
|
await client.SubscribeAsync("#");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Enqueue_Message_After_Subscription()
|
public async Task Enqueue_Message_After_Subscription()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var topics = t.Value;
|
var topics = t.Value;
|
||||||
foreach (var topic in topics)
|
foreach (var topic in topics)
|
||||||
{
|
{
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
||||||
|
|
||||||
bucketDepths.TryGetValue(topicHash, out var currentValue);
|
bucketDepths.TryGetValue(topicHash, out var currentValue);
|
||||||
++currentValue;
|
++currentValue;
|
||||||
@@ -96,7 +96,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
{
|
{
|
||||||
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
|
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
}
|
}
|
||||||
@@ -110,7 +110,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
{
|
{
|
||||||
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
|
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
// UInt64 is limited to 8 levels
|
// UInt64 is limited to 8 levels
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
var topic = sb.ToString();
|
var topic = sb.ToString();
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
|
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
var topic = sb.ToString();
|
var topic = sb.ToString();
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
|
||||||
@@ -319,7 +319,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var l3 = "#";
|
var l3 = "#";
|
||||||
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
|
||||||
@@ -354,7 +354,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var l3 = "prop1";
|
var l3 = "prop1";
|
||||||
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
|
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
|
||||||
|
|
||||||
@@ -447,7 +447,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var l3 = "prop1";
|
var l3 = "prop1";
|
||||||
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
|
||||||
@@ -475,7 +475,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
|
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
|
||||||
{
|
{
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
||||||
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine("Topic: " + topic);
|
Console.WriteLine("Topic: " + topic);
|
||||||
@@ -501,7 +501,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
{
|
{
|
||||||
var topicToFind = topicsToFind[countDown];
|
var topicToFind = topicsToFind[countDown];
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topicToFind, out var topicHash, out _, out _);
|
MqttTopicHash.Calculate(topicToFind, out var topicHash, out _, out _);
|
||||||
|
|
||||||
clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result);
|
clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result);
|
||||||
if (result.IsSubscribed)
|
if (result.IsSubscribed)
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
#if !WINDOWS_UWP && (NET48_OR_GREATER || NET5_0_OR_GREATER)
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Authentication;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Certificates;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class Tls_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
static X509Certificate2 CreateCertificate(string oid)
|
||||||
|
{
|
||||||
|
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||||
|
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
|
||||||
|
sanBuilder.AddDnsName("localhost");
|
||||||
|
|
||||||
|
using (var rsa = RSA.Create())
|
||||||
|
{
|
||||||
|
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(
|
||||||
|
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false));
|
||||||
|
|
||||||
|
certRequest.CertificateExtensions.Add(sanBuilder.Build());
|
||||||
|
|
||||||
|
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
|
||||||
|
{
|
||||||
|
var pfxCertificate = new X509Certificate2(
|
||||||
|
certificate.Export(X509ContentType.Pfx),
|
||||||
|
(string)null,
|
||||||
|
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
|
||||||
|
|
||||||
|
return pfxCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Tls_Swap_Test()
|
||||||
|
{
|
||||||
|
var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
|
||||||
|
var serverOptionsBuilder = testEnvironment.Factory.CreateServerOptionsBuilder();
|
||||||
|
|
||||||
|
var firstOid = "1.3.6.1.5.5.7.3.1";
|
||||||
|
var secondOid = "1.3.6.1.5.5.7.3.2";
|
||||||
|
|
||||||
|
var certificateProvider = new CertificateProvider
|
||||||
|
{
|
||||||
|
CurrentCertificate = CreateCertificate(firstOid)
|
||||||
|
};
|
||||||
|
|
||||||
|
serverOptionsBuilder.WithoutDefaultEndpoint().WithEncryptedEndpoint().WithEncryptionSslProtocol(SslProtocols.Tls12).WithEncryptionCertificate(certificateProvider);
|
||||||
|
|
||||||
|
var serverOptions = serverOptionsBuilder.Build();
|
||||||
|
|
||||||
|
var server = testEnvironment.CreateServer(serverOptions);
|
||||||
|
|
||||||
|
var publishedCount = 0;
|
||||||
|
server.InterceptingPublishAsync += args =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref publishedCount);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await server.StartAsync();
|
||||||
|
|
||||||
|
var firstClient = await ConnectClientAsync(
|
||||||
|
testEnvironment,
|
||||||
|
args =>
|
||||||
|
{
|
||||||
|
Assert.AreEqual(firstOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var firstClientReceivedCount = 0;
|
||||||
|
firstClient.ApplicationMessageReceivedAsync += args =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref firstClientReceivedCount);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await firstClient.SubscribeAsync("TestTopic1");
|
||||||
|
|
||||||
|
await firstClient.PublishAsync(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4 }
|
||||||
|
});
|
||||||
|
|
||||||
|
await testEnvironment.Server.InjectApplicationMessage(
|
||||||
|
new InjectedMqttApplicationMessage(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4 }
|
||||||
|
}));
|
||||||
|
|
||||||
|
certificateProvider.CurrentCertificate = CreateCertificate(secondOid);
|
||||||
|
|
||||||
|
// Validate that the certificate was switched
|
||||||
|
var secondClient = await ConnectClientAsync(
|
||||||
|
testEnvironment,
|
||||||
|
args =>
|
||||||
|
{
|
||||||
|
Assert.AreEqual(secondOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
var secondClientReceivedCount = 0;
|
||||||
|
secondClient.ApplicationMessageReceivedAsync += args =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref secondClientReceivedCount);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await secondClient.SubscribeAsync("TestTopic2");
|
||||||
|
|
||||||
|
await firstClient.PublishAsync(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic2",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4 }
|
||||||
|
});
|
||||||
|
|
||||||
|
await testEnvironment.Server.InjectApplicationMessage(
|
||||||
|
new InjectedMqttApplicationMessage(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic2",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4 }
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Ensure first client still works
|
||||||
|
await firstClient.PublishAsync(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4 }
|
||||||
|
});
|
||||||
|
|
||||||
|
await testEnvironment.Server.InjectApplicationMessage(
|
||||||
|
new InjectedMqttApplicationMessage(
|
||||||
|
new MqttApplicationMessage
|
||||||
|
{
|
||||||
|
Topic = "TestTopic1",
|
||||||
|
Payload = new byte[] { 1, 2, 3, 4 }
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(6, publishedCount);
|
||||||
|
Assert.AreEqual(4, firstClientReceivedCount);
|
||||||
|
Assert.AreEqual(2, secondClientReceivedCount);
|
||||||
|
|
||||||
|
await server.StopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Task<IMqttClient> ConnectClientAsync(TestEnvironment testEnvironment, Func<MqttClientCertificateValidationEventArgs, bool> certValidator)
|
||||||
|
{
|
||||||
|
var clientOptionsBuilder = testEnvironment.Factory.CreateClientOptionsBuilder();
|
||||||
|
clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString())
|
||||||
|
.WithTcpServer("localhost", 8883)
|
||||||
|
.WithTls(
|
||||||
|
tls =>
|
||||||
|
{
|
||||||
|
tls.UseTls = true;
|
||||||
|
tls.SslProtocol = SslProtocols.Tls12;
|
||||||
|
tls.CertificateValidationHandler = certValidator;
|
||||||
|
});
|
||||||
|
|
||||||
|
var clientOptions = clientOptionsBuilder.Build();
|
||||||
|
return await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class CertificateProvider : ICertificateProvider
|
||||||
|
{
|
||||||
|
public X509Certificate2 CurrentCertificate { get; set; }
|
||||||
|
|
||||||
|
public X509Certificate2 GetCertificate()
|
||||||
|
{
|
||||||
|
return CurrentCertificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -2,10 +2,14 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
{
|
{
|
||||||
@@ -28,5 +32,28 @@ namespace MQTTnet.Tests.Server
|
|||||||
await client.UnsubscribeAsync("#");
|
await client.UnsubscribeAsync("#");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Intercept_Unsubscribe_With_User_Properties()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
InterceptingUnsubscriptionEventArgs eventArgs = null;
|
||||||
|
server.InterceptingUnsubscriptionAsync += e =>
|
||||||
|
{
|
||||||
|
eventArgs = e;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
var unsubscribeOptions = testEnvironment.Factory.CreateUnsubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build();
|
||||||
|
await client.UnsubscribeAsync(unsubscribeOptions);
|
||||||
|
|
||||||
|
CollectionAssert.AreEqual(unsubscribeOptions.UserProperties.ToList(), eventArgs.UserProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,18 +150,8 @@ namespace MQTTnet.Tests
|
|||||||
{
|
{
|
||||||
Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter));
|
Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter));
|
||||||
|
|
||||||
ulong topicHash;
|
MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
|
||||||
ulong topicHashMask;
|
MqttTopicHash.Calculate(filter, out var filterTopicHash, out var filterTopicHashMask, out _);
|
||||||
bool topicHasWildcard;
|
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
|
|
||||||
|
|
||||||
|
|
||||||
ulong filterTopicHash;
|
|
||||||
ulong filterTopicHashMask;
|
|
||||||
bool filterTopicHasWildcard;
|
|
||||||
|
|
||||||
MqttSubscription.CalculateTopicHash(filter, out filterTopicHash, out filterTopicHashMask, out filterTopicHasWildcard);
|
|
||||||
|
|
||||||
if (expectedResult == MqttTopicFilterCompareResult.IsMatch)
|
if (expectedResult == MqttTopicFilterCompareResult.IsMatch)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,18 +27,15 @@ namespace MQTTnet.Adapter
|
|||||||
readonly byte[] _fixedHeaderBuffer = new byte[2];
|
readonly byte[] _fixedHeaderBuffer = new byte[2];
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
|
|
||||||
readonly MqttPacketInspector _packetInspector;
|
|
||||||
|
|
||||||
readonly byte[] _singleByteBuffer = new byte[1];
|
readonly byte[] _singleByteBuffer = new byte[1];
|
||||||
|
|
||||||
readonly AsyncLock _syncRoot = new AsyncLock();
|
readonly AsyncLock _syncRoot = new AsyncLock();
|
||||||
|
|
||||||
Statistics _statistics; // mutable struct, don't make readonly!
|
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));
|
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
|
||||||
_packetInspector = packetInspector;
|
|
||||||
|
|
||||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||||
|
|
||||||
@@ -64,6 +61,10 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||||
|
|
||||||
|
public MqttPacketInspector PacketInspector { get; set; }
|
||||||
|
|
||||||
|
public bool AllowPacketFragmentation { get; set; } = true;
|
||||||
|
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
@@ -126,7 +127,7 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_packetInspector?.BeginReceivePacket();
|
PacketInspector?.BeginReceivePacket();
|
||||||
|
|
||||||
ReceivedMqttPacket receivedPacket;
|
ReceivedMqttPacket receivedPacket;
|
||||||
var receivedPacketTask = ReceiveAsync(cancellationToken);
|
var receivedPacketTask = ReceiveAsync(cancellationToken);
|
||||||
@@ -144,7 +145,7 @@ namespace MQTTnet.Adapter
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_packetInspector?.EndReceivePacket();
|
PacketInspector?.EndReceivePacket();
|
||||||
|
|
||||||
Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength);
|
Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength);
|
||||||
|
|
||||||
@@ -180,7 +181,10 @@ namespace MQTTnet.Adapter
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetStatistics() => _statistics.Reset();
|
public void ResetStatistics()
|
||||||
|
{
|
||||||
|
_statistics.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -197,18 +201,18 @@ namespace MQTTnet.Adapter
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var packetBuffer = PacketFormatterAdapter.Encode(packet);
|
var packetBuffer = PacketFormatterAdapter.Encode(packet);
|
||||||
_packetInspector?.BeginSendPacket(packetBuffer);
|
PacketInspector?.BeginSendPacket(packetBuffer);
|
||||||
|
|
||||||
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
|
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
|
||||||
|
|
||||||
if (packetBuffer.Payload.Count > 0)
|
if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation)
|
||||||
{
|
{
|
||||||
await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false);
|
await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false);
|
||||||
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await _channel.WriteAsync(packetBuffer.Packet, true, cancellationToken).ConfigureAwait(false);
|
await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length);
|
Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length);
|
||||||
@@ -270,7 +274,7 @@ namespace MQTTnet.Adapter
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_packetInspector?.FillReceiveBuffer(_singleByteBuffer);
|
PacketInspector?.FillReceiveBuffer(_singleByteBuffer);
|
||||||
|
|
||||||
encodedByte = _singleByteBuffer[0];
|
encodedByte = _singleByteBuffer[0];
|
||||||
|
|
||||||
@@ -309,7 +313,7 @@ namespace MQTTnet.Adapter
|
|||||||
{
|
{
|
||||||
return ReadFixedHeaderResult.ConnectionClosed;
|
return ReadFixedHeaderResult.ConnectionClosed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
return ReadFixedHeaderResult.Canceled;
|
return ReadFixedHeaderResult.Canceled;
|
||||||
@@ -323,7 +327,7 @@ namespace MQTTnet.Adapter
|
|||||||
totalBytesRead += bytesRead;
|
totalBytesRead += bytesRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
_packetInspector?.FillReceiveBuffer(buffer);
|
PacketInspector?.FillReceiveBuffer(buffer);
|
||||||
|
|
||||||
var hasRemainingLength = buffer[1] != 0;
|
var hasRemainingLength = buffer[1] != 0;
|
||||||
if (!hasRemainingLength)
|
if (!hasRemainingLength)
|
||||||
@@ -407,7 +411,7 @@ namespace MQTTnet.Adapter
|
|||||||
bodyOffset += readBytes;
|
bodyOffset += readBytes;
|
||||||
} while (bodyOffset < bodyLength);
|
} while (bodyOffset < bodyLength);
|
||||||
|
|
||||||
_packetInspector?.FillReceiveBuffer(body);
|
PacketInspector?.FillReceiveBuffer(body);
|
||||||
|
|
||||||
var bodySegment = new ArraySegment<byte>(body, 0, bodyLength);
|
var bodySegment = new ArraySegment<byte>(body, 0, bodyLength);
|
||||||
return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength);
|
return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength);
|
||||||
@@ -455,7 +459,7 @@ namespace MQTTnet.Adapter
|
|||||||
throw new MqttCommunicationException(exception);
|
throw new MqttCommunicationException(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Statistics
|
struct Statistics
|
||||||
{
|
{
|
||||||
public long _bytesReceived;
|
public long _bytesReceived;
|
||||||
public long _bytesSent;
|
public long _bytesSent;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class MqttClientPartialAuthenticationResponse
|
||||||
|
{
|
||||||
|
public byte[] AuthenticationData { get; set; }
|
||||||
|
|
||||||
|
public MqttAuthenticateReasonCode ReasonCode { get; set; }
|
||||||
|
|
||||||
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
// 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 MQTTnet.Client.Internal;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class MqttExtendedAuthenticationExchangeEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
static readonly IReadOnlyCollection<MqttUserProperty> EmptyUserProperties = new List<MqttUserProperty>();
|
||||||
|
|
||||||
|
readonly IMqttAuthenticationTransportStrategy _transportStrategy;
|
||||||
|
|
||||||
|
public MqttExtendedAuthenticationExchangeEventArgs(MqttAuthPacket authPacket, IMqttAuthenticationTransportStrategy transportStrategy)
|
||||||
|
{
|
||||||
|
if (authPacket == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(authPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
_transportStrategy = transportStrategy ?? throw new ArgumentNullException(nameof(transportStrategy));
|
||||||
|
|
||||||
|
ReasonCode = authPacket.ReasonCode;
|
||||||
|
ReasonString = authPacket.ReasonString;
|
||||||
|
AuthenticationMethod = authPacket.AuthenticationMethod;
|
||||||
|
AuthenticationData = authPacket.AuthenticationData;
|
||||||
|
UserProperties = authPacket.UserProperties ?? EmptyUserProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the authentication data.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public byte[] AuthenticationData { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the authentication method.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public string AuthenticationMethod { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the reason code.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public MqttAuthenticateReasonCode ReasonCode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the reason string.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public string ReasonString { get; }
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// Gets the response which will be sent to the server.
|
||||||
|
// /// </summary>
|
||||||
|
// public MqttExtendedAuthenticationExchangeResponse Response { get; } = new MqttExtendedAuthenticationExchangeResponse();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user properties.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||||
|
|
||||||
|
public async Task<MqttClientPartialAuthenticationResponse> ReceiveAuthenticationDataAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var receivePacket = await _transportStrategy.ReceivePacketAsync(AuthenticationMethod, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (receivePacket == null)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("The client closed the connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MqttClientPartialAuthenticationResponse
|
||||||
|
{
|
||||||
|
ReasonCode = receivePacket.ReasonCode,
|
||||||
|
AuthenticationData = receivePacket.AuthenticationData,
|
||||||
|
UserProperties = receivePacket.UserProperties
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SendAuthenticationDataAsync(
|
||||||
|
MqttAuthenticateReasonCode reasonCode,
|
||||||
|
byte[] authenticationData = null,
|
||||||
|
string reasonString = null,
|
||||||
|
List<MqttUserProperty> userProperties = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// The authentication method will never change so we must use the already known one [MQTT-4.12.0-5].
|
||||||
|
//var authPacket = MqttPacketFactories.Auth.Create(AuthenticationMethod, reasonCode);
|
||||||
|
|
||||||
|
var authPacket = new MqttAuthPacket
|
||||||
|
{
|
||||||
|
AuthenticationMethod = AuthenticationMethod,
|
||||||
|
AuthenticationData = authenticationData,
|
||||||
|
UserProperties = userProperties,
|
||||||
|
ReasonCode = reasonCode,
|
||||||
|
ReasonString = reasonString
|
||||||
|
};
|
||||||
|
|
||||||
|
return _transportStrategy.SendPacketAsync(authPacket, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class MqttReAuthenticationOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the authentication data.
|
||||||
|
/// Authentication data is binary information used to transmit multiple iterations of cryptographic secrets of protocol
|
||||||
|
/// steps.
|
||||||
|
/// The content of the authentication data is highly dependent on the specific implementation of the authentication
|
||||||
|
/// method.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public byte[] AuthenticationData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user properties.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,110 +11,112 @@ namespace MQTTnet.Client
|
|||||||
public sealed class MqttClientConnectResult
|
public sealed class MqttClientConnectResult
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the result code.
|
/// Gets the client identifier which was chosen by the server.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
|
||||||
public MqttClientConnectResultCode ResultCode { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether a session was already available or not.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsSessionPresent { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
|
||||||
public bool WildcardSubscriptionAvailable { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether the server supports retained messages.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
|
||||||
public bool RetainAvailable { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the client identifier which was chosen by the server.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AssignedClientIdentifier { get; internal set; }
|
public string AssignedClientIdentifier { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the authentication method.
|
/// Gets the authentication data.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public byte[] AuthenticationData { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the authentication method.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AuthenticationMethod { get; internal set; }
|
public string AuthenticationMethod { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the authentication data.
|
/// Gets a value indicating whether a session was already available or not.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte[] AuthenticationData { get; internal set; }
|
public bool IsSessionPresent { get; internal set; }
|
||||||
|
|
||||||
public uint? MaximumPacketSize { get; internal set; }
|
public uint? MaximumPacketSize { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the reason string.
|
/// Gets the maximum QoS which is supported by the server.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
|
||||||
public string ReasonString { get; internal set; }
|
|
||||||
|
|
||||||
public ushort? ReceiveMaximum { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the maximum QoS which is supported by the server.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttQualityOfServiceLevel MaximumQoS { get; internal set; }
|
public MqttQualityOfServiceLevel MaximumQoS { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the response information.
|
/// Gets the reason string.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public string ReasonString { get; internal set; }
|
||||||
|
|
||||||
|
public ushort? ReceiveMaximum { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the response information.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ResponseInformation { get; internal set; }
|
public string ResponseInformation { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the maximum value for a topic alias. 0 means not supported.
|
/// Gets the result code.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort TopicAliasMaximum { get; internal set; }
|
public MqttClientConnectResultCode ResultCode { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an alternate server which should be used instead of the current one.
|
/// Gets whether the server supports retained messages.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ServerReference { get; internal set; }
|
public bool RetainAvailable { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MQTTv5 only.
|
/// MQTTv5 only.
|
||||||
/// Gets the keep alive interval which was chosen by the server instead of the
|
/// Gets the keep alive interval which was chosen by the server instead of the
|
||||||
/// keep alive interval from the client CONNECT packet.
|
/// keep alive interval from the client CONNECT packet.
|
||||||
/// A value of 0 indicates that the feature is not used.
|
/// A value of 0 indicates that the feature is not used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort ServerKeepAlive { get; internal set; }
|
public ushort ServerKeepAlive { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an alternate server which should be used instead of the current one.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public string ServerReference { get; internal set; }
|
||||||
|
|
||||||
public uint? SessionExpiryInterval { get; internal set; }
|
public uint? SessionExpiryInterval { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the subscription identifiers are available or not.
|
/// Gets a value indicating whether the shared subscriptions are available or not.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
|
||||||
public bool SubscriptionIdentifiersAvailable { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the shared subscriptions are available or not.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SharedSubscriptionAvailable { get; internal set; }
|
public bool SharedSubscriptionAvailable { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the user properties.
|
/// Gets a value indicating whether the subscription identifiers are available or not.
|
||||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// 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.
|
/// </summary>
|
||||||
/// The feature is very similar to the HTTP header concept.
|
public bool SubscriptionIdentifiersAvailable { get; internal set; }
|
||||||
/// MQTTv5 only.
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the maximum value for a topic alias. 0 means not supported.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public ushort TopicAliasMaximum { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets 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>
|
/// </summary>
|
||||||
public List<MqttUserProperty> UserProperties { get; internal set; }
|
public List<MqttUserProperty> UserProperties { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public bool WildcardSubscriptionAvailable { get; internal set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-13
@@ -1,13 +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;
|
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
|
||||||
{
|
|
||||||
public interface IMqttExtendedAuthenticationExchangeHandler
|
|
||||||
{
|
|
||||||
Task HandleRequestAsync(MqttExtendedAuthenticationExchangeContext context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-62
@@ -1,62 +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.Collections.Generic;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
using MQTTnet.Protocol;
|
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
|
||||||
{
|
|
||||||
public class MqttExtendedAuthenticationExchangeContext
|
|
||||||
{
|
|
||||||
public MqttExtendedAuthenticationExchangeContext(MqttAuthPacket authPacket, MqttClient client)
|
|
||||||
{
|
|
||||||
if (authPacket == null) throw new ArgumentNullException(nameof(authPacket));
|
|
||||||
|
|
||||||
ReasonCode = authPacket.ReasonCode;
|
|
||||||
ReasonString = authPacket.ReasonString;
|
|
||||||
AuthenticationMethod = authPacket.AuthenticationMethod;
|
|
||||||
AuthenticationData = authPacket.AuthenticationData;
|
|
||||||
UserProperties = authPacket.UserProperties;
|
|
||||||
|
|
||||||
Client = client ?? throw new ArgumentNullException(nameof(client));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the reason code.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public MqttAuthenticateReasonCode ReasonCode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the reason string.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public string ReasonString { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the authentication method.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public string AuthenticationMethod { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the authentication data.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public byte[] AuthenticationData { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets 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.
|
|
||||||
/// </summary>
|
|
||||||
public List<MqttUserProperty> UserProperties { get; }
|
|
||||||
|
|
||||||
public MqttClient Client { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-42
@@ -1,42 +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.Collections.Generic;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
using MQTTnet.Protocol;
|
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
|
||||||
{
|
|
||||||
public class MqttExtendedAuthenticationExchangeData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the reason code.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public MqttAuthenticateReasonCode ReasonCode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the reason string.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public string ReasonString { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the authentication data.
|
|
||||||
/// Authentication data is binary information used to transmit multiple iterations of cryptographic secrets of protocol steps.
|
|
||||||
/// The content of the authentication data is highly dependent on the specific implementation of the authentication method.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public byte[] AuthenticationData { 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.
|
|
||||||
/// </summary>
|
|
||||||
public List<MqttUserProperty> UserProperties { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,14 +8,16 @@ namespace MQTTnet.Client
|
|||||||
public interface IMqttClient : IDisposable
|
public interface IMqttClient : IDisposable
|
||||||
{
|
{
|
||||||
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
|
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
|
||||||
|
|
||||||
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
|
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
|
||||||
|
|
||||||
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
|
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
|
||||||
|
|
||||||
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
||||||
|
|
||||||
event Func<InspectMqttPacketEventArgs, Task> InspectPackage;
|
event Func<MqttExtendedAuthenticationExchangeEventArgs, Task> ExtendedAuthenticationExchangeAsync;
|
||||||
|
|
||||||
|
event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
|
||||||
|
|
||||||
bool IsConnected { get; }
|
bool IsConnected { get; }
|
||||||
|
|
||||||
@@ -29,7 +31,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
|
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
|
Task ReAuthenticateAsync(MqttReAuthenticationOptions options, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
|
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
// 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 MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client.Internal
|
||||||
|
{
|
||||||
|
public sealed class MqttInitialAuthenticationStrategy : IMqttAuthenticationTransportStrategy
|
||||||
|
{
|
||||||
|
readonly IMqttChannelAdapter _channelAdapter;
|
||||||
|
|
||||||
|
public MqttInitialAuthenticationStrategy(IMqttChannelAdapter channelAdapter)
|
||||||
|
{
|
||||||
|
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task SendPacketAsync(MqttAuthPacket authPacket, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (authPacket == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(authPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _channelAdapter.SendPacketAsync(authPacket, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MqttAuthPacket> ReceivePacketAsync(string authenticationMethod, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (authenticationMethod == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(authenticationMethod));
|
||||||
|
}
|
||||||
|
|
||||||
|
var receivePacket = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
if (receivePacket is MqttAuthPacket authPacket)
|
||||||
|
{
|
||||||
|
if (!string.Equals(authPacket.AuthenticationMethod, authenticationMethod, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("The authentication method is not allowed to change while authenticating.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return authPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivePacket == null)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("The server closed the connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MqttProtocolViolationException("Expected an AUTH packet from the client.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IMqttAuthenticationTransportStrategy
|
||||||
|
{
|
||||||
|
Task SendPacketAsync(MqttAuthPacket authPacket, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<MqttAuthPacket> ReceivePacketAsync(string authenticationMethod, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class MqttClientAuthenticationHandler
|
||||||
|
{
|
||||||
|
readonly MqttClient _client;
|
||||||
|
|
||||||
|
readonly AsyncEvent<MqttExtendedAuthenticationExchangeEventArgs> _extendedAuthenticationExchangeEvent = new AsyncEvent<MqttExtendedAuthenticationExchangeEventArgs>();
|
||||||
|
readonly MqttNetSourceLogger _logger;
|
||||||
|
|
||||||
|
public MqttClientAuthenticationHandler(MqttClient client, IMqttNetLogger logger)
|
||||||
|
{
|
||||||
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||||
|
_logger = logger.WithSource(nameof(MqttClientAuthenticationHandler));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddHandler(Func<MqttExtendedAuthenticationExchangeEventArgs, Task> handler)
|
||||||
|
{
|
||||||
|
_extendedAuthenticationExchangeEvent.AddHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MqttClientConnectResult> Authenticate(IMqttChannelAdapter channelAdapter, MqttClientOptions options, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (channelAdapter == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(channelAdapter));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
// From the RFC:
|
||||||
|
// The CONNACK packet is the packet sent by the Server in response to a CONNECT packet received from a Client.
|
||||||
|
// The Server MUST send a CONNACK with a 0x00 (Success) Reason Code before sending any Packet other than AUTH [MQTT-3.2.0-1].
|
||||||
|
// The Server MUST NOT send more than one CONNACK in a Network Connection [MQTT-3.2.0-2].
|
||||||
|
|
||||||
|
MqttClientConnectResult result;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Send the CONNECT packet..
|
||||||
|
var connectPacket = MqttPacketFactories.Connect.Create(options);
|
||||||
|
await _client.Send(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Wait for the CONNACK packet...
|
||||||
|
MqttConnAckPacket connAckPacket = null;
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var receivedPacket = await channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (receivedPacket is MqttAuthPacket initialAuthPacket)
|
||||||
|
{
|
||||||
|
// MQTT v3.1.1 cannot send an AUTH packet.
|
||||||
|
// If the Server requires additional information to complete the authentication, it can send an AUTH packet to the Client.
|
||||||
|
// This packet MUST contain a Reason Code of 0x18 (Continue authentication)
|
||||||
|
if (initialAuthPacket.ReasonCode != MqttAuthenticateReasonCode.ContinueAuthentication)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("Wrong reason code received [MQTT-4.12.0-2].");
|
||||||
|
}
|
||||||
|
|
||||||
|
//var response = await HandleExtendedAuthentication(authPacket, channelAdapter).ConfigureAwait(false);
|
||||||
|
await HandleExtendedAuthentication(initialAuthPacket, new MqttInitialAuthenticationStrategy(channelAdapter)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else if (receivedPacket is MqttConnAckPacket connAckPacketBuffer)
|
||||||
|
{
|
||||||
|
connAckPacket = connAckPacketBuffer;
|
||||||
|
|
||||||
|
// The CONNACK packet is the last packet when authenticating so there is no further AUTH packet allowed!
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException($"Received {receivedPacket.GetRfcName()} while authenticating.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
// If the initial CONNECT packet included an Authentication Method property then all AUTH packets,
|
||||||
|
// and any successful CONNACK packet MUST include an Authentication Method Property with the same
|
||||||
|
// value as in the CONNECT packet [MQTT-4.12.0-5].
|
||||||
|
if (!string.IsNullOrEmpty(connectPacket.AuthenticationMethod))
|
||||||
|
{
|
||||||
|
if (!string.Equals(connectPacket.AuthenticationMethod, connAckPacket?.AuthenticationMethod))
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("The CONNACK packet does not have the same authentication method as the CONNECT packet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
if (options.ThrowOnNonSuccessfulResponseFromServer)
|
||||||
|
{
|
||||||
|
_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.");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task HandleExtendedAuthentication(MqttAuthPacket initialAuthPacket, IMqttAuthenticationTransportStrategy transportStrategy)
|
||||||
|
{
|
||||||
|
ValidateEventHandler();
|
||||||
|
|
||||||
|
var eventArgs = new MqttExtendedAuthenticationExchangeEventArgs(initialAuthPacket, transportStrategy);
|
||||||
|
return _extendedAuthenticationExchangeEvent.InvokeAsync(eventArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ValidateEventHandler()
|
||||||
|
{
|
||||||
|
if (!_extendedAuthenticationExchangeEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot handle extended authentication without attached event handler.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveHandler(Func<MqttExtendedAuthenticationExchangeEventArgs, Task> handler)
|
||||||
|
{
|
||||||
|
_extendedAuthenticationExchangeEvent.RemoveHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReAuthenticate(MqttReAuthenticationOptions options, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ValidateEventHandler();
|
||||||
|
|
||||||
|
var authPacket = MqttPacketFactories.Auth.CreateReAuthenticationPacket(_client.Options, options);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Sending the AUTH packet will trigger the re authentication and the event handler will
|
||||||
|
// handle the details.
|
||||||
|
await _client.Send(authPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,126 @@
|
|||||||
|
// 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 MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client.Internal
|
||||||
|
{
|
||||||
|
public sealed class MqttClientKeepAliveHandler
|
||||||
|
{
|
||||||
|
readonly MqttClient _client;
|
||||||
|
readonly MqttNetSourceLogger _logger;
|
||||||
|
|
||||||
|
CancellationTokenSource _cancellationToken;
|
||||||
|
TimeSpan _keepAliveInterval;
|
||||||
|
Task _keepAlivePacketsSenderTask;
|
||||||
|
DateTime _lastPacketSentTimestamp;
|
||||||
|
|
||||||
|
public MqttClientKeepAliveHandler(MqttClient client, IMqttNetLogger logger)
|
||||||
|
{
|
||||||
|
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||||
|
_logger = logger.WithSource(nameof(MqttClientKeepAliveHandler));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disable()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_cancellationToken?.Cancel(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_cancellationToken?.Dispose();
|
||||||
|
_cancellationToken = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Enable(MqttClientConnectResult connectResult)
|
||||||
|
{
|
||||||
|
if (connectResult == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(connectResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
_keepAliveInterval = _client.Options.KeepAlivePeriod;
|
||||||
|
|
||||||
|
if (connectResult.ServerKeepAlive > 0)
|
||||||
|
{
|
||||||
|
_logger.Info("Using keep alive value {0} sent from the server", connectResult.ServerKeepAlive);
|
||||||
|
_keepAliveInterval = TimeSpan.FromSeconds(connectResult.ServerKeepAlive);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_keepAliveInterval == TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cancellationToken = new CancellationTokenSource();
|
||||||
|
_keepAlivePacketsSenderTask = Task.Run(() => SendKeepAliveMessagesLoop(_cancellationToken.Token), _cancellationToken.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TrackSentPacket()
|
||||||
|
{
|
||||||
|
// Only use UTC to avoid performance hit by loading current time zone all the time.
|
||||||
|
_lastPacketSentTimestamp = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task SendKeepAliveMessagesLoop(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.Verbose("Start sending keep alive packets.");
|
||||||
|
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// Values described here: [MQTT-3.1.2-24].
|
||||||
|
var timeWithoutPacketSent = DateTime.UtcNow - _lastPacketSentTimestamp;
|
||||||
|
|
||||||
|
if (timeWithoutPacketSent > _keepAliveInterval)
|
||||||
|
{
|
||||||
|
using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
|
||||||
|
{
|
||||||
|
timeout.CancelAfter(_client.Options.Timeout);
|
||||||
|
await _client.PingAsync(timeout.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a fixed time in all cases. Calculation of the remaining time is complicated
|
||||||
|
// due to some edge cases and was buggy in the past. Now we wait several ms because the
|
||||||
|
// min keep alive value is one second so that the server will wait 1.5 seconds for a PING packet.
|
||||||
|
await Task.Delay(250, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
if (_client._cleanDisconnectInitiated)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception is OperationCanceledException)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (exception is MqttCommunicationException)
|
||||||
|
{
|
||||||
|
_logger.Warning(exception, "Communication error while sending/receiving keep alive packets");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Error(exception, "Error exception while sending/receiving keep alive packets");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _client.DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_logger.Verbose("Stopped sending keep alive packets");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+262
-306
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Client.Internal;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
@@ -20,25 +21,19 @@ namespace MQTTnet.Client
|
|||||||
public sealed class MqttClient : Disposable, IMqttClient
|
public sealed class MqttClient : Disposable, IMqttClient
|
||||||
{
|
{
|
||||||
readonly IMqttClientAdapterFactory _adapterFactory;
|
readonly IMqttClientAdapterFactory _adapterFactory;
|
||||||
readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
|
readonly MqttClientAuthenticationHandler _authenticationHandler;
|
||||||
|
|
||||||
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 object _disconnectLock = new object();
|
||||||
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
|
readonly MqttClientEvents _events = new MqttClientEvents();
|
||||||
|
readonly MqttClientKeepAliveHandler _keepAliveHandler;
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
|
|
||||||
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
readonly IMqttNetLogger _rootLogger;
|
readonly IMqttNetLogger _rootLogger;
|
||||||
|
|
||||||
IMqttChannelAdapter _adapter;
|
IMqttChannelAdapter _channelAdapter;
|
||||||
|
|
||||||
bool _cleanDisconnectInitiated;
|
internal bool _cleanDisconnectInitiated;
|
||||||
|
|
||||||
|
CancellationTokenSource _clientAlive;
|
||||||
volatile int _connectionStatus;
|
volatile int _connectionStatus;
|
||||||
|
|
||||||
// The value for this field can be set from two different enums.
|
// The value for this field can be set from two different enums.
|
||||||
@@ -46,11 +41,6 @@ namespace MQTTnet.Client
|
|||||||
int _disconnectReason;
|
int _disconnectReason;
|
||||||
string _disconnectReasonString;
|
string _disconnectReasonString;
|
||||||
List<MqttUserProperty> _disconnectUserProperties;
|
List<MqttUserProperty> _disconnectUserProperties;
|
||||||
|
|
||||||
Task _keepAlivePacketsSenderTask;
|
|
||||||
DateTime _lastPacketSentTimestamp;
|
|
||||||
|
|
||||||
CancellationTokenSource _mqttClientAlive;
|
|
||||||
MqttPacketDispatcher _packetDispatcher;
|
MqttPacketDispatcher _packetDispatcher;
|
||||||
Task _packetReceiverTask;
|
Task _packetReceiverTask;
|
||||||
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
|
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
|
||||||
@@ -61,36 +51,45 @@ namespace MQTTnet.Client
|
|||||||
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
|
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
|
||||||
_rootLogger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_rootLogger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_logger = logger.WithSource(nameof(MqttClient));
|
_logger = logger.WithSource(nameof(MqttClient));
|
||||||
|
|
||||||
|
_authenticationHandler = new MqttClientAuthenticationHandler(this, logger);
|
||||||
|
_keepAliveHandler = new MqttClientKeepAliveHandler(this, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
||||||
{
|
{
|
||||||
add => _applicationMessageReceivedEvent.AddHandler(value);
|
add => _events.ApplicationMessageReceivedEvent.AddHandler(value);
|
||||||
remove => _applicationMessageReceivedEvent.RemoveHandler(value);
|
remove => _events.ApplicationMessageReceivedEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
|
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
|
||||||
{
|
{
|
||||||
add => _connectedEvent.AddHandler(value);
|
add => _events.ConnectedEvent.AddHandler(value);
|
||||||
remove => _connectedEvent.RemoveHandler(value);
|
remove => _events.ConnectedEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync
|
public event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync
|
||||||
{
|
{
|
||||||
add => _connectingEvent.AddHandler(value);
|
add => _events.ConnectingEvent.AddHandler(value);
|
||||||
remove => _connectingEvent.RemoveHandler(value);
|
remove => _events.ConnectingEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
|
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
|
||||||
{
|
{
|
||||||
add => _disconnectedEvent.AddHandler(value);
|
add => _events.DisconnectedEvent.AddHandler(value);
|
||||||
remove => _disconnectedEvent.RemoveHandler(value);
|
remove => _events.DisconnectedEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Func<InspectMqttPacketEventArgs, Task> InspectPackage
|
public event Func<MqttExtendedAuthenticationExchangeEventArgs, Task> ExtendedAuthenticationExchangeAsync
|
||||||
{
|
{
|
||||||
add => _inspectPacketEvent.AddHandler(value);
|
add => _authenticationHandler.AddHandler(value);
|
||||||
remove => _inspectPacketEvent.RemoveHandler(value);
|
remove => _authenticationHandler.RemoveHandler(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync
|
||||||
|
{
|
||||||
|
add => _events.InspectPacketEvent.AddHandler(value);
|
||||||
|
remove => _events.InspectPacketEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
|
public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
|
||||||
@@ -114,21 +113,19 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
Options = options;
|
Options = options;
|
||||||
|
|
||||||
if (_connectingEvent.HasHandlers)
|
if (_events.ConnectingEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
|
await _events.ConnectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
Cleanup();
|
Cleanup();
|
||||||
|
|
||||||
_packetIdentifierProvider.Reset();
|
_packetIdentifierProvider.Reset();
|
||||||
_packetDispatcher = new MqttPacketDispatcher();
|
_packetDispatcher = new MqttPacketDispatcher();
|
||||||
|
_clientAlive = new CancellationTokenSource();
|
||||||
|
|
||||||
_mqttClientAlive = new CancellationTokenSource();
|
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_events.InspectPacketEvent, _rootLogger), _rootLogger);
|
||||||
var mqttClientAliveToken = _mqttClientAlive.Token;
|
_channelAdapter = adapter;
|
||||||
|
|
||||||
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_inspectPacketEvent, _rootLogger), _rootLogger);
|
|
||||||
_adapter = adapter;
|
|
||||||
|
|
||||||
if (cancellationToken.CanBeCanceled)
|
if (cancellationToken.CanBeCanceled)
|
||||||
{
|
{
|
||||||
@@ -144,39 +141,26 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastPacketSentTimestamp = DateTime.UtcNow;
|
|
||||||
|
|
||||||
var keepAliveInterval = Options.KeepAlivePeriod;
|
|
||||||
if (connectResult.ServerKeepAlive > 0)
|
|
||||||
{
|
|
||||||
_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(mqttClientAliveToken), mqttClientAliveToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
|
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
|
||||||
|
|
||||||
_logger.Info("Connected.");
|
_logger.Info("Connected");
|
||||||
|
|
||||||
if (_connectedEvent.HasHandlers)
|
await OnConnected(connectResult).ConfigureAwait(false);
|
||||||
{
|
|
||||||
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
|
|
||||||
await _connectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return connectResult;
|
return connectResult;
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
|
if (exception is MqttConnectingFailedException connectingFailedException)
|
||||||
|
{
|
||||||
|
connectResult = connectingFailedException.Result;
|
||||||
|
}
|
||||||
|
|
||||||
_disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError;
|
_disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError;
|
||||||
|
|
||||||
_logger.Error(exception, "Error while connecting with server.");
|
_logger.Error(exception, "Error while connecting with server");
|
||||||
|
|
||||||
await DisconnectInternalAsync(null, exception, connectResult).ConfigureAwait(false);
|
await DisconnectInternal(null, exception, connectResult).ConfigureAwait(false);
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -210,7 +194,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (Options.ValidateFeatures)
|
if (Options.ValidateFeatures)
|
||||||
{
|
{
|
||||||
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sending the DISCONNECT may fail due to connection issues. The resulting exception
|
// Sending the DISCONNECT may fail due to connection issues. The resulting exception
|
||||||
@@ -219,19 +203,19 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (cancellationToken.CanBeCanceled)
|
if (cancellationToken.CanBeCanceled)
|
||||||
{
|
{
|
||||||
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
await Send(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||||
{
|
{
|
||||||
await SendAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
await Send(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
await DisconnectCoreAsync(null, null, null, clientWasConnected).ConfigureAwait(false);
|
await DisconnectCore(null, null, null, clientWasConnected).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,13 +223,13 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
if (cancellationToken.CanBeCanceled)
|
if (cancellationToken.CanBeCanceled)
|
||||||
{
|
{
|
||||||
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
|
await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,7 +245,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (Options.ValidateFeatures)
|
if (Options.ValidateFeatures)
|
||||||
{
|
{
|
||||||
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
|
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
|
||||||
@@ -274,11 +258,11 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||||
{
|
{
|
||||||
return PublishAtLeastOnceAsync(publishPacket, cancellationToken);
|
return PublishAtLeastOnce(publishPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||||
{
|
{
|
||||||
return PublishExactlyOnceAsync(publishPacket, cancellationToken);
|
return PublishExactlyOnce(publishPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
@@ -287,26 +271,25 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default)
|
public Task ReAuthenticateAsync(MqttReAuthenticationOptions options, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (data == null)
|
if (options == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(data));
|
throw new ArgumentNullException(nameof(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
ThrowIfNotConnected();
|
ThrowIfNotConnected();
|
||||||
|
|
||||||
var authPacket = new MqttAuthPacket
|
if (cancellationToken.CanBeCanceled)
|
||||||
{
|
{
|
||||||
// This must always be equal to the value from the CONNECT packet. So we use it here to ensure that.
|
return _authenticationHandler.ReAuthenticate(options, cancellationToken);
|
||||||
AuthenticationMethod = Options.AuthenticationMethod,
|
}
|
||||||
AuthenticationData = data.AuthenticationData,
|
|
||||||
ReasonString = data.ReasonString,
|
|
||||||
UserProperties = data.UserProperties
|
|
||||||
};
|
|
||||||
|
|
||||||
return SendAsync(authPacket, cancellationToken);
|
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||||
|
{
|
||||||
|
return _authenticationHandler.ReAuthenticate(options, timeout.Token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default)
|
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default)
|
||||||
@@ -323,7 +306,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (Options.ValidateFeatures)
|
if (Options.ValidateFeatures)
|
||||||
{
|
{
|
||||||
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
@@ -335,17 +318,17 @@ namespace MQTTnet.Client
|
|||||||
MqttSubAckPacket subAckPacket;
|
MqttSubAckPacket subAckPacket;
|
||||||
if (cancellationToken.CanBeCanceled)
|
if (cancellationToken.CanBeCanceled)
|
||||||
{
|
{
|
||||||
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
subAckPacket = await Request<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||||
{
|
{
|
||||||
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, timeout.Token).ConfigureAwait(false);
|
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)
|
public async Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default)
|
||||||
@@ -365,7 +348,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (Options.ValidateFeatures)
|
if (Options.ValidateFeatures)
|
||||||
{
|
{
|
||||||
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
|
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
|
||||||
@@ -374,17 +357,38 @@ namespace MQTTnet.Client
|
|||||||
MqttUnsubAckPacket unsubAckPacket;
|
MqttUnsubAckPacket unsubAckPacket;
|
||||||
if (cancellationToken.CanBeCanceled)
|
if (cancellationToken.CanBeCanceled)
|
||||||
{
|
{
|
||||||
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
unsubAckPacket = await Request<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||||
{
|
{
|
||||||
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, timeout.Token).ConfigureAwait(false);
|
unsubAckPacket = await Request<MqttUnsubAckPacket>(unsubscribePacket, timeout.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket);
|
return MqttClientResultFactory.UnsubscribeResult.Create(unsubscribePacket, unsubAckPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult)
|
||||||
|
{
|
||||||
|
var clientWasConnected = IsConnected;
|
||||||
|
|
||||||
|
if (!DisconnectIsPendingOrFinished())
|
||||||
|
{
|
||||||
|
return DisconnectCore(sender, exception, connectResult, clientWasConnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Task Send(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
_keepAliveHandler.TrackSentPacket();
|
||||||
|
|
||||||
|
return _channelAdapter.SendPacketAsync(packet, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
@@ -408,7 +412,7 @@ namespace MQTTnet.Client
|
|||||||
if (!eventArgs.ProcessingFailed)
|
if (!eventArgs.ProcessingFailed)
|
||||||
{
|
{
|
||||||
var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs);
|
var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs);
|
||||||
return SendAsync(pubAckPacket, cancellationToken);
|
return Send(pubAckPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
|
else if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
|
||||||
@@ -416,7 +420,7 @@ namespace MQTTnet.Client
|
|||||||
if (!eventArgs.ProcessingFailed)
|
if (!eventArgs.ProcessingFailed)
|
||||||
{
|
{
|
||||||
var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs);
|
var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs);
|
||||||
return SendAsync(pubRecPacket, cancellationToken);
|
return Send(pubRecPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -427,50 +431,22 @@ namespace MQTTnet.Client
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<MqttClientConnectResult> AuthenticateAsync(MqttClientOptions options, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
MqttClientConnectResult result;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var connectPacket = MqttPacketFactories.Connect.Create(options);
|
|
||||||
|
|
||||||
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var clientConnectResultFactory = new MqttClientConnectResultFactory();
|
|
||||||
result = clientConnectResultFactory.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
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.");
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Cleanup()
|
void Cleanup()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_mqttClientAlive?.Cancel(false);
|
_clientAlive?.Cancel(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_mqttClientAlive?.Dispose();
|
_clientAlive?.Dispose();
|
||||||
_mqttClientAlive = null;
|
_clientAlive = null;
|
||||||
|
|
||||||
_publishPacketReceiverQueue?.Dispose();
|
_publishPacketReceiverQueue?.Dispose();
|
||||||
_publishPacketReceiverQueue = null;
|
_publishPacketReceiverQueue = null;
|
||||||
|
|
||||||
_adapter?.Dispose();
|
_channelAdapter?.Dispose();
|
||||||
_adapter = null;
|
_channelAdapter = null;
|
||||||
|
|
||||||
_packetDispatcher?.Dispose();
|
_packetDispatcher?.Dispose();
|
||||||
_packetDispatcher = null;
|
_packetDispatcher = null;
|
||||||
@@ -484,37 +460,46 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
async Task<MqttClientConnectResult> ConnectInternal(CancellationToken cancellationToken)
|
async Task<MqttClientConnectResult> ConnectInternal(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var backgroundCancellationToken = _mqttClientAlive.Token;
|
var clientAliveToken = _clientAlive.Token;
|
||||||
|
|
||||||
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
|
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(clientAliveToken, cancellationToken))
|
||||||
{
|
{
|
||||||
_logger.Verbose("Trying to connect with server '{0}'.", Options.ChannelOptions);
|
_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();
|
await _channelAdapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||||
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
|
|
||||||
|
|
||||||
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
|
_logger.Verbose("Connection with server established");
|
||||||
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
|
|
||||||
|
|
||||||
return await AuthenticateAsync(Options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
var connectResult = await _authenticationHandler.Authenticate(_channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (connectResult.ResultCode == MqttClientConnectResultCode.Success)
|
||||||
|
{
|
||||||
|
_publishPacketReceiverQueue?.Dispose();
|
||||||
|
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
|
||||||
|
|
||||||
|
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPacketsLoop(clientAliveToken), clientAliveToken);
|
||||||
|
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(clientAliveToken), clientAliveToken);
|
||||||
|
|
||||||
|
_keepAliveHandler.Enable(connectResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
|
async Task DisconnectCore(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
|
||||||
{
|
{
|
||||||
TryInitiateDisconnect();
|
TryInitiateDisconnect();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_adapter != null)
|
if (_channelAdapter != null)
|
||||||
{
|
{
|
||||||
_logger.Verbose("Disconnecting [Timeout={0}]", Options.Timeout);
|
_logger.Verbose("Disconnecting [Timeout={0}]", Options.Timeout);
|
||||||
|
|
||||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||||
{
|
{
|
||||||
await _adapter.DisconnectAsync(timeout.Token).ConfigureAwait(false);
|
await _channelAdapter.DisconnectAsync(timeout.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,9 +516,10 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
|
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
|
||||||
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
|
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
|
||||||
var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger);
|
|
||||||
|
|
||||||
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
|
_keepAliveHandler.Disable();
|
||||||
|
|
||||||
|
await Task.WhenAll(receiverTask, publishPacketReceiverTask).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception innerException)
|
catch (Exception innerException)
|
||||||
{
|
{
|
||||||
@@ -557,17 +543,17 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
// This handler must be executed in a new thread because otherwise a dead lock may happen
|
// This handler must be executed in a new thread because otherwise a dead lock may happen
|
||||||
// when trying to reconnect in that handler etc.
|
// when trying to reconnect in that handler etc.
|
||||||
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;
|
var clientWasConnected = IsConnected;
|
||||||
|
|
||||||
if (!DisconnectIsPendingOrFinished())
|
if (!DisconnectIsPendingOrFinished())
|
||||||
{
|
{
|
||||||
return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected);
|
return DisconnectCore(sender, exception, connectResult, clientWasConnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
@@ -612,21 +598,21 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
|
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessage(MqttPublishPacket publishPacket)
|
||||||
{
|
{
|
||||||
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
|
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
|
||||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
|
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
|
||||||
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _events.ApplicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
|
|
||||||
return eventArgs;
|
return eventArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket)
|
Task OnConnected(MqttClientConnectResult connectResult)
|
||||||
{
|
{
|
||||||
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
|
if (_events.ConnectedEvent.HasHandlers)
|
||||||
if (extendedAuthenticationExchangeHandler != null)
|
|
||||||
{
|
{
|
||||||
return extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this));
|
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
|
||||||
|
return _events.ConnectedEvent.InvokeAsync(eventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
@@ -641,10 +627,10 @@ namespace MQTTnet.Client
|
|||||||
// Also dispatch disconnect to waiting threads to generate a proper exception.
|
// Also dispatch disconnect to waiting threads to generate a proper exception.
|
||||||
_packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket));
|
_packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket));
|
||||||
|
|
||||||
return DisconnectInternalAsync(_packetReceiverTask, null, null);
|
return DisconnectInternal(_packetReceiverTask, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken)
|
async Task ProcessReceivedPublishPacketsLoop(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -657,7 +643,7 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
var publishPacket = publishPacketDequeueResult.Item;
|
var publishPacket = publishPacketDequeueResult.Item;
|
||||||
var eventArgs = await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false);
|
var eventArgs = await HandleReceivedApplicationMessage(publishPacket).ConfigureAwait(false);
|
||||||
|
|
||||||
if (eventArgs.AutoAcknowledge)
|
if (eventArgs.AutoAcknowledge)
|
||||||
{
|
{
|
||||||
@@ -681,7 +667,7 @@ namespace MQTTnet.Client
|
|||||||
// The packet is unknown. Probably due to a restart of the 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.
|
// So wen send this to the server to trigger a full resend of the message.
|
||||||
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
|
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
|
||||||
return SendAsync(pubRelPacket, cancellationToken);
|
return Send(pubRelPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
@@ -690,53 +676,150 @@ namespace MQTTnet.Client
|
|||||||
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
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();
|
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||||
|
|
||||||
var pubAckPacket = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
var pubAckPacket = await Request<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||||
return _clientPublishResultFactory.Create(pubAckPacket);
|
return MqttClientResultFactory.PublishResult.Create(pubAckPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<MqttClientPublishResult> PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
async Task<MqttClientPublishResult> PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
|
// 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();
|
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 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 = _channelAdapter.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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<TPacket> Receive<TPacket>(int packetIdentifier, CancellationToken cancellationToken) where TPacket : MqttPacket
|
||||||
|
{
|
||||||
|
using (var packetAwaitable = _packetDispatcher.AddAwaitable<TPacket>(packetIdentifier))
|
||||||
|
{
|
||||||
|
return await packetAwaitable.WaitOneAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<TResponsePacket> Request<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
ushort packetIdentifier = 0;
|
// Some packets are a direct response to another packet but have no packet identifier for correlation!
|
||||||
|
int packetIdentifier;
|
||||||
if (requestPacket is MqttPacketWithIdentifier packetWithIdentifier)
|
if (requestPacket is MqttPacketWithIdentifier packetWithIdentifier)
|
||||||
{
|
{
|
||||||
packetIdentifier = packetWithIdentifier.PacketIdentifier;
|
packetIdentifier = packetWithIdentifier.PacketIdentifier;
|
||||||
}
|
}
|
||||||
|
else if (requestPacket is MqttPingReqPacket)
|
||||||
|
{
|
||||||
|
packetIdentifier = MqttPacketDispatcher.PingRespPacketFakeIdentifier;
|
||||||
|
}
|
||||||
|
else if (requestPacket is MqttAuthPacket)
|
||||||
|
{
|
||||||
|
packetIdentifier = MqttPacketDispatcher.AuthPacketFakeIdentifier;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException($"Expecting a response for packet {requestPacket.GetRfcName()} is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
using (var packetAwaitable = _packetDispatcher.AddAwaitable<TResponsePacket>(packetIdentifier))
|
using (var packetAwaitable = _packetDispatcher.AddAwaitable<TResponsePacket>(packetIdentifier))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await SendAsync(requestPacket, cancellationToken).ConfigureAwait(false);
|
await Send(requestPacket, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
@@ -760,15 +843,6 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Task SendAsync(MqttPacket packet, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
_lastPacketSentTimestamp = DateTime.UtcNow;
|
|
||||||
|
|
||||||
return _adapter.SendPacketAsync(packet, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThrowIfConnected(string message)
|
void ThrowIfConnected(string message)
|
||||||
{
|
{
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
@@ -814,7 +888,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_mqttClientAlive?.Cancel(false);
|
_clientAlive?.Cancel(false);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
@@ -823,7 +897,7 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task TryProcessReceivedPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
async Task TryProcessReceivedPacket(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -843,23 +917,31 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
await ProcessReceivedDisconnectPacket(disconnectPacket).ConfigureAwait(false);
|
await ProcessReceivedDisconnectPacket(disconnectPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttAuthPacket authPacket)
|
// else if (packet is MqttAuthPacket authPacket)
|
||||||
{
|
// {
|
||||||
await ProcessReceivedAuthPacket(authPacket).ConfigureAwait(false);
|
// await _authenticationHandler.HandleExtendedAuthentication(authPacket, new MqttInitialAuthenticationStrategy(_channelAdapter)).ConfigureAwait(false);
|
||||||
}
|
// }
|
||||||
else if (packet is MqttPingRespPacket)
|
else if (packet is MqttPingRespPacket)
|
||||||
{
|
{
|
||||||
_packetDispatcher.TryDispatch(packet);
|
_packetDispatcher.TryDispatch(packet);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPingReqPacket)
|
else if (packet is MqttPingReqPacket)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("The PINGREQ Packet is sent from a Client to the Server only.");
|
throw new MqttProtocolViolationException("The PINGREQ packet can only be sent from client to server.");
|
||||||
|
}
|
||||||
|
else if (packet is MqttConnAckPacket)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("The CONNACK packet can only be send while connecting.");
|
||||||
|
}
|
||||||
|
else if (packet is MqttConnectPacket)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("The CONNECT packet can only be sent from client to server.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!_packetDispatcher.TryDispatch(packet))
|
if (!_packetDispatcher.TryDispatch(packet))
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
|
throw new MqttProtocolViolationException($"Received unexpected {packet} packet.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -884,133 +966,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
_packetDispatcher.FailAll(exception);
|
_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)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.Verbose("Start sending keep alive packets.");
|
|
||||||
|
|
||||||
var keepAlivePeriod = Options.KeepAlivePeriod;
|
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
// Values described here: [MQTT-3.1.2-24].
|
|
||||||
var timeWithoutPacketSent = DateTime.UtcNow - _lastPacketSentTimestamp;
|
|
||||||
|
|
||||||
if (timeWithoutPacketSent > keepAlivePeriod)
|
|
||||||
{
|
|
||||||
using (var timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
|
|
||||||
{
|
|
||||||
timeoutCancellationTokenSource.CancelAfter(Options.Timeout);
|
|
||||||
await PingAsync(timeoutCancellationTokenSource.Token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait a fixed time in all cases. Calculation of the remaining time is complicated
|
|
||||||
// due to some edge cases and was buggy in the past. Now we wait several ms because the
|
|
||||||
// min keep alive value is one second so that the server will wait 1.5 seconds for a PING
|
|
||||||
// packet.
|
|
||||||
await Task.Delay(250, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
if (_cleanDisconnectInitiated)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exception is OperationCanceledException)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (exception is MqttCommunicationException)
|
|
||||||
{
|
|
||||||
_logger.Warning(exception, "Communication error while sending/receiving keep alive packets.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.Error(exception, "Error exception while sending/receiving keep alive packets.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_logger.Verbose("Stopped sending keep alive packets.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public static class MqttClientExtensions
|
public static class MqttClientExtensions
|
||||||
{
|
{
|
||||||
|
static readonly MqttReAuthenticationOptions EmptyReAuthenticationOptions = new MqttReAuthenticationOptions();
|
||||||
|
|
||||||
public static Task DisconnectAsync(
|
public static Task DisconnectAsync(
|
||||||
this IMqttClient client,
|
this IMqttClient client,
|
||||||
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
|
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
|
||||||
@@ -77,6 +79,11 @@ namespace MQTTnet.Client
|
|||||||
return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken);
|
return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task ReAuthenticateAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return client.ReAuthenticateAsync(EmptyReAuthenticationOptions, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (client.Options == null)
|
if (client.Options == null)
|
||||||
@@ -88,16 +95,6 @@ namespace MQTTnet.Client
|
|||||||
return client.ConnectAsync(client.Options, cancellationToken);
|
return client.ConnectAsync(client.Options, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task SendExtendedAuthenticationExchangeDataAsync(this IMqttClient client, MqttExtendedAuthenticationExchangeData data)
|
|
||||||
{
|
|
||||||
if (client == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(client));
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.SendExtendedAuthenticationExchangeDataAsync(data, CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Task<MqttClientSubscribeResult> SubscribeAsync(this IMqttClient mqttClient, MqttTopicFilter topicFilter, CancellationToken cancellationToken = default)
|
public static Task<MqttClientSubscribeResult> SubscribeAsync(this IMqttClient mqttClient, MqttTopicFilter topicFilter, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (mqttClient == null)
|
if (mqttClient == null)
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public sealed class MqttClientOptions
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the authentication data.
|
/// Gets or sets the authentication data.
|
||||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
@@ -45,9 +53,7 @@ namespace MQTTnet.Client
|
|||||||
public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
|
public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
public IMqttClientCredentialsProvider Credentials { get; set; }
|
public IMqttClientCredentialsProvider Credentials { get; set; }
|
||||||
|
|
||||||
public IMqttExtendedAuthenticationExchangeHandler ExtendedAuthenticationExchangeHandler { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the keep alive period.
|
/// Gets or sets the keep alive period.
|
||||||
/// The connection is normally left open by the client so that is can send and receive data at any time.
|
/// The connection is normally left open by the client so that is can send and receive data at any time.
|
||||||
@@ -92,6 +98,11 @@ namespace MQTTnet.Client
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public uint SessionExpiryInterval { get; set; }
|
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 ThrowOnNonSuccessfulResponseFromServer { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the timeout which will be applied at socket level and internal operations.
|
/// 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.
|
/// The default value is the same as for sockets in .NET in general.
|
||||||
|
|||||||
@@ -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.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
@@ -75,23 +75,35 @@ namespace MQTTnet.Client
|
|||||||
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
|
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
|
||||||
|
|
||||||
MqttClientOptionsValidator.ThrowIfNotSupported(_options);
|
MqttClientOptionsValidator.ThrowIfNotSupported(_options);
|
||||||
|
|
||||||
return _options;
|
return _options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
|
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data = null)
|
||||||
{
|
{
|
||||||
_options.AuthenticationMethod = method;
|
_options.AuthenticationMethod = method;
|
||||||
_options.AuthenticationData = data;
|
_options.AuthenticationData = data;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean session is used in MQTT versions below 5.0.0. It is the same as setting "CleanStart".
|
||||||
|
/// </summary>
|
||||||
public MqttClientOptionsBuilder WithCleanSession(bool value = true)
|
public MqttClientOptionsBuilder WithCleanSession(bool value = true)
|
||||||
{
|
{
|
||||||
_options.CleanSession = value;
|
_options.CleanSession = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean start is used in MQTT versions 5.0.0 and higher. It is the same as setting "CleanSession".
|
||||||
|
/// </summary>
|
||||||
|
public MqttClientOptionsBuilder WithCleanStart(bool value = true)
|
||||||
|
{
|
||||||
|
_options.CleanSession = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithClientId(string value)
|
public MqttClientOptionsBuilder WithClientId(string value)
|
||||||
{
|
{
|
||||||
_options.ClientId = value;
|
_options.ClientId = value;
|
||||||
@@ -165,12 +177,6 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithExtendedAuthenticationExchangeHandler(IMqttExtendedAuthenticationExchangeHandler handler)
|
|
||||||
{
|
|
||||||
_options.ExtendedAuthenticationExchangeHandler = handler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value)
|
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value)
|
||||||
{
|
{
|
||||||
_options.KeepAlivePeriod = value;
|
_options.KeepAlivePeriod = value;
|
||||||
@@ -188,6 +194,18 @@ namespace MQTTnet.Client
|
|||||||
return WithKeepAlivePeriod(TimeSpan.Zero);
|
return WithKeepAlivePeriod(TimeSpan.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
|
||||||
|
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
|
||||||
|
/// will close the connection when receiving such packets. If such a service is used this flag must
|
||||||
|
/// be set to _true_.
|
||||||
|
/// </summary>
|
||||||
|
public MqttClientOptionsBuilder WithoutPacketFragmentation()
|
||||||
|
{
|
||||||
|
_options.AllowPacketFragmentation = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
|
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
|
||||||
{
|
{
|
||||||
if (value == MqttProtocolVersion.Unknown)
|
if (value == MqttProtocolVersion.Unknown)
|
||||||
@@ -266,7 +284,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
|
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
|
||||||
{
|
{
|
||||||
if (optionsBuilder == null)
|
if (optionsBuilder == null)
|
||||||
@@ -373,6 +391,18 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
|
||||||
|
{
|
||||||
|
_options.WillContentType = willContentType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWillCorrelationData(byte[] willCorrelationData)
|
||||||
|
{
|
||||||
|
_options.WillCorrelationData = willCorrelationData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithWillDelayInterval(uint willDelayInterval)
|
public MqttClientOptionsBuilder WithWillDelayInterval(uint willDelayInterval)
|
||||||
{
|
{
|
||||||
_options.WillDelayInterval = willDelayInterval;
|
_options.WillDelayInterval = willDelayInterval;
|
||||||
@@ -384,27 +414,45 @@ namespace MQTTnet.Client
|
|||||||
_options.WillPayload = willPayload;
|
_options.WillPayload = willPayload;
|
||||||
return this;
|
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)
|
public MqttClientOptionsBuilder WithWillPayload(string willPayload)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(willPayload))
|
if (string.IsNullOrEmpty(willPayload))
|
||||||
{
|
{
|
||||||
return WithWillPayload((byte[])null);
|
return WithWillPayload((byte[])null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_options.WillPayload = Encoding.UTF8.GetBytes(willPayload);
|
_options.WillPayload = Encoding.UTF8.GetBytes(willPayload);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator)
|
||||||
|
{
|
||||||
|
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel)
|
public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel)
|
||||||
{
|
{
|
||||||
_options.WillQualityOfServiceLevel = willQualityOfServiceLevel;
|
_options.WillQualityOfServiceLevel = willQualityOfServiceLevel;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithWillTopic(string willTopic)
|
public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic)
|
||||||
{
|
{
|
||||||
_options.WillTopic = willTopic;
|
_options.WillResponseTopic = willResponseTopic;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,38 +461,20 @@ namespace MQTTnet.Client
|
|||||||
_options.WillRetain = willRetain;
|
_options.WillRetain = willRetain;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
|
public MqttClientOptionsBuilder WithWillTopic(string willTopic)
|
||||||
{
|
{
|
||||||
_options.WillContentType = willContentType;
|
_options.WillTopic = willTopic;
|
||||||
return this;
|
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)
|
public MqttClientOptionsBuilder WithWillUserProperty(string name, string value)
|
||||||
{
|
{
|
||||||
if (_options.WillUserProperties == null)
|
if (_options.WillUserProperties == null)
|
||||||
{
|
{
|
||||||
_options.WillUserProperties = new List<MqttUserProperty>();
|
_options.WillUserProperties = new List<MqttUserProperty>();
|
||||||
}
|
}
|
||||||
|
|
||||||
_options.WillUserProperties.Add(new MqttUserProperty(name, value));
|
_options.WillUserProperties.Add(new MqttUserProperty(name, value));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace MQTTnet.Client
|
|||||||
/// Gets or sets the reason code.
|
/// Gets or sets the reason code.
|
||||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttClientPublishReasonCode ReasonCode { get; } = MqttClientPublishReasonCode.Success;
|
public MqttClientPublishReasonCode ReasonCode { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the reason string.
|
/// Gets or sets the reason string.
|
||||||
|
|||||||
@@ -42,6 +42,6 @@ namespace MQTTnet.Client
|
|||||||
/// Gets the user properties which were part of the UNSUBACK packet.
|
/// Gets the user properties which were part of the UNSUBACK packet.
|
||||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ namespace MQTTnet.Formatter
|
|||||||
return new MqttApplicationMessage
|
return new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = publishPacket.Topic,
|
Topic = publishPacket.Topic,
|
||||||
Payload = publishPacket.Payload,
|
PayloadSegment = publishPacket.PayloadSegment,
|
||||||
QualityOfServiceLevel = publishPacket.QualityOfServiceLevel,
|
QualityOfServiceLevel = publishPacket.QualityOfServiceLevel,
|
||||||
Retain = publishPacket.Retain,
|
Retain = publishPacket.Retain,
|
||||||
Dup = publishPacket.Dup,
|
Dup = publishPacket.Dup,
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Formatter
|
||||||
|
{
|
||||||
|
public sealed class MqttAuthPacketFactory
|
||||||
|
{
|
||||||
|
public MqttAuthPacket CreateReAuthenticationPacket(MqttClientOptions clientOptions, MqttReAuthenticationOptions reAuthenticationOptions)
|
||||||
|
{
|
||||||
|
if (clientOptions == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(clientOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reAuthenticationOptions == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(reAuthenticationOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MqttAuthPacket
|
||||||
|
{
|
||||||
|
// The authentication method cannot change and must be always the same as long as the client is connected.
|
||||||
|
AuthenticationMethod = clientOptions.AuthenticationMethod,
|
||||||
|
ReasonCode = MqttAuthenticateReasonCode.ReAuthenticate,
|
||||||
|
AuthenticationData = reAuthenticationOptions.AuthenticationData,
|
||||||
|
UserProperties = reAuthenticationOptions.UserProperties,
|
||||||
|
ReasonString = null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,8 @@ namespace MQTTnet.Formatter
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class MqttBufferWriter
|
public sealed class MqttBufferWriter
|
||||||
{
|
{
|
||||||
public const uint VariableByteIntegerMaxValue = 268435455;
|
const uint VariableByteIntegerMaxValue = 268435455;
|
||||||
|
const int EncodedStringMaxLength = 65535;
|
||||||
|
|
||||||
readonly int _maxBufferSize;
|
readonly int _maxBufferSize;
|
||||||
|
|
||||||
@@ -112,25 +113,7 @@ namespace MQTTnet.Formatter
|
|||||||
WriteBinary(propertyWriter._buffer, 0, propertyWriter.Length);
|
WriteBinary(propertyWriter._buffer, 0, propertyWriter.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteBinary(byte[] buffer, int offset, int count)
|
public void WriteBinary(byte[] value)
|
||||||
{
|
|
||||||
if (buffer == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(buffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureAdditionalCapacity(count);
|
|
||||||
|
|
||||||
MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count);
|
|
||||||
IncreasePosition(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteBinaryData(byte[] value)
|
|
||||||
{
|
{
|
||||||
if (value == null || value.Length == 0)
|
if (value == null || value.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -155,6 +138,24 @@ namespace MQTTnet.Formatter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WriteBinary(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureAdditionalCapacity(count);
|
||||||
|
|
||||||
|
MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count);
|
||||||
|
IncreasePosition(count);
|
||||||
|
}
|
||||||
|
|
||||||
public void WriteByte(byte @byte)
|
public void WriteByte(byte @byte)
|
||||||
{
|
{
|
||||||
EnsureAdditionalCapacity(1);
|
EnsureAdditionalCapacity(1);
|
||||||
@@ -186,6 +187,13 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
var writtenBytes = Encoding.UTF8.GetBytes(value, 0, value.Length, _buffer, _position + 2);
|
var writtenBytes = Encoding.UTF8.GetBytes(value, 0, value.Length, _buffer, _position + 2);
|
||||||
|
|
||||||
|
// From RFC: 1.5.4 UTF-8 Encoded String
|
||||||
|
// Unless stated otherwise all UTF-8 encoded strings can have any length in the range 0 to 65,535 bytes.
|
||||||
|
if (writtenBytes > EncodedStringMaxLength)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException($"The maximum string length is 65535. The current string has a length of {writtenBytes}.");
|
||||||
|
}
|
||||||
|
|
||||||
_buffer[_position] = (byte)(writtenBytes >> 8);
|
_buffer[_position] = (byte)(writtenBytes >> 8);
|
||||||
_buffer[_position + 1] = (byte)writtenBytes;
|
_buffer[_position + 1] = (byte)writtenBytes;
|
||||||
|
|
||||||
@@ -273,8 +281,7 @@ namespace MQTTnet.Formatter
|
|||||||
newBufferLength *= 2;
|
newBufferLength *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Array.Resize will create a new array and copy the existing
|
// Array.Resize will create a new array and copy the existing data to the new one.
|
||||||
// data to the new one.
|
|
||||||
Array.Resize(ref _buffer, newBufferLength);
|
Array.Resize(ref _buffer, newBufferLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +292,8 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
if (_position > Length)
|
if (_position > Length)
|
||||||
{
|
{
|
||||||
|
// Also extend the position because we reached the end of the
|
||||||
|
// pre allocated buffer.
|
||||||
Length = _position;
|
Length = _position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ namespace MQTTnet.Formatter
|
|||||||
return MqttConnectReturnCode.ConnectionAccepted;
|
return MqttConnectReturnCode.ConnectionAccepted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case MqttConnectReasonCode.BadAuthenticationMethod:
|
||||||
|
case MqttConnectReasonCode.Banned:
|
||||||
case MqttConnectReasonCode.NotAuthorized:
|
case MqttConnectReasonCode.NotAuthorized:
|
||||||
{
|
{
|
||||||
return MqttConnectReturnCode.ConnectionRefusedNotAuthorized;
|
return MqttConnectReturnCode.ConnectionRefusedNotAuthorized;
|
||||||
@@ -73,12 +75,13 @@ namespace MQTTnet.Formatter
|
|||||||
{
|
{
|
||||||
return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected;
|
return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MqttConnectReasonCode.UnsupportedProtocolVersion:
|
case MqttConnectReasonCode.UnsupportedProtocolVersion:
|
||||||
{
|
{
|
||||||
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
|
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case MqttConnectReasonCode.UseAnotherServer:
|
||||||
case MqttConnectReasonCode.ServerUnavailable:
|
case MqttConnectReasonCode.ServerUnavailable:
|
||||||
case MqttConnectReasonCode.ServerBusy:
|
case MqttConnectReasonCode.ServerBusy:
|
||||||
case MqttConnectReasonCode.ServerMoved:
|
case MqttConnectReasonCode.ServerMoved:
|
||||||
@@ -88,7 +91,8 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("Unable to convert connect reason code (MQTTv5) to return code (MQTTv3).");
|
// This is the most best matching value.
|
||||||
|
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ namespace MQTTnet.Formatter
|
|||||||
{
|
{
|
||||||
public static class MqttPacketFactories
|
public static class MqttPacketFactories
|
||||||
{
|
{
|
||||||
|
public static MqttAuthPacketFactory Auth { get; } = new MqttAuthPacketFactory();
|
||||||
|
|
||||||
public static MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
|
public static MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
|
||||||
|
|
||||||
public static MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
|
public static MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
|
||||||
|
|||||||
@@ -31,10 +31,14 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
public MqttProtocolVersion ProtocolVersion { get; private set; } = MqttProtocolVersion.Unknown;
|
public MqttProtocolVersion ProtocolVersion { get; private set; } = MqttProtocolVersion.Unknown;
|
||||||
|
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
_bufferWriter.Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket)
|
public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket)
|
||||||
{
|
{
|
||||||
ThrowIfFormatterNotSet();
|
ThrowIfFormatterNotSet();
|
||||||
|
|
||||||
return _formatter.Decode(receivedMqttPacket);
|
return _formatter.Decode(receivedMqttPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,11 +54,6 @@ namespace MQTTnet.Formatter
|
|||||||
return _formatter.Encode(packet);
|
return _formatter.Encode(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cleanup()
|
|
||||||
{
|
|
||||||
_bufferWriter.Cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IMqttPacketFormatter GetMqttPacketFormatter(MqttProtocolVersion protocolVersion, MqttBufferWriter bufferWriter)
|
public static IMqttPacketFormatter GetMqttPacketFormatter(MqttProtocolVersion protocolVersion, MqttBufferWriter bufferWriter)
|
||||||
{
|
{
|
||||||
if (protocolVersion == MqttProtocolVersion.Unknown)
|
if (protocolVersion == MqttProtocolVersion.Unknown)
|
||||||
@@ -98,7 +97,7 @@ namespace MQTTnet.Formatter
|
|||||||
// Remove the mosquitto try_private flag (MQTT 3.1.1 Bridge).
|
// Remove the mosquitto try_private flag (MQTT 3.1.1 Bridge).
|
||||||
// This flag is accepted but not yet used.
|
// This flag is accepted but not yet used.
|
||||||
protocolLevel &= 0x7F;
|
protocolLevel &= 0x7F;
|
||||||
|
|
||||||
if (protocolName == "MQTT")
|
if (protocolName == "MQTT")
|
||||||
{
|
{
|
||||||
if (protocolLevel == 5)
|
if (protocolLevel == 5)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace MQTTnet.Formatter
|
|||||||
return new MqttPublishPacket
|
return new MqttPublishPacket
|
||||||
{
|
{
|
||||||
Topic = publishPacket.Topic,
|
Topic = publishPacket.Topic,
|
||||||
Payload = publishPacket.Payload,
|
PayloadSegment = publishPacket.PayloadSegment,
|
||||||
Retain = publishPacket.Retain,
|
Retain = publishPacket.Retain,
|
||||||
QualityOfServiceLevel = publishPacket.QualityOfServiceLevel,
|
QualityOfServiceLevel = publishPacket.QualityOfServiceLevel,
|
||||||
Dup = publishPacket.Dup,
|
Dup = publishPacket.Dup,
|
||||||
@@ -36,7 +36,7 @@ namespace MQTTnet.Formatter
|
|||||||
var packet = new MqttPublishPacket
|
var packet = new MqttPublishPacket
|
||||||
{
|
{
|
||||||
Topic = applicationMessage.Topic,
|
Topic = applicationMessage.Topic,
|
||||||
Payload = applicationMessage.Payload,
|
PayloadSegment = applicationMessage.PayloadSegment,
|
||||||
QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel,
|
QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel,
|
||||||
Retain = applicationMessage.Retain,
|
Retain = applicationMessage.Retain,
|
||||||
Dup = applicationMessage.Dup,
|
Dup = applicationMessage.Dup,
|
||||||
@@ -65,10 +65,16 @@ namespace MQTTnet.Formatter
|
|||||||
throw new MqttProtocolViolationException("The CONNECT packet contains no will message (WillFlag).");
|
throw new MqttProtocolViolationException("The CONNECT packet contains no will message (WillFlag).");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArraySegment<byte> willMessageBuffer = default;
|
||||||
|
if (connectPacket.WillMessage?.Length > 0)
|
||||||
|
{
|
||||||
|
willMessageBuffer = new ArraySegment<byte>(connectPacket.WillMessage);
|
||||||
|
}
|
||||||
|
|
||||||
var packet = new MqttPublishPacket
|
var packet = new MqttPublishPacket
|
||||||
{
|
{
|
||||||
Topic = connectPacket.WillTopic,
|
Topic = connectPacket.WillTopic,
|
||||||
Payload = connectPacket.WillMessage,
|
PayloadSegment = willMessageBuffer,
|
||||||
QualityOfServiceLevel = connectPacket.WillQoS,
|
QualityOfServiceLevel = connectPacket.WillQoS,
|
||||||
Retain = connectPacket.WillRetain,
|
Retain = connectPacket.WillRetain,
|
||||||
ContentType = connectPacket.WillContentType,
|
ContentType = connectPacket.WillContentType,
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ namespace MQTTnet.Formatter.V3
|
|||||||
return DecodePubRelPacket(receivedMqttPacket.Body);
|
return DecodePubRelPacket(receivedMqttPacket.Body);
|
||||||
case MqttControlPacketType.PubComp:
|
case MqttControlPacketType.PubComp:
|
||||||
return DecodePubCompPacket(receivedMqttPacket.Body);
|
return DecodePubCompPacket(receivedMqttPacket.Body);
|
||||||
|
|
||||||
case MqttControlPacketType.PingReq:
|
case MqttControlPacketType.PingReq:
|
||||||
return MqttPingReqPacket.Instance;
|
return MqttPingReqPacket.Instance;
|
||||||
case MqttControlPacketType.PingResp:
|
case MqttControlPacketType.PingResp:
|
||||||
return MqttPingRespPacket.Instance;
|
return MqttPingRespPacket.Instance;
|
||||||
|
|
||||||
case MqttControlPacketType.Connect:
|
case MqttControlPacketType.Connect:
|
||||||
return DecodeConnectPacket(receivedMqttPacket.Body);
|
return DecodeConnectPacket(receivedMqttPacket.Body);
|
||||||
case MqttControlPacketType.ConnAck:
|
case MqttControlPacketType.ConnAck:
|
||||||
@@ -73,7 +73,7 @@ namespace MQTTnet.Formatter.V3
|
|||||||
}
|
}
|
||||||
case MqttControlPacketType.Disconnect:
|
case MqttControlPacketType.Disconnect:
|
||||||
return DisconnectPacket;
|
return DisconnectPacket;
|
||||||
|
|
||||||
case MqttControlPacketType.Subscribe:
|
case MqttControlPacketType.Subscribe:
|
||||||
return DecodeSubscribePacket(receivedMqttPacket.Body);
|
return DecodeSubscribePacket(receivedMqttPacket.Body);
|
||||||
case MqttControlPacketType.SubAck:
|
case MqttControlPacketType.SubAck:
|
||||||
@@ -103,9 +103,10 @@ namespace MQTTnet.Formatter.V3
|
|||||||
var remainingLength = (uint)(_bufferWriter.Length - 5);
|
var remainingLength = (uint)(_bufferWriter.Length - 5);
|
||||||
|
|
||||||
var publishPacket = packet as MqttPublishPacket;
|
var publishPacket = packet as MqttPublishPacket;
|
||||||
if (publishPacket?.Payload != null)
|
var payloadSegment = publishPacket?.PayloadSegment;
|
||||||
|
if (payloadSegment != null)
|
||||||
{
|
{
|
||||||
remainingLength += (uint)publishPacket.Payload.Length;
|
remainingLength += (uint)payloadSegment.Value.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
|
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
|
||||||
@@ -119,18 +120,13 @@ namespace MQTTnet.Formatter.V3
|
|||||||
_bufferWriter.WriteVariableByteInteger(remainingLength);
|
_bufferWriter.WriteVariableByteInteger(remainingLength);
|
||||||
|
|
||||||
var buffer = _bufferWriter.GetBuffer();
|
var buffer = _bufferWriter.GetBuffer();
|
||||||
|
|
||||||
var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset);
|
var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset);
|
||||||
|
|
||||||
if (publishPacket?.Payload != null)
|
return payloadSegment == null
|
||||||
{
|
? new MqttPacketBuffer(firstSegment)
|
||||||
var payloadSegment = new ArraySegment<byte>(publishPacket.Payload, 0, publishPacket.Payload.Length);
|
: new MqttPacketBuffer(firstSegment, payloadSegment.Value);
|
||||||
return new MqttPacketBuffer(firstSegment, payloadSegment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MqttPacketBuffer(firstSegment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttPacket DecodeConnAckPacket(ArraySegment<byte> body)
|
MqttPacket DecodeConnAckPacket(ArraySegment<byte> body)
|
||||||
{
|
{
|
||||||
ThrowIfBodyIsEmpty(body);
|
ThrowIfBodyIsEmpty(body);
|
||||||
@@ -177,7 +173,7 @@ namespace MQTTnet.Formatter.V3
|
|||||||
|
|
||||||
var tryPrivate = (protocolVersion & 0x80) > 0;
|
var tryPrivate = (protocolVersion & 0x80) > 0;
|
||||||
protocolVersion &= 0x7F;
|
protocolVersion &= 0x7F;
|
||||||
|
|
||||||
if (protocolVersion != 3 && protocolVersion != 4)
|
if (protocolVersion != 3 && protocolVersion != 4)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("MQTT protocol version do not match MQTT v3.");
|
throw new MqttProtocolViolationException("MQTT protocol version do not match MQTT v3.");
|
||||||
@@ -282,7 +278,7 @@ namespace MQTTnet.Formatter.V3
|
|||||||
|
|
||||||
if (!_bufferReader.EndOfStream)
|
if (!_bufferReader.EndOfStream)
|
||||||
{
|
{
|
||||||
packet.Payload = _bufferReader.ReadRemainingData();
|
packet.PayloadSegment = new ArraySegment<byte>(_bufferReader.ReadRemainingData());
|
||||||
}
|
}
|
||||||
|
|
||||||
return packet;
|
return packet;
|
||||||
@@ -421,7 +417,7 @@ namespace MQTTnet.Formatter.V3
|
|||||||
{
|
{
|
||||||
protocolVersion |= 0x80;
|
protocolVersion |= 0x80;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferWriter.WriteByte((byte)protocolVersion);
|
bufferWriter.WriteByte((byte)protocolVersion);
|
||||||
|
|
||||||
byte connectFlags = 0x0;
|
byte connectFlags = 0x0;
|
||||||
@@ -463,7 +459,7 @@ namespace MQTTnet.Formatter.V3
|
|||||||
if (packet.WillFlag)
|
if (packet.WillFlag)
|
||||||
{
|
{
|
||||||
bufferWriter.WriteString(packet.WillTopic);
|
bufferWriter.WriteString(packet.WillTopic);
|
||||||
bufferWriter.WriteBinaryData(packet.WillMessage);
|
bufferWriter.WriteBinary(packet.WillMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.Username != null)
|
if (packet.Username != null)
|
||||||
@@ -473,7 +469,7 @@ namespace MQTTnet.Formatter.V3
|
|||||||
|
|
||||||
if (packet.Password != null)
|
if (packet.Password != null)
|
||||||
{
|
{
|
||||||
bufferWriter.WriteBinaryData(packet.Password);
|
bufferWriter.WriteBinary(packet.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
|
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
|
||||||
@@ -525,7 +521,7 @@ namespace MQTTnet.Formatter.V3
|
|||||||
if (packet.WillFlag)
|
if (packet.WillFlag)
|
||||||
{
|
{
|
||||||
bufferWriter.WriteString(packet.WillTopic);
|
bufferWriter.WriteString(packet.WillTopic);
|
||||||
bufferWriter.WriteBinaryData(packet.WillMessage);
|
bufferWriter.WriteBinary(packet.WillMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.Username != null)
|
if (packet.Username != null)
|
||||||
@@ -535,7 +531,7 @@ namespace MQTTnet.Formatter.V3
|
|||||||
|
|
||||||
if (packet.Password != null)
|
if (packet.Password != null)
|
||||||
{
|
{
|
||||||
bufferWriter.WriteBinaryData(packet.Password);
|
bufferWriter.WriteBinary(packet.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
|
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ namespace MQTTnet.Formatter.V5
|
|||||||
|
|
||||||
if (!_bufferReader.EndOfStream)
|
if (!_bufferReader.EndOfStream)
|
||||||
{
|
{
|
||||||
packet.Payload = _bufferReader.ReadRemainingData();
|
packet.PayloadSegment = new ArraySegment<byte>(_bufferReader.ReadRemainingData());
|
||||||
}
|
}
|
||||||
|
|
||||||
return packet;
|
return packet;
|
||||||
|
|||||||
@@ -37,9 +37,11 @@ namespace MQTTnet.Formatter.V5
|
|||||||
var remainingLength = (uint)_bufferWriter.Length - 5;
|
var remainingLength = (uint)_bufferWriter.Length - 5;
|
||||||
|
|
||||||
var publishPacket = packet as MqttPublishPacket;
|
var publishPacket = packet as MqttPublishPacket;
|
||||||
if (publishPacket?.Payload != null)
|
var payloadSegment = publishPacket?.PayloadSegment;
|
||||||
|
|
||||||
|
if (payloadSegment != null)
|
||||||
{
|
{
|
||||||
remainingLength += (uint)publishPacket.Payload.Length;
|
remainingLength += (uint)payloadSegment.Value.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
|
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
|
||||||
@@ -53,18 +55,13 @@ namespace MQTTnet.Formatter.V5
|
|||||||
_bufferWriter.WriteVariableByteInteger(remainingLength);
|
_bufferWriter.WriteVariableByteInteger(remainingLength);
|
||||||
|
|
||||||
var buffer = _bufferWriter.GetBuffer();
|
var buffer = _bufferWriter.GetBuffer();
|
||||||
|
|
||||||
var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset);
|
var firstSegment = new ArraySegment<byte>(buffer, headerOffset, _bufferWriter.Length - headerOffset);
|
||||||
|
|
||||||
if (publishPacket?.Payload != null)
|
return payloadSegment == null
|
||||||
{
|
? new MqttPacketBuffer(firstSegment)
|
||||||
var payloadSegment = new ArraySegment<byte>(publishPacket.Payload, 0, publishPacket.Payload.Length);
|
: new MqttPacketBuffer(firstSegment, payloadSegment.Value);
|
||||||
return new MqttPacketBuffer(firstSegment, payloadSegment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MqttPacketBuffer(firstSegment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byte EncodeAuthPacket(MqttAuthPacket packet)
|
byte EncodeAuthPacket(MqttAuthPacket packet)
|
||||||
{
|
{
|
||||||
_bufferWriter.WriteByte((byte)packet.ReasonCode);
|
_bufferWriter.WriteByte((byte)packet.ReasonCode);
|
||||||
@@ -189,7 +186,7 @@ namespace MQTTnet.Formatter.V5
|
|||||||
_propertiesWriter.Reset();
|
_propertiesWriter.Reset();
|
||||||
|
|
||||||
_bufferWriter.WriteString(packet.WillTopic);
|
_bufferWriter.WriteString(packet.WillTopic);
|
||||||
_bufferWriter.WriteBinaryData(packet.WillMessage);
|
_bufferWriter.WriteBinary(packet.WillMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.Username != null)
|
if (packet.Username != null)
|
||||||
@@ -199,7 +196,7 @@ namespace MQTTnet.Formatter.V5
|
|||||||
|
|
||||||
if (packet.Password != null)
|
if (packet.Password != null)
|
||||||
{
|
{
|
||||||
_bufferWriter.WriteBinaryData(packet.Password);
|
_bufferWriter.WriteBinary(packet.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
|
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ namespace MQTTnet.Formatter.V5
|
|||||||
}
|
}
|
||||||
|
|
||||||
_bufferWriter.WriteByte((byte)id);
|
_bufferWriter.WriteByte((byte)id);
|
||||||
_bufferWriter.WriteBinaryData(value);
|
_bufferWriter.WriteBinary(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteAsFourByteInteger(MqttPropertyId id, uint value)
|
void WriteAsFourByteInteger(MqttPropertyId id, uint value)
|
||||||
|
|||||||
@@ -40,7 +40,12 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
var bufferWriter = new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax);
|
var bufferWriter = new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax);
|
||||||
var packetFormatterAdapter = new MqttPacketFormatterAdapter(options.ProtocolVersion, bufferWriter);
|
var packetFormatterAdapter = new MqttPacketFormatterAdapter(options.ProtocolVersion, bufferWriter);
|
||||||
return new MqttChannelAdapter(channel, packetFormatterAdapter, packetInspector, logger);
|
|
||||||
|
return new MqttChannelAdapter(channel, packetFormatterAdapter, logger)
|
||||||
|
{
|
||||||
|
AllowPacketFragmentation = options.AllowPacketFragmentation,
|
||||||
|
PacketInspector = packetInspector
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ namespace MQTTnet.Implementations
|
|||||||
var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter);
|
var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter);
|
||||||
var tcpChannel = new MqttTcpChannel(args.Socket, clientCertificate, _options);
|
var tcpChannel = new MqttTcpChannel(args.Socket, clientCertificate, _options);
|
||||||
|
|
||||||
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, null, _rootLogger))
|
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger))
|
||||||
{
|
{
|
||||||
await clientHandler(clientAdapter).ConfigureAwait(false);
|
await clientHandler(clientAdapter).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,51 @@
|
|||||||
// 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.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
#if !WINDOWS_UWP
|
#if !WINDOWS_UWP
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.Diagnostics;
|
|
||||||
using MQTTnet.Server;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Implementations
|
namespace MQTTnet.Implementations
|
||||||
{
|
{
|
||||||
public sealed class MqttTcpServerAdapter : IMqttServerAdapter
|
public sealed class MqttTcpServerAdapter : IMqttServerAdapter
|
||||||
{
|
{
|
||||||
readonly List<MqttTcpServerListener> _listeners = new List<MqttTcpServerListener>();
|
readonly List<MqttTcpServerListener> _listeners = new List<MqttTcpServerListener>();
|
||||||
|
|
||||||
|
CancellationTokenSource _cancellationTokenSource;
|
||||||
|
|
||||||
MqttServerOptions _serverOptions;
|
MqttServerOptions _serverOptions;
|
||||||
CancellationTokenSource _cancellationTokenSource;
|
|
||||||
|
|
||||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||||
|
|
||||||
public bool TreatSocketOpeningErrorAsWarning { get; set; }
|
public bool TreatSocketOpeningErrorAsWarning { get; set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
if (_cancellationTokenSource != null) throw new InvalidOperationException("Server is already started.");
|
if (_cancellationTokenSource != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Server is already started.");
|
||||||
|
}
|
||||||
|
|
||||||
_serverOptions = options;
|
_serverOptions = options;
|
||||||
|
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
if (options.DefaultEndpointOptions.IsEnabled)
|
if (options.DefaultEndpointOptions.IsEnabled)
|
||||||
{
|
{
|
||||||
RegisterListeners(options.DefaultEndpointOptions, null, logger, _cancellationTokenSource.Token);
|
RegisterListeners(options.DefaultEndpointOptions, logger, _cancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.TlsEndpointOptions?.IsEnabled == true)
|
if (options.TlsEndpointOptions?.IsEnabled == true)
|
||||||
@@ -48,13 +55,7 @@ namespace MQTTnet.Implementations
|
|||||||
throw new ArgumentException("TLS certificate is not set.");
|
throw new ArgumentException("TLS certificate is not set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var tlsCertificate = options.TlsEndpointOptions.CertificateProvider.GetCertificate();
|
RegisterListeners(options.TlsEndpointOptions, logger, _cancellationTokenSource.Token);
|
||||||
if (!tlsCertificate.HasPrivateKey)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterListeners(options.TlsEndpointOptions, tlsCertificate, logger, _cancellationTokenSource.Token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
@@ -66,11 +67,6 @@ namespace MQTTnet.Implementations
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Cleanup()
|
void Cleanup()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -91,11 +87,22 @@ namespace MQTTnet.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, X509Certificate2 tlsCertificate, IMqttNetLogger logger, CancellationToken cancellationToken)
|
Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter)
|
||||||
|
{
|
||||||
|
var clientHandler = ClientHandler;
|
||||||
|
if (clientHandler == null)
|
||||||
|
{
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientHandler(channelAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, IMqttNetLogger logger, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!tcpEndpointOptions.BoundInterNetworkAddress.Equals(IPAddress.None))
|
if (!tcpEndpointOptions.BoundInterNetworkAddress.Equals(IPAddress.None))
|
||||||
{
|
{
|
||||||
var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, tlsCertificate, logger)
|
var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, logger)
|
||||||
{
|
{
|
||||||
ClientHandler = OnClientAcceptedAsync
|
ClientHandler = OnClientAcceptedAsync
|
||||||
};
|
};
|
||||||
@@ -108,7 +115,7 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
if (!tcpEndpointOptions.BoundInterNetworkV6Address.Equals(IPAddress.None))
|
if (!tcpEndpointOptions.BoundInterNetworkV6Address.Equals(IPAddress.None))
|
||||||
{
|
{
|
||||||
var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, tlsCertificate, logger)
|
var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, logger)
|
||||||
{
|
{
|
||||||
ClientHandler = OnClientAcceptedAsync
|
ClientHandler = OnClientAcceptedAsync
|
||||||
};
|
};
|
||||||
@@ -119,17 +126,6 @@ namespace MQTTnet.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter)
|
|
||||||
{
|
|
||||||
var clientHandler = ClientHandler;
|
|
||||||
if (clientHandler == null)
|
|
||||||
{
|
|
||||||
return CompletedTask.Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientHandler(channelAdapter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -27,8 +27,7 @@ namespace MQTTnet.Implementations
|
|||||||
readonly MqttServerOptions _serverOptions;
|
readonly MqttServerOptions _serverOptions;
|
||||||
readonly MqttServerTcpEndpointBaseOptions _options;
|
readonly MqttServerTcpEndpointBaseOptions _options;
|
||||||
readonly MqttServerTlsTcpEndpointOptions _tlsOptions;
|
readonly MqttServerTlsTcpEndpointOptions _tlsOptions;
|
||||||
readonly X509Certificate2 _tlsCertificate;
|
|
||||||
|
|
||||||
CrossPlatformSocket _socket;
|
CrossPlatformSocket _socket;
|
||||||
IPEndPoint _localEndPoint;
|
IPEndPoint _localEndPoint;
|
||||||
|
|
||||||
@@ -36,13 +35,11 @@ namespace MQTTnet.Implementations
|
|||||||
AddressFamily addressFamily,
|
AddressFamily addressFamily,
|
||||||
MqttServerOptions serverOptions,
|
MqttServerOptions serverOptions,
|
||||||
MqttServerTcpEndpointBaseOptions tcpEndpointOptions,
|
MqttServerTcpEndpointBaseOptions tcpEndpointOptions,
|
||||||
X509Certificate2 tlsCertificate,
|
|
||||||
IMqttNetLogger logger)
|
IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
_addressFamily = addressFamily;
|
_addressFamily = addressFamily;
|
||||||
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
||||||
_options = tcpEndpointOptions ?? throw new ArgumentNullException(nameof(tcpEndpointOptions));
|
_options = tcpEndpointOptions ?? throw new ArgumentNullException(nameof(tcpEndpointOptions));
|
||||||
_tlsCertificate = tlsCertificate;
|
|
||||||
_rootLogger = logger;
|
_rootLogger = logger;
|
||||||
_logger = logger.WithSource(nameof(MqttTcpServerListener));
|
_logger = logger.WithSource(nameof(MqttTcpServerListener));
|
||||||
|
|
||||||
@@ -66,7 +63,7 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
_localEndPoint = new IPEndPoint(boundIp, _options.Port);
|
_localEndPoint = new IPEndPoint(boundIp, _options.Port);
|
||||||
|
|
||||||
_logger.Info("Starting TCP listener (Endpoint='{0}', TLS={1}).", _localEndPoint, _tlsCertificate != null);
|
_logger.Info("Starting TCP listener (Endpoint={0}, TLS={1})", _localEndPoint, _tlsOptions?.CertificateProvider != null);
|
||||||
|
|
||||||
_socket = new CrossPlatformSocket(_addressFamily);
|
_socket = new CrossPlatformSocket(_addressFamily);
|
||||||
|
|
||||||
@@ -115,7 +112,7 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
_socket.Listen(_options.ConnectionBacklog);
|
_socket.Listen(_options.ConnectionBacklog);
|
||||||
|
|
||||||
_logger.Verbose("TCP listener started (Endpoint='{0}'.", _localEndPoint);
|
_logger.Verbose("TCP listener started (Endpoint={0})", _localEndPoint);
|
||||||
|
|
||||||
Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
|
Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
|
||||||
|
|
||||||
@@ -128,7 +125,7 @@ namespace MQTTnet.Implementations
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Warning(exception, "Error while creating listener socket for local end point '{0}'.", _localEndPoint);
|
_logger.Warning(exception, "Error while starting TCP listener (Endpoint={0})", _localEndPoint);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,10 +133,6 @@ namespace MQTTnet.Implementations
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_socket?.Dispose();
|
_socket?.Dispose();
|
||||||
|
|
||||||
#if !NET452
|
|
||||||
_tlsCertificate?.Dispose();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task AcceptClientConnectionsAsync(CancellationToken cancellationToken)
|
async Task AcceptClientConnectionsAsync(CancellationToken cancellationToken)
|
||||||
@@ -170,7 +163,7 @@ namespace MQTTnet.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Error(exception, "Error while accepting connection at TCP listener {0} TLS={1}.", _localEndPoint, _tlsCertificate != null);
|
_logger.Error(exception, "Error while accepting TCP connection (Endpoint={0})", _localEndPoint);
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,24 +178,26 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
remoteEndPoint = clientSocket.RemoteEndPoint.ToString();
|
remoteEndPoint = clientSocket.RemoteEndPoint.ToString();
|
||||||
|
|
||||||
_logger.Verbose("Client '{0}' accepted by TCP listener '{1}, {2}'.",
|
_logger.Verbose("TCP client '{0}' accepted (Local endpoint={1})", remoteEndPoint, _localEndPoint);
|
||||||
remoteEndPoint,
|
|
||||||
_localEndPoint,
|
|
||||||
_addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6");
|
|
||||||
|
|
||||||
clientSocket.NoDelay = _options.NoDelay;
|
clientSocket.NoDelay = _options.NoDelay;
|
||||||
stream = clientSocket.GetStream();
|
stream = clientSocket.GetStream();
|
||||||
X509Certificate2 clientCertificate = null;
|
var clientCertificate = _tlsOptions?.CertificateProvider?.GetCertificate();
|
||||||
|
|
||||||
if (_tlsCertificate != null)
|
if (clientCertificate != null)
|
||||||
{
|
{
|
||||||
|
if (!clientCertificate.HasPrivateKey)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
|
||||||
|
}
|
||||||
|
|
||||||
var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback);
|
var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback);
|
||||||
|
|
||||||
#if NETCOREAPP3_1_OR_GREATER
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
await sslStream.AuthenticateAsServerAsync(
|
await sslStream.AuthenticateAsServerAsync(
|
||||||
new SslServerAuthenticationOptions()
|
new SslServerAuthenticationOptions
|
||||||
{
|
{
|
||||||
ServerCertificate = _tlsCertificate,
|
ServerCertificate = clientCertificate,
|
||||||
ClientCertificateRequired = _tlsOptions.ClientCertificateRequired,
|
ClientCertificateRequired = _tlsOptions.ClientCertificateRequired,
|
||||||
EnabledSslProtocols = _tlsOptions.SslProtocol,
|
EnabledSslProtocols = _tlsOptions.SslProtocol,
|
||||||
CertificateRevocationCheckMode = _tlsOptions.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
|
CertificateRevocationCheckMode = _tlsOptions.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
|
||||||
@@ -211,7 +206,7 @@ namespace MQTTnet.Implementations
|
|||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
#else
|
#else
|
||||||
await sslStream.AuthenticateAsServerAsync(
|
await sslStream.AuthenticateAsServerAsync(
|
||||||
_tlsCertificate,
|
clientCertificate,
|
||||||
_tlsOptions.ClientCertificateRequired,
|
_tlsOptions.ClientCertificateRequired,
|
||||||
_tlsOptions.SslProtocol,
|
_tlsOptions.SslProtocol,
|
||||||
_tlsOptions.CheckCertificateRevocation).ConfigureAwait(false);
|
_tlsOptions.CheckCertificateRevocation).ConfigureAwait(false);
|
||||||
@@ -234,7 +229,7 @@ namespace MQTTnet.Implementations
|
|||||||
var bufferWriter = new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax);
|
var bufferWriter = new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax);
|
||||||
var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter);
|
var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter);
|
||||||
|
|
||||||
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, null, _rootLogger))
|
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger))
|
||||||
{
|
{
|
||||||
await clientHandler(clientAdapter).ConfigureAwait(false);
|
await clientHandler(clientAdapter).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -254,7 +249,7 @@ namespace MQTTnet.Implementations
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Error(exception, "Error while handling client connection.");
|
_logger.Error(exception, "Error while handling TCP client connection");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -269,10 +264,7 @@ namespace MQTTnet.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Verbose("Client '{0}' disconnected at TCP listener '{1}, {2}'.",
|
_logger.Verbose("TCP client '{0}' disconnected (Local endpoint={1})", remoteEndPoint, _localEndPoint);
|
||||||
remoteEndPoint,
|
|
||||||
_localEndPoint,
|
|
||||||
_addressFamily == AddressFamily.InterNetwork ? "ipv4" : "ipv6");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false);
|
await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch
|
||||||
{
|
{
|
||||||
// Prevent a memory leak when always creating new instance which will fail while connecting.
|
// Prevent a memory leak when always creating new instance which will fail while connecting.
|
||||||
clientWebSocket.Dispose();
|
clientWebSocket.Dispose();
|
||||||
@@ -103,7 +103,7 @@ namespace MQTTnet.Implementations
|
|||||||
public async Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
|
public async Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
// MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is received the recipient MUST close the Network Connection [MQTT-6.0.0-1].
|
// MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is received the recipient MUST close the Network Connection [MQTT-6.0.0-1].
|
||||||
// A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2].
|
// A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2].
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace MQTTnet.Internal
|
|||||||
|
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
|
|
||||||
#if !NETSTANDARD1_3
|
#if !NETSTANDARD1_3 && !WINDOWS_UWP
|
||||||
if (typeof(IDisposable).IsAssignableFrom(typeof(TItem)))
|
if (typeof(IDisposable).IsAssignableFrom(typeof(TItem)))
|
||||||
{
|
{
|
||||||
while (_queue.TryDequeue(out TItem item))
|
while (_queue.TryDequeue(out TItem item))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace MQTTnet.LowLevelClient
|
|||||||
{
|
{
|
||||||
public interface ILowLevelMqttClient : IDisposable
|
public interface ILowLevelMqttClient : IDisposable
|
||||||
{
|
{
|
||||||
event Func<InspectMqttPacketEventArgs, Task> InspectPackage;
|
event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
|
||||||
|
|
||||||
bool IsConnected { get; }
|
bool IsConnected { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ namespace MQTTnet.LowLevelClient
|
|||||||
_logger = logger.WithSource(nameof(LowLevelMqttClient));
|
_logger = logger.WithSource(nameof(LowLevelMqttClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Func<InspectMqttPacketEventArgs, Task> InspectPackage
|
public event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync
|
||||||
{
|
{
|
||||||
add => _inspectPacketEvent.AddHandler(value);
|
add => _inspectPacketEvent.AddHandler(value);
|
||||||
remove => _inspectPacketEvent.RemoveHandler(value);
|
remove => _inspectPacketEvent.RemoveHandler(value);
|
||||||
@@ -52,13 +52,19 @@ namespace MQTTnet.LowLevelClient
|
|||||||
throw new InvalidOperationException("Low level MQTT client is already connected. Disconnect first before connecting again.");
|
throw new InvalidOperationException("Low level MQTT client is already connected. Disconnect first before connecting again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var newAdapter = _clientAdapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_inspectPacketEvent, _rootLogger), _rootLogger);
|
MqttPacketInspector packetInspector = null;
|
||||||
|
if (_inspectPacketEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
packetInspector = new MqttPacketInspector(_inspectPacketEvent, _rootLogger);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newAdapter = _clientAdapterFactory.CreateClientAdapter(options, packetInspector, _rootLogger);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Verbose("Trying to connect with server '{0}'.", options.ChannelOptions);
|
_logger.Verbose("Trying to connect with server '{0}'", options.ChannelOptions);
|
||||||
await newAdapter.ConnectAsync(cancellationToken).ConfigureAwait(false);
|
await newAdapter.ConnectAsync(cancellationToken).ConfigureAwait(false);
|
||||||
_logger.Verbose("Connection with server established.");
|
_logger.Verbose("Connection with server established");
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cauthentication/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cconnecting/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cconnecting/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdiagnostics/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdiagnostics/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdisconnecting/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdisconnecting/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
@@ -2,14 +2,19 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace MQTTnet
|
namespace MQTTnet
|
||||||
{
|
{
|
||||||
public sealed class MqttApplicationMessage
|
public sealed class MqttApplicationMessage
|
||||||
{
|
{
|
||||||
|
byte[] _payloadCache;
|
||||||
|
ArraySegment<byte> _payloadSegment = EmptyBuffer.ArraySegment;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the content type.
|
/// Gets or sets the content type.
|
||||||
/// The content type must be a UTF-8 encoded string. The content type value identifies the kind of UTF-8 encoded
|
/// The content type must be a UTF-8 encoded string. The content type value identifies the kind of UTF-8 encoded
|
||||||
@@ -48,10 +53,53 @@ namespace MQTTnet
|
|||||||
public uint MessageExpiryInterval { get; set; }
|
public uint MessageExpiryInterval { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the payload.
|
/// Gets or sets the payload.
|
||||||
/// The payload is the data bytes sent via the MQTT protocol.
|
/// The payload is the data bytes sent via the MQTT protocol.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte[] Payload { get; set; }
|
[Obsolete("Use PayloadSegment instead. This property will be removed in a future release.")]
|
||||||
|
public byte[] Payload
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_payloadSegment.Array == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// just reference from _payloadSegment.Array
|
||||||
|
if (_payloadSegment.Count == _payloadSegment.Array.Length)
|
||||||
|
{
|
||||||
|
return _payloadSegment.Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy from _payloadSegment
|
||||||
|
if (_payloadCache == null)
|
||||||
|
{
|
||||||
|
_payloadCache = new byte[_payloadSegment.Count];
|
||||||
|
MqttMemoryHelper.Copy(_payloadSegment.Array, _payloadSegment.Offset, _payloadCache, 0, _payloadCache.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _payloadCache;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_payloadCache = null;
|
||||||
|
_payloadSegment = value == null || value.Length == 0 ? EmptyBuffer.ArraySegment : new ArraySegment<byte>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get or set ArraySegment style of Payload.
|
||||||
|
/// </summary>
|
||||||
|
public ArraySegment<byte> PayloadSegment
|
||||||
|
{
|
||||||
|
get => _payloadSegment;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_payloadCache = null;
|
||||||
|
_payloadSegment = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the payload format indicator.
|
/// Gets or sets the payload format indicator.
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
@@ -17,8 +20,9 @@ namespace MQTTnet
|
|||||||
string _contentType;
|
string _contentType;
|
||||||
byte[] _correlationData;
|
byte[] _correlationData;
|
||||||
uint _messageExpiryInterval;
|
uint _messageExpiryInterval;
|
||||||
byte[] _payload;
|
|
||||||
MqttPayloadFormatIndicator _payloadFormatIndicator;
|
MqttPayloadFormatIndicator _payloadFormatIndicator;
|
||||||
|
ArraySegment<byte> _payloadSegment;
|
||||||
MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce;
|
MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce;
|
||||||
string _responseTopic;
|
string _responseTopic;
|
||||||
bool _retain;
|
bool _retain;
|
||||||
@@ -37,7 +41,7 @@ namespace MQTTnet
|
|||||||
var applicationMessage = new MqttApplicationMessage
|
var applicationMessage = new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
Topic = _topic,
|
Topic = _topic,
|
||||||
Payload = _payload,
|
PayloadSegment = _payloadSegment,
|
||||||
QualityOfServiceLevel = _qualityOfServiceLevel,
|
QualityOfServiceLevel = _qualityOfServiceLevel,
|
||||||
Retain = _retain,
|
Retain = _retain,
|
||||||
ContentType = _contentType,
|
ContentType = _contentType,
|
||||||
@@ -85,7 +89,13 @@ namespace MQTTnet
|
|||||||
|
|
||||||
public MqttApplicationMessageBuilder WithPayload(byte[] payload)
|
public MqttApplicationMessageBuilder WithPayload(byte[] payload)
|
||||||
{
|
{
|
||||||
_payload = payload;
|
_payloadSegment = payload == null || payload.Length == 0 ? EmptyBuffer.ArraySegment : new ArraySegment<byte>(payload);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttApplicationMessageBuilder WithPayload(ArraySegment<byte> payloadSegment)
|
||||||
|
{
|
||||||
|
_payloadSegment = payloadSegment;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,68 +103,59 @@ namespace MQTTnet
|
|||||||
{
|
{
|
||||||
if (payload == null)
|
if (payload == null)
|
||||||
{
|
{
|
||||||
_payload = null;
|
return WithPayload(default(byte[]));
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_payload = payload as byte[] ?? payload.ToArray();
|
if (payload is byte[] byteArray)
|
||||||
|
{
|
||||||
|
return WithPayload(byteArray);
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
if (payload is ArraySegment<byte> arraySegment)
|
||||||
|
{
|
||||||
|
return WithPayloadSegment(arraySegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return WithPayload(payload.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttApplicationMessageBuilder WithPayload(Stream payload)
|
public MqttApplicationMessageBuilder WithPayload(Stream payload)
|
||||||
{
|
{
|
||||||
if (payload == null)
|
return payload == null ? WithPayload(default(byte[])) : WithPayload(payload, payload.Length - payload.Position);
|
||||||
{
|
|
||||||
_payload = null;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
return WithPayload(payload, payload.Length - payload.Position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttApplicationMessageBuilder WithPayload(Stream payload, long length)
|
public MqttApplicationMessageBuilder WithPayload(Stream payload, long length)
|
||||||
{
|
{
|
||||||
if (payload == null)
|
if (payload == null || length == 0)
|
||||||
{
|
{
|
||||||
_payload = null;
|
return WithPayload(default(byte[]));
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.Length == 0)
|
var payloadBuffer = new byte[length];
|
||||||
|
var totalRead = 0;
|
||||||
|
do
|
||||||
{
|
{
|
||||||
_payload = null;
|
var bytesRead = payload.Read(payloadBuffer, totalRead, payloadBuffer.Length - totalRead);
|
||||||
}
|
if (bytesRead == 0)
|
||||||
else
|
|
||||||
{
|
|
||||||
_payload = new byte[length];
|
|
||||||
|
|
||||||
var totalRead = 0;
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
var bytesRead = payload.Read(_payload, totalRead, _payload.Length - totalRead);
|
break;
|
||||||
if (bytesRead == 0)
|
}
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalRead += bytesRead;
|
totalRead += bytesRead;
|
||||||
} while (totalRead < length);
|
} while (totalRead < length);
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return WithPayload(payloadBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttApplicationMessageBuilder WithPayload(string payload)
|
public MqttApplicationMessageBuilder WithPayload(string payload)
|
||||||
{
|
{
|
||||||
if (payload == null)
|
if (string.IsNullOrEmpty(payload))
|
||||||
{
|
{
|
||||||
_payload = null;
|
return WithPayload(default(byte[]));
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_payload = string.IsNullOrEmpty(payload) ? null : Encoding.UTF8.GetBytes(payload);
|
var payloadBuffer = Encoding.UTF8.GetBytes(payload);
|
||||||
return this;
|
return WithPayload(payloadBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -167,6 +168,20 @@ namespace MQTTnet
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttApplicationMessageBuilder WithPayloadSegment(ArraySegment<byte> payloadSegment)
|
||||||
|
{
|
||||||
|
_payloadSegment = payloadSegment;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if NETCOREAPP3_1_OR_GREATER || NETSTANDARD2_1
|
||||||
|
public MqttApplicationMessageBuilder WithPayloadSegment(ReadOnlyMemory<byte> payloadSegment)
|
||||||
|
{
|
||||||
|
return MemoryMarshal.TryGetArray(payloadSegment, out var segment) ? WithPayloadSegment(segment) : WithPayload(payloadSegment.ToArray());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The quality of service level.
|
/// The quality of service level.
|
||||||
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
|
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
|
||||||
|
|||||||
@@ -11,19 +11,13 @@ namespace MQTTnet
|
|||||||
{
|
{
|
||||||
public static string ConvertPayloadToString(this MqttApplicationMessage applicationMessage)
|
public static string ConvertPayloadToString(this MqttApplicationMessage applicationMessage)
|
||||||
{
|
{
|
||||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
if (applicationMessage == null)
|
||||||
|
|
||||||
if (applicationMessage.Payload == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applicationMessage.Payload.Length == 0)
|
var payloadSegment = applicationMessage.PayloadSegment;
|
||||||
{
|
return Encoding.UTF8.GetString(payloadSegment.Array, payloadSegment.Offset, payloadSegment.Count);
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Encoding.UTF8.GetString(applicationMessage.Payload, 0, applicationMessage.Payload.Length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace MQTTnet.PacketDispatcher
|
|||||||
{
|
{
|
||||||
public interface IMqttPacketAwaitable : IDisposable
|
public interface IMqttPacketAwaitable : IDisposable
|
||||||
{
|
{
|
||||||
MqttPacketAwaitableFilter Filter { get; }
|
int PacketIdentifier { get; }
|
||||||
|
|
||||||
void Complete(MqttPacket packet);
|
void Complete(MqttPacket packet);
|
||||||
|
|
||||||
|
|||||||
@@ -16,18 +16,13 @@ namespace MQTTnet.PacketDispatcher
|
|||||||
readonly AsyncTaskCompletionSource<MqttPacket> _promise = new AsyncTaskCompletionSource<MqttPacket>();
|
readonly AsyncTaskCompletionSource<MqttPacket> _promise = new AsyncTaskCompletionSource<MqttPacket>();
|
||||||
readonly MqttPacketDispatcher _owningPacketDispatcher;
|
readonly MqttPacketDispatcher _owningPacketDispatcher;
|
||||||
|
|
||||||
public MqttPacketAwaitable(ushort packetIdentifier, MqttPacketDispatcher owningPacketDispatcher)
|
public MqttPacketAwaitable(int packetPacketIdentifier, MqttPacketDispatcher owningPacketDispatcher)
|
||||||
{
|
{
|
||||||
Filter = new MqttPacketAwaitableFilter
|
PacketIdentifier = packetPacketIdentifier;
|
||||||
{
|
|
||||||
Type = typeof(TPacket),
|
|
||||||
Identifier = packetIdentifier
|
|
||||||
};
|
|
||||||
|
|
||||||
_owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher));
|
_owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttPacketAwaitableFilter Filter { get; }
|
public int PacketIdentifier { get; }
|
||||||
|
|
||||||
public async Task<TPacket> WaitOneAsync(CancellationToken cancellationToken)
|
public async Task<TPacket> WaitOneAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,11 +10,15 @@ namespace MQTTnet.PacketDispatcher
|
|||||||
{
|
{
|
||||||
public sealed class MqttPacketDispatcher : IDisposable
|
public sealed class MqttPacketDispatcher : IDisposable
|
||||||
{
|
{
|
||||||
|
public const int PingRespPacketFakeIdentifier = 2147483647;
|
||||||
|
public const int AuthPacketFakeIdentifier = 2147483646;
|
||||||
|
public const int SuccessAuthPacketFakeIdentifier = 2147483647;
|
||||||
|
|
||||||
readonly List<IMqttPacketAwaitable> _waiters = new List<IMqttPacketAwaitable>();
|
readonly List<IMqttPacketAwaitable> _waiters = new List<IMqttPacketAwaitable>();
|
||||||
|
|
||||||
bool _isDisposed;
|
bool _isDisposed;
|
||||||
|
|
||||||
public MqttPacketAwaitable<TResponsePacket> AddAwaitable<TResponsePacket>(ushort packetIdentifier) where TResponsePacket : MqttPacket
|
public MqttPacketAwaitable<TResponsePacket> AddAwaitable<TResponsePacket>(int packetIdentifier) where TResponsePacket : MqttPacket
|
||||||
{
|
{
|
||||||
var awaitable = new MqttPacketAwaitable<TResponsePacket>(packetIdentifier, this);
|
var awaitable = new MqttPacketAwaitable<TResponsePacket>(packetIdentifier, this);
|
||||||
|
|
||||||
@@ -99,15 +103,21 @@ namespace MQTTnet.PacketDispatcher
|
|||||||
throw new ArgumentNullException(nameof(packet));
|
throw new ArgumentNullException(nameof(packet));
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort identifier = 0;
|
var identifier = 0;
|
||||||
if (packet is MqttPacketWithIdentifier packetWithIdentifier)
|
if (packet is MqttPacketWithIdentifier packetWithIdentifier)
|
||||||
{
|
{
|
||||||
identifier = packetWithIdentifier.PacketIdentifier;
|
identifier = packetWithIdentifier.PacketIdentifier;
|
||||||
}
|
}
|
||||||
|
else if (packet is MqttPingRespPacket)
|
||||||
var packetType = packet.GetType();
|
{
|
||||||
|
identifier = PingRespPacketFakeIdentifier;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Cannot dispatch {packet.GetRfcName()} packet.");
|
||||||
|
}
|
||||||
|
|
||||||
var waiters = new List<IMqttPacketAwaitable>();
|
var waiters = new List<IMqttPacketAwaitable>();
|
||||||
|
|
||||||
lock (_waiters)
|
lock (_waiters)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
@@ -115,11 +125,8 @@ namespace MQTTnet.PacketDispatcher
|
|||||||
for (var i = _waiters.Count - 1; i >= 0; i--)
|
for (var i = _waiters.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var entry = _waiters[i];
|
var entry = _waiters[i];
|
||||||
|
|
||||||
// Note: The PingRespPacket will also arrive here and has NO identifier but there
|
if (entry.PacketIdentifier != identifier)
|
||||||
// is code which waits for it. So the code must be able to deal with filters which
|
|
||||||
// are referring to the type only (identifier is 0)!
|
|
||||||
if (entry.Filter.Type != packetType || entry.Filter.Identifier != identifier)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace MQTTnet.Packets
|
|||||||
public MqttQualityOfServiceLevel MaximumQoS { get; set; }
|
public MqttQualityOfServiceLevel MaximumQoS { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Added in MQTTv5.
|
/// The return code is only used in MQTTv 5.0.0 and higher.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttConnectReasonCode ReasonCode { get; set; }
|
public MqttConnectReasonCode ReasonCode { get; set; }
|
||||||
|
|
||||||
@@ -39,13 +39,16 @@ namespace MQTTnet.Packets
|
|||||||
public string ResponseInformation { get; set; }
|
public string ResponseInformation { get; set; }
|
||||||
|
|
||||||
public bool RetainAvailable { get; set; }
|
public bool RetainAvailable { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The return code is only used in MQTTv 3.1.1 and lower.
|
||||||
|
/// </summary>
|
||||||
public MqttConnectReturnCode ReturnCode { get; set; }
|
public MqttConnectReturnCode ReturnCode { get; set; }
|
||||||
|
|
||||||
public ushort ServerKeepAlive { get; set; }
|
public ushort ServerKeepAlive { get; set; }
|
||||||
|
|
||||||
public string ServerReference { get; set; }
|
public string ServerReference { get; set; }
|
||||||
|
|
||||||
public uint SessionExpiryInterval { get; set; }
|
public uint SessionExpiryInterval { get; set; }
|
||||||
|
|
||||||
public bool SharedSubscriptionAvailable { get; set; }
|
public bool SharedSubscriptionAvailable { get; set; }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
@@ -17,10 +18,10 @@ namespace MQTTnet.Packets
|
|||||||
|
|
||||||
public uint MessageExpiryInterval { get; set; }
|
public uint MessageExpiryInterval { get; set; }
|
||||||
|
|
||||||
public byte[] Payload { get; set; }
|
|
||||||
|
|
||||||
public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified;
|
public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified;
|
||||||
|
|
||||||
|
public ArraySegment<byte> PayloadSegment { get; set; }
|
||||||
|
|
||||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } = MqttQualityOfServiceLevel.AtMostOnce;
|
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } = MqttQualityOfServiceLevel.AtMostOnce;
|
||||||
|
|
||||||
public string ResponseTopic { get; set; }
|
public string ResponseTopic { get; set; }
|
||||||
@@ -38,7 +39,7 @@ namespace MQTTnet.Packets
|
|||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
$"Publish: [Topic={Topic}] [Payload.Length={Payload?.Length}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]";
|
$"Publish: [Topic={Topic}] [PayloadLength={PayloadSegment.Count}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MQTTnet.Packets
|
||||||
|
{
|
||||||
|
public sealed class MqttUserPropertiesBuilder
|
||||||
|
{
|
||||||
|
readonly List<MqttUserProperty> _properties = new List<MqttUserProperty>();
|
||||||
|
|
||||||
|
public List<MqttUserProperty> Build()
|
||||||
|
{
|
||||||
|
return _properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttUserPropertiesBuilder With(string name, string value = "")
|
||||||
|
{
|
||||||
|
_properties.Add(new MqttUserProperty(name, value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-4
@@ -4,12 +4,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace MQTTnet.PacketDispatcher
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class MqttPacketAwaitableFilter
|
public sealed class ClientReAuthenticatingEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public Type Type { get; set; }
|
|
||||||
|
|
||||||
public ushort Identifier { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class InterceptingClientApplicationMessageEnqueueEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public InterceptingClientApplicationMessageEnqueueEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage)
|
||||||
|
{
|
||||||
|
SenderClientId = senderClientId ?? throw new ArgumentNullException(nameof(senderClientId));
|
||||||
|
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
|
||||||
|
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether the enqueue of the application message should be performed or not.
|
||||||
|
/// If set to _False_ the client will not receive the application message.
|
||||||
|
/// </summary>
|
||||||
|
public bool AcceptEnqueue { get; set; } = true;
|
||||||
|
|
||||||
|
public MqttApplicationMessage ApplicationMessage { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the connection with the sender should be closed.
|
||||||
|
/// </summary>
|
||||||
|
public bool CloseSenderConnection { get; set; }
|
||||||
|
|
||||||
|
public string ReceiverClientId { get; }
|
||||||
|
|
||||||
|
public string SenderClientId { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,12 +16,14 @@ namespace MQTTnet.Server
|
|||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
string clientId,
|
string clientId,
|
||||||
MqttSessionStatus session,
|
MqttSessionStatus session,
|
||||||
MqttTopicFilter topicFilter)
|
MqttTopicFilter topicFilter,
|
||||||
|
List<MqttUserProperty> userProperties)
|
||||||
{
|
{
|
||||||
CancellationToken = cancellationToken;
|
CancellationToken = cancellationToken;
|
||||||
ClientId = clientId;
|
ClientId = clientId;
|
||||||
Session = session;
|
Session = session;
|
||||||
TopicFilter = topicFilter;
|
TopicFilter = topicFilter;
|
||||||
|
UserProperties = userProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -75,6 +77,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the user properties.
|
/// Gets or sets the user properties.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<MqttUserProperty> UserProperties { get; set; }
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,21 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs
|
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, IDictionary sessionItems, string topic)
|
public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, IDictionary sessionItems, string topic, List<MqttUserProperty> userProperties)
|
||||||
{
|
{
|
||||||
CancellationToken = cancellationToken;
|
CancellationToken = cancellationToken;
|
||||||
ClientId = clientId;
|
ClientId = clientId;
|
||||||
SessionItems = sessionItems;
|
SessionItems = sessionItems;
|
||||||
Topic = topic;
|
Topic = topic;
|
||||||
|
UserProperties = userProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -59,5 +62,11 @@ namespace MQTTnet.Server
|
|||||||
/// separator).
|
/// separator).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Topic { get; }
|
public string Topic { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user properties.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class PartialAuthenticationResponse
|
||||||
|
{
|
||||||
|
public byte[] AuthenticationData { get; set; }
|
||||||
|
|
||||||
|
public MqttAuthenticateReasonCode ReasonCode { get; set; }
|
||||||
|
|
||||||
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,10 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
@@ -19,11 +22,12 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
readonly MqttConnectPacket _connectPacket;
|
readonly MqttConnectPacket _connectPacket;
|
||||||
|
|
||||||
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter, IDictionary sessionItems)
|
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter, IDictionary sessionItems, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||||
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
||||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
CancellationToken = cancellationToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -44,6 +48,11 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a cancellation token which is being canceled as soon as the transport connection is closed.
|
||||||
|
/// </summary>
|
||||||
|
public CancellationToken CancellationToken { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the channel adapter. This can be a _MqttConnectionContext_ (used in ASP.NET), a _MqttChannelAdapter_ (used for
|
/// Gets the channel adapter. This can be a _MqttConnectionContext_ (used in ASP.NET), a _MqttChannelAdapter_ (used for
|
||||||
/// TCP or WebSockets) or a custom implementation.
|
/// TCP or WebSockets) or a custom implementation.
|
||||||
@@ -189,5 +198,51 @@ namespace MQTTnet.Server
|
|||||||
/// A value of 0 indicates that the value is not used.
|
/// A value of 0 indicates that the value is not used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint WillDelayInterval => _connectPacket.WillDelayInterval;
|
public uint WillDelayInterval => _connectPacket.WillDelayInterval;
|
||||||
|
|
||||||
|
public async Task<PartialAuthenticationResponse> ReceiveAuthenticationDataAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var receivePacket = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
if (receivePacket is MqttAuthPacket authPacket)
|
||||||
|
{
|
||||||
|
if (!string.Equals(authPacket.AuthenticationMethod, AuthenticationMethod, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("The authentication method is not allowed to change while authenticating.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PartialAuthenticationResponse
|
||||||
|
{
|
||||||
|
ReasonCode = authPacket.ReasonCode,
|
||||||
|
AuthenticationData = authPacket.AuthenticationData,
|
||||||
|
UserProperties = authPacket.UserProperties
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivePacket == null)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("The client closed the connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MqttProtocolViolationException("Expected an AUTH packet from the client.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SendAuthenticationDataAsync(
|
||||||
|
MqttAuthenticateReasonCode reasonCode,
|
||||||
|
byte[] authenticationData = null,
|
||||||
|
string reasonString = null,
|
||||||
|
List<MqttUserProperty> userProperties = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// The authentication method will never change so we must use the already known one [MQTT-4.12.0-5].
|
||||||
|
var authPacket = new MqttAuthPacket
|
||||||
|
{
|
||||||
|
AuthenticationMethod = AuthenticationMethod,
|
||||||
|
AuthenticationData = authenticationData,
|
||||||
|
UserProperties = userProperties,
|
||||||
|
ReasonCode = reasonCode,
|
||||||
|
ReasonString = reasonString
|
||||||
|
};
|
||||||
|
|
||||||
|
return ChannelAdapter.SendPacketAsync(authPacket, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user