mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 01:03:01 +08:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0c5f20c5a | ||
|
|
9152fe637e | ||
|
|
b976e9c177 | ||
|
|
262a4681ad | ||
|
|
ad546adafe | ||
|
|
29a9941d86 | ||
|
|
5a21939e29 | ||
|
|
469caf656e | ||
|
|
42402fba04 | ||
|
|
a0dc8ab0d7 | ||
|
|
02ede5a2d8 | ||
|
|
cef61900df | ||
|
|
09385b1b36 | ||
|
|
cbf413f04e | ||
|
|
11249d006f | ||
|
|
0d7b020994 | ||
|
|
d71ff3d615 | ||
|
|
57745e8324 | ||
|
|
a94776eeea | ||
|
|
d3848d8cb0 | ||
|
|
6c29ea56fc | ||
|
|
ed7c150cc5 | ||
|
|
13762eedd4 | ||
|
|
e33495010e | ||
|
|
b0d1fcd161 | ||
|
|
f7a739e162 | ||
|
|
422945929e | ||
|
|
76e54dc49b | ||
|
|
b10a69a0ae | ||
|
|
4b1c7cf772 | ||
|
|
3f67df007b | ||
|
|
36b1ed06f0 | ||
|
|
4a982ea228 | ||
|
|
9fe5073186 | ||
|
|
305c552474 | ||
|
|
f632fbb447 | ||
|
|
8231d62f9f | ||
|
|
a52a717965 | ||
|
|
2b653f2379 | ||
|
|
725cfb643f | ||
|
|
29ec6edbf4 | ||
|
|
f3ef2dbf7c | ||
|
|
14eb202873 | ||
|
|
a3c7f9a22c | ||
|
|
41e0a25fc8 | ||
|
|
772cd3cd12 | ||
|
|
42cf9f73c2 | ||
|
|
ec2e08668f | ||
|
|
e9e8583d1a | ||
|
|
f68b109471 | ||
|
|
dd1e155ff4 | ||
|
|
cf6369d63f | ||
|
|
3049fc6608 | ||
|
|
b75076a1c5 | ||
|
|
ae8e2ec7c9 | ||
|
|
e7e6a080e0 | ||
|
|
dc9afa6c3a | ||
|
|
02b17f277d | ||
|
|
b9d9ea2d4d |
@@ -24,7 +24,7 @@ A clear and concise description of what the bug is.
|
|||||||
<!-- Remove the items which don't apply from the following list. -->
|
<!-- Remove the items which don't apply from the following list. -->
|
||||||
- Client
|
- Client
|
||||||
- ManagedClient
|
- ManagedClient
|
||||||
- RcpClient
|
- RpcClient
|
||||||
- Server
|
- Server
|
||||||
|
|
||||||
### To Reproduce
|
### To Reproduce
|
||||||
|
|||||||
@@ -14,6 +14,5 @@ A clear and concise description of what you want to know.
|
|||||||
<!-- Remove the items which don't apply from the following list -->
|
<!-- Remove the items which don't apply from the following list -->
|
||||||
- Client
|
- Client
|
||||||
- ManagedClient
|
- ManagedClient
|
||||||
- MQTTnet.Server standalone
|
|
||||||
- Server
|
- Server
|
||||||
- Generic
|
- Generic
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ A clear and concise description of what the problem is. Example. I'm am trying t
|
|||||||
<!-- Remove the items which don't apply from the following list -->
|
<!-- Remove the items which don't apply from the following list -->
|
||||||
- Client
|
- Client
|
||||||
- ManagedClient
|
- ManagedClient
|
||||||
- MQTTnet.Server standalone
|
|
||||||
- Server
|
- Server
|
||||||
- Generic
|
- Generic
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
We have joined the .NET Foundation!
|
|
||||||
|
|
||||||
Version 4 comes with a new API so a lot of breaking changes should be expected.
|
|
||||||
Checkout the upgrade guide (https://github.com/dotnet/MQTTnet/wiki/Upgrading-guide) for an overview of the changes.
|
|
||||||
Checkout the new samples (https://github.com/dotnet/MQTTnet/tree/feature/master/Samples) how to use the new API. The wiki only remains for version 3 of this library.
|
|
||||||
|
|
||||||
Preview builds of this library are available at: https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet
|
|
||||||
|
|
||||||
* [Core] Improved memory management when working with large payloads.
|
|
||||||
* [Core] Added support for .NET 6.0.
|
|
||||||
* [Core] nuget packages are now created by MSBuild including more information (i.e. commit hash).
|
|
||||||
* [Client] Exposed socket linger state in options.
|
|
||||||
* [Client] The OS will now choose the best TLS version to use. It is no longer fixed to 1.3 etc. (thanks to @patagonaa, #1271).
|
|
||||||
* [Client] Added support for _ServerKeepAlive_ (MQTTv5).
|
|
||||||
* [Client] Exposed user properties and reason string in subscribe result.
|
|
||||||
* [Client] Exposed user properties and reason string in unsubscribe result.
|
|
||||||
* [Client] Migrated application message handler to a regular .NET event (BREAKING CHANGE!).
|
|
||||||
* [Client] The will message is longer a regular application message due to not supported properties by the will message (BREAKING CHANGE!).
|
|
||||||
* [Client] Timeouts are no longer handled inside the library. Each method (Connect, Publish etc.) supports a cancellation token so that custom timeouts can and must be used (BREAKING CHANGE!).
|
|
||||||
* [Client] Exposed certificate revocation mode on options (@andyolivares).
|
|
||||||
* [Server] Exposed socket linger state in options.
|
|
||||||
* [Server] Added support for returning individual subscription errors (#80 thanks to @jimch)
|
|
||||||
* [Server] Improved topic filter comparisons (support for $).
|
|
||||||
* [Server] Added more MQTTv5 response information to all interceptors (BREAKING CHANGE!).
|
|
||||||
* [Server] Improved session management for MQTT v5 (#1294, thanks to @logicaloud).
|
|
||||||
* [Server] All interceptors and events are migrated from interfaces to simple events. All existing APIs are availble but must be migrated to corresponding events (BREAKING CHANGE!).
|
|
||||||
* [Server] Removed all interceptor and event interfaces including the delegate implementations etc. (BREAKING CHANGE!).
|
|
||||||
* [Server] Renamed a lot of classes and adjusted namespaces (BREAKING CHANGE!).
|
|
||||||
* [Server] Introduced a new queueing approach for internal message process (packet bus).
|
|
||||||
* [Server] For security reasons the default endpoint (1883) is no longer enabled by default (BREAKING CHANGE!).
|
|
||||||
* [Server] Added support for choosing the cipher suite (thanks to @TimSiefert).
|
|
||||||
* [Nuget] Added method summaries etc. to nuget packages (thanks to @SpringHgui).
|
|
||||||
@@ -3,8 +3,7 @@ name: CI
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ASSEMBLY_VERSION: "4.0.0.${{github.run_number}}"
|
VERSION: "4.1.0.${{github.run_number}}"
|
||||||
NUGET_VERSION: "4.0.0.${{github.run_number}}"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -40,7 +39,7 @@ jobs:
|
|||||||
run: msbuild MQTTnet.sln /t:Restore /p:Configuration="Release" /verbosity:m
|
run: msbuild MQTTnet.sln /t:Restore /p:Configuration="Release" /verbosity:m
|
||||||
|
|
||||||
- name: Build solution
|
- name: Build solution
|
||||||
run: msbuild MQTTnet.sln /t:Build /p:Configuration="Release" /verbosity:m /p:FileVersion=${{ env.ASSEMBLY_VERSION }} /p:AssemblyVersion=${{ env.ASSEMBLY_VERSION }} /p:PackageVersion=${{ env.NUGET_VERSION }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}\certificate.snk
|
run: msbuild MQTTnet.sln /t:Build /p:Configuration="Release" /verbosity:m /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}\certificate.snk
|
||||||
|
|
||||||
- name: Collect nuget Packages
|
- name: Collect nuget Packages
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/ProcessingInstructionAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/ProcessingInstructionAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/TagAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/XmlDocFormatter/TagAttributesFormat/@EntryValue">OnDifferentLines</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue"><?xml version="1.0" encoding="utf-16"?>
|
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue"><?xml version="1.0" encoding="utf-16"?>
|
||||||
<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
|
<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
|
||||||
<TypePattern DisplayName="Non-reorderable types">
|
<TypePattern DisplayName="Non-reorderable types">
|
||||||
<TypePattern.Match>
|
<TypePattern.Match>
|
||||||
<Or>
|
<Or>
|
||||||
@@ -226,6 +226,9 @@
|
|||||||
</Entry>
|
</Entry>
|
||||||
</TypePattern>
|
</TypePattern>
|
||||||
</Patterns></s:String>
|
</Patterns></s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">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.</s:String>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpAttributeForSingleLineMethodUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
@@ -2,10 +2,15 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Extensions.WebSocket4Net;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Samples.Helpers;
|
using MQTTnet.Samples.Shared;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Client;
|
namespace MQTTnet.Samples.Client;
|
||||||
|
|
||||||
@@ -111,13 +116,21 @@ public static class Client_Connection_Samples
|
|||||||
.WithTls(
|
.WithTls(
|
||||||
o =>
|
o =>
|
||||||
{
|
{
|
||||||
|
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||||
|
// certificates. This should not be used in live environments.
|
||||||
|
o.CertificateValidationHandler = _ => true;
|
||||||
|
|
||||||
|
// The default value is determined by the OS. Set manually to force version.
|
||||||
o.SslProtocol = SslProtocols.Tls12;
|
o.SslProtocol = SslProtocols.Tls12;
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
using (var timeout = new CancellationTokenSource(5000))
|
||||||
|
{
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||||
|
|
||||||
Console.WriteLine("The MQTT client is connected.");
|
Console.WriteLine("The MQTT client is connected.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +155,30 @@ public static class Client_Connection_Samples
|
|||||||
response.DumpToConsole();
|
response.DumpToConsole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task Connect_Client_Using_WebSocket4Net()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||||
|
* Instead of the .NET implementation of WebSockets the implementaion from WebSocket4Net is used. It provides more
|
||||||
|
* encryption algorithms and supports more platforms.
|
||||||
|
*
|
||||||
|
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory().UseWebSocket4Net();
|
||||||
|
|
||||||
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
|
||||||
|
|
||||||
|
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Console.WriteLine("The MQTT client is connected.");
|
||||||
|
|
||||||
|
response.DumpToConsole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task Connect_Client_With_TLS_Encryption()
|
public static async Task Connect_Client_With_TLS_Encryption()
|
||||||
{
|
{
|
||||||
@@ -159,19 +196,59 @@ public static class Client_Connection_Samples
|
|||||||
.WithTls(
|
.WithTls(
|
||||||
o =>
|
o =>
|
||||||
{
|
{
|
||||||
o.SslProtocol = SslProtocols.Tls12; // The default value is determined by the OS. Set manually to force version.
|
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||||
|
// certificates. This should not be used in live environments.
|
||||||
|
o.CertificateValidationHandler = _ => true;
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
// In MQTTv5 the response contains much more information.
|
// In MQTTv5 the response contains much more information.
|
||||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
using (var timeout = new CancellationTokenSource(5000))
|
||||||
|
{
|
||||||
|
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||||
|
|
||||||
|
Console.WriteLine("The MQTT client is connected.");
|
||||||
|
|
||||||
Console.WriteLine("The MQTT client is connected.");
|
response.DumpToConsole();
|
||||||
|
}
|
||||||
response.DumpToConsole();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task Inspect_Certificate_Validation_Errors()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample prints the certificate information while connection. This data can be used to decide whether a connection is secure or not
|
||||||
|
* including the reason for that status.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||||
|
.WithTls(
|
||||||
|
o =>
|
||||||
|
{
|
||||||
|
o.CertificateValidationHandler = eventArgs =>
|
||||||
|
{
|
||||||
|
eventArgs.Certificate.Subject.DumpToConsole();
|
||||||
|
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||||
|
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
|
||||||
|
eventArgs.Chain.ChainStatus.DumpToConsole();
|
||||||
|
eventArgs.SslPolicyErrors.DumpToConsole();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// In MQTTv5 the response contains much more information.
|
||||||
|
using (var timeout = new CancellationTokenSource(5000))
|
||||||
|
{
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task Ping_Server()
|
public static async Task Ping_Server()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Client;
|
namespace MQTTnet.Samples.Client;
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Samples.Helpers;
|
using MQTTnet.Samples.Shared;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Client;
|
namespace MQTTnet.Samples.Client;
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,13 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Samples.Shared;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Diagnostics;
|
namespace MQTTnet.Samples.Diagnostics;
|
||||||
|
|
||||||
@@ -18,9 +23,7 @@ public static class Logger_Samples
|
|||||||
|
|
||||||
var mqttFactory = new MqttFactory(new MyLogger());
|
var mqttFactory = new MqttFactory(new MyLogger());
|
||||||
|
|
||||||
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
.WithTcpServer("broker.hivemq.com")
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
@@ -28,13 +31,31 @@ public static class Logger_Samples
|
|||||||
|
|
||||||
Console.WriteLine("MQTT client is connected.");
|
Console.WriteLine("MQTT client is connected.");
|
||||||
|
|
||||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
|
||||||
.Build();
|
|
||||||
|
|
||||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task Log_To_Console()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample covers the creation of a custom logger which is used to log to the console.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory(new ConsoleLogger());
|
||||||
|
|
||||||
|
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
|
|
||||||
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||||
|
|
||||||
|
Console.WriteLine("MQTT client is connected. Press any key to exit.");
|
||||||
|
Console.ReadKey(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task Use_Event_Logger()
|
public static async Task Use_Event_Logger()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@@ -60,9 +81,7 @@ public static class Logger_Samples
|
|||||||
|
|
||||||
var mqttFactory = new MqttFactory(mqttEventLogger);
|
var mqttFactory = new MqttFactory(mqttEventLogger);
|
||||||
|
|
||||||
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
|
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||||
.WithTcpServer("broker.hivemq.com")
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||||
{
|
{
|
||||||
@@ -70,8 +89,7 @@ public static class Logger_Samples
|
|||||||
|
|
||||||
Console.WriteLine("MQTT client is connected.");
|
Console.WriteLine("MQTT client is connected.");
|
||||||
|
|
||||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
|
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
|
||||||
.Build();
|
|
||||||
|
|
||||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||||
}
|
}
|
||||||
@@ -79,7 +97,7 @@ public static class Logger_Samples
|
|||||||
|
|
||||||
sealed class MyLogger : IMqttNetLogger
|
sealed class MyLogger : IMqttNetLogger
|
||||||
{
|
{
|
||||||
public bool IsEnabled { get; set; } = true;
|
public bool IsEnabled { get; } = true;
|
||||||
|
|
||||||
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception)
|
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[] parameters, Exception exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Diagnostics;
|
namespace MQTTnet.Samples.Diagnostics;
|
||||||
|
|||||||
@@ -15,8 +15,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
|
||||||
|
|
||||||
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||||
<ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
||||||
<ProjectReference Include="..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
// 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 MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Extensions.ManagedClient;
|
using MQTTnet.Extensions.ManagedClient;
|
||||||
|
|
||||||
|
|||||||
+10
-3
@@ -8,6 +8,7 @@ Console.WriteLine("Welcome to MQTTnet samples!");
|
|||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
|
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
|
||||||
|
sampleClasses.RemoveAll(c => c.Namespace.Contains(".Shared"));
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var sampleClass in sampleClasses)
|
foreach (var sampleClass in sampleClasses)
|
||||||
@@ -39,10 +40,16 @@ Console.WriteLine();
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var task = selectedSampleMethod.Invoke(null, null) as Task;
|
if (selectedSampleMethod.Invoke(null, null) is Task task)
|
||||||
task?.Wait();
|
{
|
||||||
|
await task;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
Console.WriteLine(exception.ToString());
|
Console.WriteLine(exception.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("Press Enter to exit.");
|
||||||
|
Console.ReadLine();
|
||||||
@@ -2,13 +2,17 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Extensions.Rpc;
|
using MQTTnet.Extensions.Rpc;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.RpcClient;
|
namespace MQTTnet.Samples.RpcClient;
|
||||||
|
|
||||||
public static class RcpClient_Samples
|
public static class RpcClient_Samples
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* The extension MQTTnet.Extensions.Rpc (available as nuget) allows sending a request and waiting for the matching reply.
|
* The extension MQTTnet.Extensions.Rpc (available as nuget) allows sending a request and waiting for the matching reply.
|
||||||
@@ -33,7 +37,7 @@ public static class RcpClient_Samples
|
|||||||
|
|
||||||
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
|
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
|
||||||
{
|
{
|
||||||
// Access to a fully featured application message is not supported for RCP calls!
|
// Access to a fully featured application message is not supported for RPC calls!
|
||||||
// The method will throw an exception when the response was not received in time.
|
// The method will throw an exception when the response was not received in time.
|
||||||
await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
}
|
}
|
||||||
@@ -81,4 +85,4 @@ public static class RcpClient_Samples
|
|||||||
// ! Do not send response message with the _retain_ flag set to true.
|
// ! Do not send response message with the _retain_ flag set to true.
|
||||||
// ! All required data for a RPC call and the result must be placed into the payload.
|
// ! All required data for a RPC call and the result must be placed into the payload.
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
// ReSharper disable EmptyConstructor
|
||||||
|
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using MQTTnet.AspNetCore;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
|
public static class Server_Asp_Net_Samples
|
||||||
|
{
|
||||||
|
public static Task Start_Server_With_WebSockets_Support()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample starts a minimal ASP.NET Webserver including a hosted MQTT server.
|
||||||
|
*/
|
||||||
|
var host = Host.CreateDefaultBuilder(Array.Empty<string>())
|
||||||
|
.ConfigureWebHostDefaults(
|
||||||
|
webBuilder =>
|
||||||
|
{
|
||||||
|
webBuilder.UseKestrel(
|
||||||
|
o =>
|
||||||
|
{
|
||||||
|
// This will allow MQTT connections based on TCP port 1883.
|
||||||
|
o.ListenAnyIP(1883, l => l.UseMqtt());
|
||||||
|
|
||||||
|
// This will allow MQTT connections based on HTTP WebSockets with URI "localhost:5000/mqtt"
|
||||||
|
// See code below for URI configuration.
|
||||||
|
o.ListenAnyIP(5000); // Default HTTP pipeline
|
||||||
|
});
|
||||||
|
|
||||||
|
webBuilder.UseStartup<Startup>();
|
||||||
|
});
|
||||||
|
|
||||||
|
return host.RunConsoleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class MqttController
|
||||||
|
{
|
||||||
|
public MqttController()
|
||||||
|
{
|
||||||
|
// Inject other services via constructor.
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task OnClientConnected(ClientConnectedEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Client '{eventArgs.ClientId}' connected.");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Startup
|
||||||
|
{
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController)
|
||||||
|
{
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseEndpoints(
|
||||||
|
endpoints =>
|
||||||
|
{
|
||||||
|
endpoints.MapConnectionHandler<MqttConnectionHandler>(
|
||||||
|
"/mqtt",
|
||||||
|
httpConnectionDispatcherOptions => httpConnectionDispatcherOptions.WebSockets.SubProtocolSelector =
|
||||||
|
protocolList => protocolList.FirstOrDefault() ?? string.Empty);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseMqttServer(
|
||||||
|
server =>
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Attach event handlers etc. if required.
|
||||||
|
*/
|
||||||
|
|
||||||
|
server.ValidatingConnectionAsync += mqttController.ValidateConnection;
|
||||||
|
server.ClientConnectedAsync += mqttController.OnClientConnected;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddHostedMqttServer(
|
||||||
|
optionsBuilder =>
|
||||||
|
{
|
||||||
|
optionsBuilder.WithDefaultEndpoint();
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddMqttConnectionHandler();
|
||||||
|
services.AddConnections();
|
||||||
|
|
||||||
|
services.AddSingleton<MqttController>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// 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.Text.Json;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
|
public static class Server_Retained_Messages_Samples
|
||||||
|
{
|
||||||
|
public static async Task Persist_Retained_Messages()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample starts a simple MQTT server which will store all retained messages in a file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var storePath = Path.Combine(Path.GetTempPath(), "RetainedMessages.json");
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
|
// Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
|
||||||
|
var mqttServerOptions = mqttFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
|
||||||
|
using (var server = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||||
|
{
|
||||||
|
// Make sure that the server will load the retained messages.
|
||||||
|
server.LoadingRetainedMessageAsync += async eventArgs =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
|
||||||
|
Console.WriteLine("Retained messages loaded.");
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException)
|
||||||
|
{
|
||||||
|
// Ignore because nothing is stored yet.
|
||||||
|
Console.WriteLine("No retained messages stored yet.");
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure to persist the changed retained messages.
|
||||||
|
server.RetainedMessageChangedAsync += async eventArgs =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages.
|
||||||
|
// The event args also contain the affected retained message (property ChangedRetainedMessage). This can be
|
||||||
|
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
|
||||||
|
// of retained messages must be provided in the loaded event.
|
||||||
|
|
||||||
|
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
|
||||||
|
await File.WriteAllBytesAsync(storePath, buffer);
|
||||||
|
Console.WriteLine("Retained messages saved.");
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure to clear the retained messages when they are all deleted via API.
|
||||||
|
server.RetainedMessagesClearedAsync += _ =>
|
||||||
|
{
|
||||||
|
File.Delete(storePath);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await server.StartAsync();
|
||||||
|
|
||||||
|
Console.WriteLine("Press Enter to exit.");
|
||||||
|
Console.ReadLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,34 +2,107 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Samples.Shared;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Server;
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
public static class Server_Simple_Samples
|
public static class Server_Simple_Samples
|
||||||
{
|
{
|
||||||
|
public static async Task Force_Disconnecting_Client()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample will disconnect a client.
|
||||||
|
*
|
||||||
|
* See _Run_Minimal_Server_ for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using (var mqttServer = await StartMqttServer())
|
||||||
|
{
|
||||||
|
// Let the client connect.
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
// Now disconnect the client (if connected).
|
||||||
|
var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient");
|
||||||
|
if (affectedClient != null)
|
||||||
|
{
|
||||||
|
await affectedClient.DisconnectAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Publish_Message_From_Broker()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample will publish a message directly at the broker.
|
||||||
|
*
|
||||||
|
* See _Run_Minimal_Server_ for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using (var mqttServer = await StartMqttServer())
|
||||||
|
{
|
||||||
|
// Create a new message using the builder as usual.
|
||||||
|
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
|
||||||
|
|
||||||
|
// Now inject the new message at the broker.
|
||||||
|
await mqttServer.InjectApplicationMessage(
|
||||||
|
new InjectedMqttApplicationMessage(message)
|
||||||
|
{
|
||||||
|
SenderClientId = "SenderClientId"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task Run_Minimal_Server()
|
public static async Task Run_Minimal_Server()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This sample starts a simple MQTT server which will accept any TCP connection.
|
* This sample starts a simple MQTT server which will accept any TCP connection.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
// The port for the default endpoint is 1883.
|
// The port for the default endpoint is 1883.
|
||||||
// The default endpoint is NOT encrypted!
|
// The default endpoint is NOT encrypted!
|
||||||
// Use the builder classes where possible.
|
// Use the builder classes where possible.
|
||||||
var mqttServerOptions = new MqttServerOptionsBuilder()
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
.WithDefaultEndpoint()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
// The port can be changed using the following API (not used in this example).
|
// The port can be changed using the following API (not used in this example).
|
||||||
new MqttServerOptionsBuilder()
|
// new MqttServerOptionsBuilder()
|
||||||
.WithDefaultEndpoint()
|
// .WithDefaultEndpoint()
|
||||||
.WithDefaultEndpointPort(1234)
|
// .WithDefaultEndpointPort(1234)
|
||||||
.Build();
|
// .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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Run_Server_With_Logging()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample starts a simple MQTT server and prints the logs to the output.
|
||||||
|
*
|
||||||
|
* IMPORTANT! Do not enable logging in live environment. It will decrease performance.
|
||||||
|
*
|
||||||
|
* See sample "Run_Minimal_Server" for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory(new ConsoleLogger());
|
||||||
|
|
||||||
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
|
||||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||||
{
|
{
|
||||||
await mqttServer.StartAsync();
|
await mqttServer.StartAsync();
|
||||||
@@ -52,9 +125,7 @@ public static class Server_Simple_Samples
|
|||||||
|
|
||||||
var mqttFactory = new MqttFactory();
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
var mqttServerOptions = new MqttServerOptionsBuilder()
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
.WithDefaultEndpoint()
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||||
{
|
{
|
||||||
@@ -67,7 +138,7 @@ public static class Server_Simple_Samples
|
|||||||
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.Username != "ValidUser")
|
if (e.UserName != "ValidUser")
|
||||||
{
|
{
|
||||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||||
}
|
}
|
||||||
@@ -88,4 +159,15 @@ public static class Server_Simple_Samples
|
|||||||
await mqttServer.StopAsync();
|
await mqttServer.StopAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
static async Task<MqttServer> StartMqttServer()
|
||||||
|
{
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
|
||||||
|
// Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
|
||||||
|
var mqttServerOptions = mqttFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
var server = mqttFactory.CreateMqttServer(mqttServerOptions);
|
||||||
|
await server.StartAsync();
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Shared;
|
||||||
|
|
||||||
|
public sealed class ConsoleLogger : IMqttNetLogger
|
||||||
|
{
|
||||||
|
readonly object _consoleSyncRoot = new();
|
||||||
|
|
||||||
|
public bool IsEnabled => true;
|
||||||
|
|
||||||
|
public void Publish(MqttNetLogLevel logLevel, string source, string message, object[]? parameters, Exception? exception)
|
||||||
|
{
|
||||||
|
var foregroundColor = ConsoleColor.White;
|
||||||
|
switch (logLevel)
|
||||||
|
{
|
||||||
|
case MqttNetLogLevel.Verbose:
|
||||||
|
foregroundColor = ConsoleColor.White;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MqttNetLogLevel.Info:
|
||||||
|
foregroundColor = ConsoleColor.Green;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MqttNetLogLevel.Warning:
|
||||||
|
foregroundColor = ConsoleColor.DarkYellow;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MqttNetLogLevel.Error:
|
||||||
|
foregroundColor = ConsoleColor.Red;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters?.Length > 0)
|
||||||
|
{
|
||||||
|
message = string.Format(message, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_consoleSyncRoot)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = foregroundColor;
|
||||||
|
Console.WriteLine(message);
|
||||||
|
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Helpers;
|
namespace MQTTnet.Samples.Shared;
|
||||||
|
|
||||||
internal static class ObjectExtensions
|
internal static class ObjectExtensions
|
||||||
{
|
{
|
||||||
@@ -9,9 +9,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<Copyright>Christian Kratky 2016-2022</Copyright>
|
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<PackageIcon>nuget.png</PackageIcon>
|
<PackageIcon>nuget.png</PackageIcon>
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
@@ -41,16 +41,9 @@
|
|||||||
<PackagePath>\</PackagePath>
|
<PackagePath>\</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\..\LICENSE">
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||||
<Pack>True</Pack>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
_stream.Position = 0;
|
_stream.Position = 0;
|
||||||
|
|
||||||
var channel = new TestMqttChannel(_stream);
|
var channel = new MemoryMqttChannel(_stream);
|
||||||
|
|
||||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using MQTTnet.Diagnostics;
|
|||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter]
|
[RPlotExporter]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class LoggerBenchmark
|
public class LoggerBenchmark
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ using BenchmarkDotNet.Attributes;
|
|||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using MqttClient = MQTTnet.Client.MqttClient;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MessageProcessingBenchmark
|
public class MessageProcessingBenchmark
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using MQTTnet.Diagnostics;
|
|||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MessageProcessingMqttConnectionContextBenchmark
|
public class MessageProcessingMqttConnectionContextBenchmark
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using MQTTnet.Tests.Mockups;
|
|||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MqttPacketReaderWriterBenchmark
|
public class MqttPacketReaderWriterBenchmark
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ using MQTTnet.Diagnostics;
|
|||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public sealed class MqttTcpChannelBenchmark
|
public sealed class MqttTcpChannelBenchmark
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using MQTTnet.Tests.Server;
|
|||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class RoundtripProcessingBenchmark
|
public class RoundtripProcessingBenchmark
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ using MQTTnet.Diagnostics;
|
|||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter]
|
[RPlotExporter]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class SerializerBenchmark : BaseBenchmark
|
public class SerializerBenchmark : BaseBenchmark
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using MQTTnet.Tests.Server;
|
|||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class ServerProcessingBenchmark
|
public class ServerProcessingBenchmark
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using MQTTnet.AspNetCore.Client.Tcp;
|
|||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class TcpPipesBenchmark
|
public class TcpPipesBenchmark
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using BenchmarkDotNet.Jobs;
|
|||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter]
|
[RPlotExporter]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class TopicFilterComparerBenchmark
|
public class TopicFilterComparerBenchmark
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync;
|
event Func<ApplicationMessageSkippedEventArgs, Task> ApplicationMessageSkippedAsync;
|
||||||
|
|
||||||
event Func<EventArgs, Task> ConnectedAsync;
|
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
|
||||||
|
|
||||||
event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync;
|
event Func<ConnectingFailedEventArgs, Task> ConnectingFailedAsync;
|
||||||
|
|
||||||
event Func<EventArgs, Task> ConnectionStateChangedAsync;
|
event Func<EventArgs, Task> ConnectionStateChangedAsync;
|
||||||
|
|
||||||
event Func<EventArgs, Task> DisconnectedAsync;
|
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
||||||
|
|
||||||
event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync;
|
event Func<ManagedProcessFailedEventArgs, Task> SynchronizingSubscriptionsFailedAsync;
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<Copyright>Christian Kratky 2016-2022</Copyright>
|
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
<PackageIcon>nuget.png</PackageIcon>
|
<PackageIcon>nuget.png</PackageIcon>
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
@@ -55,16 +55,9 @@
|
|||||||
<PackagePath>\</PackagePath>
|
<PackagePath>\</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\..\LICENSE">
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||||
<Pack>True</Pack>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
/// cref="MaintainConnectionAsync" />
|
/// cref="MaintainConnectionAsync" />
|
||||||
/// .
|
/// .
|
||||||
/// </summary>
|
/// </summary>
|
||||||
readonly Dictionary<string, MqttQualityOfServiceLevel> _reconnectSubscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
readonly Dictionary<string, MqttTopicFilter> _reconnectSubscriptions = new Dictionary<string, MqttTopicFilter>();
|
||||||
|
|
||||||
readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
readonly Dictionary<string, MqttTopicFilter> _subscriptions = new Dictionary<string, MqttTopicFilter>();
|
||||||
readonly SemaphoreSlim _subscriptionsQueuedSignal = new SemaphoreSlim(0);
|
readonly SemaphoreSlim _subscriptionsQueuedSignal = new SemaphoreSlim(0);
|
||||||
readonly HashSet<string> _unsubscriptions = new HashSet<string>();
|
readonly HashSet<string> _unsubscriptions = new HashSet<string>();
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
remove => InternalClient.ApplicationMessageReceivedAsync -= value;
|
remove => InternalClient.ApplicationMessageReceivedAsync -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Func<EventArgs, Task> ConnectedAsync
|
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
|
||||||
{
|
{
|
||||||
add => InternalClient.ConnectedAsync += value;
|
add => InternalClient.ConnectedAsync += value;
|
||||||
remove => InternalClient.ConnectedAsync -= value;
|
remove => InternalClient.ConnectedAsync -= value;
|
||||||
@@ -111,7 +111,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
remove => _connectionStateChangedEvent.RemoveHandler(value);
|
remove => _connectionStateChangedEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Func<EventArgs, Task> DisconnectedAsync
|
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
|
||||||
{
|
{
|
||||||
add => InternalClient.DisconnectedAsync += value;
|
add => InternalClient.DisconnectedAsync += value;
|
||||||
remove => InternalClient.DisconnectedAsync -= value;
|
remove => InternalClient.DisconnectedAsync -= value;
|
||||||
@@ -201,7 +201,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (_applicationMessageSkippedEvent.HasHandlers)
|
if (applicationMessageSkippedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
|
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -289,7 +289,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
{
|
{
|
||||||
foreach (var topicFilter in topicFilters)
|
foreach (var topicFilter in topicFilters)
|
||||||
{
|
{
|
||||||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
|
_subscriptions[topicFilter.Topic] = topicFilter;
|
||||||
_unsubscriptions.Remove(topicFilter.Topic);
|
_unsubscriptions.Remove(topicFilter.Topic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -455,18 +455,11 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
{
|
{
|
||||||
if (_reconnectSubscriptions.Any())
|
if (_reconnectSubscriptions.Any())
|
||||||
{
|
{
|
||||||
var subscriptions = _reconnectSubscriptions.Select(
|
|
||||||
i => new MqttTopicFilter
|
|
||||||
{
|
|
||||||
Topic = i.Key,
|
|
||||||
QualityOfServiceLevel = i.Value
|
|
||||||
});
|
|
||||||
|
|
||||||
var topicFilters = new List<MqttTopicFilter>();
|
var topicFilters = new List<MqttTopicFilter>();
|
||||||
|
|
||||||
foreach (var sub in subscriptions)
|
foreach (var sub in _reconnectSubscriptions)
|
||||||
{
|
{
|
||||||
topicFilters.Add(sub);
|
topicFilters.Add(sub.Value);
|
||||||
|
|
||||||
if (topicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
|
if (topicFilters.Count == Options.MaxTopicFiltersInSubscribeUnsubscribePackets)
|
||||||
{
|
{
|
||||||
@@ -495,14 +488,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
lock (_subscriptions)
|
lock (_subscriptions)
|
||||||
{
|
{
|
||||||
subscriptions = _subscriptions.Select(
|
subscriptions = _subscriptions.Values.ToList();
|
||||||
i => new MqttTopicFilter
|
|
||||||
{
|
|
||||||
Topic = i.Key,
|
|
||||||
QualityOfServiceLevel = i.Value
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
_subscriptions.Clear();
|
_subscriptions.Clear();
|
||||||
|
|
||||||
unsubscriptions = new HashSet<string>(_unsubscriptions);
|
unsubscriptions = new HashSet<string>(_unsubscriptions);
|
||||||
@@ -523,7 +509,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
foreach (var subscription in subscriptions)
|
foreach (var subscription in subscriptions)
|
||||||
{
|
{
|
||||||
_reconnectSubscriptions[subscription.Topic] = subscription.QualityOfServiceLevel;
|
_reconnectSubscriptions[subscription.Topic] = subscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
var addedTopicFilters = new List<MqttTopicFilter>();
|
var addedTopicFilters = new List<MqttTopicFilter>();
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<Copyright>Christian Kratky 2016-2022</Copyright>
|
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
<PackageIcon>nuget.png</PackageIcon>
|
<PackageIcon>nuget.png</PackageIcon>
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
@@ -55,16 +55,9 @@
|
|||||||
<PackagePath>\</PackagePath>
|
<PackagePath>\</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\..\LICENSE">
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||||
<Pack>True</Pack>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<Copyright>Christian Kratky 2016-2022</Copyright>
|
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
<PackageIcon>nuget.png</PackageIcon>
|
<PackageIcon>nuget.png</PackageIcon>
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
@@ -55,24 +55,17 @@
|
|||||||
<PackagePath>\</PackagePath>
|
<PackagePath>\</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\..\LICENSE">
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||||
<Pack>True</Pack>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
|
<PackageReference Include="WebSocket4Net" Version="0.15.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="WebSocket4Net" Version="0.15.2"/>
|
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
#else
|
#else
|
||||||
certificates.Add(certificate);
|
certificates.Add(certificate);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -23,10 +23,4 @@
|
|||||||
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="Start.bat">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
if (e.ClientId == "SpecialClient")
|
if (e.ClientId == "SpecialClient")
|
||||||
{
|
{
|
||||||
if (e.Username != "USER" || e.Password != "PASS")
|
if (e.UserName != "USER" || e.Password != "PASS")
|
||||||
{
|
{
|
||||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
dotnet MQTTnet.TestApp.NetCore.dll
|
|
||||||
@@ -563,7 +563,7 @@ namespace MQTTnet.Tests
|
|||||||
|
|
||||||
using (var headerStream = new MemoryStream(buffer1.Join().ToArray()))
|
using (var headerStream = new MemoryStream(buffer1.Join().ToArray()))
|
||||||
{
|
{
|
||||||
using (var channel = new TestMqttChannel(headerStream))
|
using (var channel = new MemoryMqttChannel(headerStream))
|
||||||
{
|
{
|
||||||
using (var adapter = new MqttChannelAdapter(
|
using (var adapter = new MqttChannelAdapter(
|
||||||
channel,
|
channel,
|
||||||
@@ -583,7 +583,7 @@ namespace MQTTnet.Tests
|
|||||||
|
|
||||||
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
|
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
|
||||||
{
|
{
|
||||||
var channel = new TestMqttChannel(buffer);
|
var channel = new MemoryMqttChannel(buffer);
|
||||||
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, null, new MqttNetEventLogger());
|
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, null, new MqttNetEventLogger());
|
||||||
|
|
||||||
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
@@ -603,7 +603,7 @@ namespace MQTTnet.Tests
|
|||||||
var serializer = MqttPacketFormatterAdapter.GetMqttPacketFormatter(protocolVersion, writer);
|
var serializer = MqttPacketFormatterAdapter.GetMqttPacketFormatter(protocolVersion, writer);
|
||||||
var buffer = serializer.Encode(packet);
|
var buffer = serializer.Encode(packet);
|
||||||
|
|
||||||
using (var channel = new TestMqttChannel(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)), null, new MqttNetEventLogger());
|
||||||
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Internal
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class AsyncEvent_Tests
|
||||||
|
{
|
||||||
|
int _testEventAsyncCount;
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Attach_EventHandler()
|
||||||
|
{
|
||||||
|
var testClass = new TestClass();
|
||||||
|
|
||||||
|
testClass.TestEventAsync += OnTestEventAsync;
|
||||||
|
|
||||||
|
await testClass.FireEventAsync(EventArgs.Empty);
|
||||||
|
|
||||||
|
Assert.AreEqual(1, _testEventAsyncCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Detach_EventHandler()
|
||||||
|
{
|
||||||
|
var testClass = new TestClass();
|
||||||
|
|
||||||
|
testClass.TestEventAsync += OnTestEventAsync;
|
||||||
|
testClass.TestEventAsync -= OnTestEventAsync;
|
||||||
|
|
||||||
|
await testClass.FireEventAsync(EventArgs.Empty);
|
||||||
|
|
||||||
|
Assert.AreEqual(0, _testEventAsyncCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task OnTestEventAsync(EventArgs arg)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref _testEventAsyncCount);
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class TestClass
|
||||||
|
{
|
||||||
|
readonly AsyncEvent<EventArgs> _testEvent = new AsyncEvent<EventArgs>();
|
||||||
|
|
||||||
|
public event Func<EventArgs, Task> TestEventAsync
|
||||||
|
{
|
||||||
|
add => _testEvent.AddHandler(value);
|
||||||
|
remove => _testEvent.RemoveHandler(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task FireEventAsync(EventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return _testEvent.InvokeAsync(eventArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,9 +10,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ namespace MQTTnet.Tests.MQTTv5
|
|||||||
|
|
||||||
var client1 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client1"));
|
var client1 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client1"));
|
||||||
|
|
||||||
var testMessageHandler = new TestApplicationMessageReceivedHandler(client1);
|
var testMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1);
|
||||||
|
|
||||||
await client1.SubscribeAsync("a");
|
await client1.SubscribeAsync("a");
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -10,16 +10,16 @@ using MQTTnet.Channel;
|
|||||||
|
|
||||||
namespace MQTTnet.Tests.Mockups
|
namespace MQTTnet.Tests.Mockups
|
||||||
{
|
{
|
||||||
public sealed class TestMqttChannel : IMqttChannel
|
public sealed class MemoryMqttChannel : IMqttChannel
|
||||||
{
|
{
|
||||||
readonly MemoryStream _stream;
|
readonly MemoryStream _stream;
|
||||||
|
|
||||||
public TestMqttChannel(MemoryStream stream)
|
public MemoryMqttChannel(MemoryStream stream)
|
||||||
{
|
{
|
||||||
_stream = stream;
|
_stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestMqttChannel(byte[] buffer)
|
public MemoryMqttChannel(byte[] buffer)
|
||||||
{
|
{
|
||||||
_stream = new MemoryStream(buffer);
|
_stream = new MemoryStream(buffer);
|
||||||
}
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Mockups
|
|
||||||
{
|
|
||||||
public sealed class MqttPacketAsserts
|
|
||||||
{
|
|
||||||
public void AssertIsConnectPacket(MqttPacket packet)
|
|
||||||
{
|
|
||||||
Assert.AreEqual(packet.GetType(), typeof(MqttConnectPacket));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AssertIsConnAckPacket(MqttPacket packet)
|
|
||||||
{
|
|
||||||
Assert.AreEqual(packet.GetType(), typeof(MqttConnAckPacket));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,7 @@ namespace MQTTnet.Tests
|
|||||||
|
|
||||||
public MqttPacket Decode(MqttPacketBuffer buffer)
|
public MqttPacket Decode(MqttPacketBuffer buffer)
|
||||||
{
|
{
|
||||||
using (var channel = new TestMqttChannel(buffer.ToArray()))
|
using (var channel = new MemoryMqttChannel(buffer.ToArray()))
|
||||||
{
|
{
|
||||||
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
|
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
|
||||||
|
|
||||||
|
|||||||
@@ -798,38 +798,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.IsTrue(longBody.SequenceEqual(receivedBody ?? new byte[0]));
|
Assert.IsTrue(longBody.SequenceEqual(receivedBody ?? new byte[0]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Session_Takeover()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var options = new MqttClientOptionsBuilder().WithCleanSession(false)
|
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V500) // Disconnect reason is only available in MQTT 5+
|
|
||||||
.WithClientId("a");
|
|
||||||
|
|
||||||
var client1 = await testEnvironment.ConnectClient(options);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
var disconnectReason = MqttClientDisconnectReason.NormalDisconnection;
|
|
||||||
client1.DisconnectedAsync += c =>
|
|
||||||
{
|
|
||||||
disconnectReason = c.Reason;
|
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
|
||||||
};
|
|
||||||
|
|
||||||
var client2 = await testEnvironment.ConnectClient(options);
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
Assert.IsFalse(client1.IsConnected);
|
|
||||||
Assert.IsTrue(client2.IsConnected);
|
|
||||||
|
|
||||||
Assert.AreEqual(MqttClientDisconnectReason.SessionTakenOver, disconnectReason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Set_Subscription_At_Server()
|
public async Task Set_Subscription_At_Server()
|
||||||
{
|
{
|
||||||
@@ -944,26 +913,6 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Use_Clean_Session()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var client = testEnvironment.CreateClient();
|
|
||||||
var connectResult = await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).WithCleanSession().Build());
|
|
||||||
|
|
||||||
// Create the session including the subscription.
|
|
||||||
var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a").WithCleanSession(false));
|
|
||||||
await client1.SubscribeAsync("x");
|
|
||||||
await client1.DisconnectAsync();
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
Assert.IsFalse(connectResult.IsSessionPresent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Use_Empty_Client_ID()
|
public async Task Use_Empty_Client_ID()
|
||||||
{
|
{
|
||||||
@@ -986,98 +935,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.IsTrue(client.IsConnected);
|
Assert.IsTrue(client.IsConnected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
var receivedMessagesCount = 0;
|
|
||||||
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
|
|
||||||
|
|
||||||
var c1 = await testEnvironment.ConnectClient();
|
|
||||||
|
|
||||||
c1.ApplicationMessageReceivedAsync += e =>
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref receivedMessagesCount);
|
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
|
||||||
};
|
|
||||||
|
|
||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
|
||||||
|
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
|
||||||
await c2.DisconnectAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
Assert.AreEqual(0, receivedMessagesCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Will_Message_Do_Not_Send_On_Takeover()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
var receivedMessagesCount = 0;
|
|
||||||
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
// C1 will receive the last will!
|
|
||||||
var c1 = await testEnvironment.ConnectClient();
|
|
||||||
c1.ApplicationMessageReceivedAsync += e =>
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref receivedMessagesCount);
|
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
|
||||||
};
|
|
||||||
|
|
||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
|
||||||
|
|
||||||
// C2 has the last will defined.
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner");
|
|
||||||
|
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
|
||||||
|
|
||||||
// C3 will do the connection takeover.
|
|
||||||
var c3 = await testEnvironment.ConnectClient(clientOptions);
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
Assert.AreEqual(0, receivedMessagesCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Will_Message_Send()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
|
|
||||||
|
|
||||||
var receivedMessagesCount = 0;
|
|
||||||
c1.ApplicationMessageReceivedAsync += e =>
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref receivedMessagesCount);
|
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
|
||||||
};
|
|
||||||
|
|
||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
|
||||||
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
Assert.AreEqual(1, receivedMessagesCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConnectionValidationHandler(ValidatingConnectionEventArgs eventArgs)
|
void ConnectionValidationHandler(ValidatingConnectionEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (_connected.ContainsKey(eventArgs.ClientId))
|
if (_connected.ContainsKey(eventArgs.ClientId))
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// 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 System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class QoS_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Fire_Event_On_Client_Acknowledges_QoS_0()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
ClientAcknowledgedPublishPacketEventArgs eventArgs = null;
|
||||||
|
server.ClientAcknowledgedPublishPacketAsync += args =>
|
||||||
|
{
|
||||||
|
eventArgs = args;
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client1 = await testEnvironment.ConnectClient();
|
||||||
|
await client1.SubscribeAsync("A");
|
||||||
|
|
||||||
|
var client2 = await testEnvironment.ConnectClient();
|
||||||
|
await client2.PublishStringAsync("A");
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Must be null because no event should be fired for QoS 0.
|
||||||
|
Assert.IsNull(eventArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Fire_Event_On_Client_Acknowledges_QoS_1()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
ClientAcknowledgedPublishPacketEventArgs eventArgs = null;
|
||||||
|
server.ClientAcknowledgedPublishPacketAsync += args =>
|
||||||
|
{
|
||||||
|
eventArgs = args;
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client1 = await testEnvironment.ConnectClient();
|
||||||
|
await client1.SubscribeAsync("A", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
var client2 = await testEnvironment.ConnectClient();
|
||||||
|
await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
Assert.IsNotNull(eventArgs);
|
||||||
|
Assert.IsNotNull(eventArgs.PublishPacket);
|
||||||
|
Assert.IsNotNull(eventArgs.AcknowledgePacket);
|
||||||
|
Assert.IsTrue(eventArgs.IsCompleted);
|
||||||
|
|
||||||
|
Assert.AreEqual("A", eventArgs.PublishPacket.Topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Fire_Event_On_Client_Acknowledges_QoS_2()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var eventArgs = new List<ClientAcknowledgedPublishPacketEventArgs>();
|
||||||
|
server.ClientAcknowledgedPublishPacketAsync += args =>
|
||||||
|
{
|
||||||
|
lock (eventArgs)
|
||||||
|
{
|
||||||
|
eventArgs.Add(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client1 = await testEnvironment.ConnectClient();
|
||||||
|
await client1.SubscribeAsync("A", MqttQualityOfServiceLevel.ExactlyOnce);
|
||||||
|
|
||||||
|
var client2 = await testEnvironment.ConnectClient();
|
||||||
|
await client2.PublishStringAsync("A", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
Assert.AreEqual(2, eventArgs.Count);
|
||||||
|
|
||||||
|
var firstEvent = eventArgs[0];
|
||||||
|
|
||||||
|
Assert.IsNotNull(firstEvent);
|
||||||
|
Assert.IsNotNull(firstEvent.PublishPacket);
|
||||||
|
Assert.IsNotNull(firstEvent.AcknowledgePacket);
|
||||||
|
Assert.IsFalse(firstEvent.IsCompleted);
|
||||||
|
|
||||||
|
Assert.AreEqual("A", firstEvent.PublishPacket.Topic);
|
||||||
|
|
||||||
|
var secondEvent = eventArgs[1];
|
||||||
|
|
||||||
|
Assert.IsNotNull(secondEvent);
|
||||||
|
Assert.IsNotNull(secondEvent.PublishPacket);
|
||||||
|
Assert.IsNotNull(secondEvent.AcknowledgePacket);
|
||||||
|
Assert.IsTrue(secondEvent.IsCompleted);
|
||||||
|
|
||||||
|
Assert.AreEqual("A", secondEvent.PublishPacket.Topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,67 +16,52 @@ namespace MQTTnet.Tests.Server
|
|||||||
public sealed class Retained_Messages_Tests : BaseTestClass
|
public sealed class Retained_Messages_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Server_Reports_Retained_Messages_Supported_V3()
|
public async Task Clear_Retained_Message_With_Empty_Payload()
|
||||||
{
|
{
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
{
|
{
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
var client = testEnvironment.CreateClient();
|
|
||||||
|
|
||||||
var connectResult = await client.ConnectAsync(testEnvironment.Factory.CreateClientOptionsBuilder()
|
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311)
|
|
||||||
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
|
|
||||||
|
|
||||||
Assert.IsTrue(connectResult.RetainAvailable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Server_Reports_Retained_Messages_Supported_V5()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var client = testEnvironment.CreateClient();
|
|
||||||
var connectResult = await client.ConnectAsync(testEnvironment.Factory.CreateClientOptionsBuilder()
|
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V500)
|
|
||||||
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
|
|
||||||
|
|
||||||
Assert.IsTrue(connectResult.RetainAvailable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Retained_Messages_Flow()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build();
|
|
||||||
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
var c1 = await testEnvironment.ConnectClient();
|
var c1 = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
|
||||||
|
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build());
|
||||||
|
|
||||||
|
await c1.DisconnectAsync();
|
||||||
|
|
||||||
var c2 = await testEnvironment.ConnectClient();
|
var c2 = await testEnvironment.ConnectClient();
|
||||||
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
|
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
|
||||||
|
|
||||||
await c1.PublishAsync(retainedMessage);
|
await Task.Delay(200);
|
||||||
|
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce });
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
messageHandler.AssertReceivedCountEquals(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Clear_Retained_Message_With_Null_Payload()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var c1 = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
|
||||||
|
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload((byte[])null).WithRetainFlag().Build());
|
||||||
|
|
||||||
await c1.DisconnectAsync();
|
await c1.DisconnectAsync();
|
||||||
await LongTestDelay();
|
|
||||||
|
|
||||||
for (var i = 0; i < 5; i++)
|
var c2 = await testEnvironment.ConnectClient();
|
||||||
{
|
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
|
||||||
await c2.UnsubscribeAsync("r");
|
|
||||||
await Task.Delay(100);
|
|
||||||
messageHandler.AssertReceivedCountEquals(i);
|
|
||||||
|
|
||||||
await c2.SubscribeAsync("r");
|
|
||||||
await Task.Delay(100);
|
|
||||||
messageHandler.AssertReceivedCountEquals(i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
await c2.DisconnectAsync();
|
await Task.Delay(200);
|
||||||
|
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce });
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
messageHandler.AssertReceivedCountEquals(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +99,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
var c2 = await testEnvironment.ConnectClient();
|
var c2 = await testEnvironment.ConnectClient();
|
||||||
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
|
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
|
||||||
|
|
||||||
await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained").Build());
|
await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained").Build());
|
||||||
|
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
@@ -125,52 +110,102 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Clear_Retained_Message_With_Empty_Payload()
|
public async Task Receive_Retained_Messages_From_Higher_Qos_Level()
|
||||||
{
|
{
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
{
|
{
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
// Upload retained message.
|
||||||
var c1 = await testEnvironment.ConnectClient();
|
var c1 = await testEnvironment.ConnectClient();
|
||||||
|
await c1.PublishAsync(
|
||||||
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
|
new MqttApplicationMessageBuilder().WithTopic("retained")
|
||||||
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build());
|
.WithPayload(new byte[1])
|
||||||
|
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
||||||
|
.WithRetainFlag()
|
||||||
|
.Build());
|
||||||
|
|
||||||
await c1.DisconnectAsync();
|
await c1.DisconnectAsync();
|
||||||
|
|
||||||
|
// Subscribe using a new client.
|
||||||
var c2 = await testEnvironment.ConnectClient();
|
var c2 = await testEnvironment.ConnectClient();
|
||||||
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
|
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
|
||||||
|
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce });
|
// Using QoS 2 will lead to 1 instead because the publish was made with QoS level 1 (see 3.8.4 SUBSCRIBE Actions)!
|
||||||
|
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce });
|
||||||
await Task.Delay(500);
|
await Task.Delay(500);
|
||||||
|
|
||||||
messageHandler.AssertReceivedCountEquals(0);
|
messageHandler.AssertReceivedCountEquals(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Clear_Retained_Message_With_Null_Payload()
|
public async Task Retained_Messages_Flow()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build();
|
||||||
|
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
var c1 = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
var c2 = await testEnvironment.ConnectClient();
|
||||||
|
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
|
||||||
|
|
||||||
|
await c1.PublishAsync(retainedMessage);
|
||||||
|
await c1.DisconnectAsync();
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
for (var i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
await c2.UnsubscribeAsync("r");
|
||||||
|
await Task.Delay(100);
|
||||||
|
messageHandler.AssertReceivedCountEquals(i);
|
||||||
|
|
||||||
|
await c2.SubscribeAsync("r");
|
||||||
|
await Task.Delay(100);
|
||||||
|
messageHandler.AssertReceivedCountEquals(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await c2.DisconnectAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Server_Reports_Retained_Messages_Supported_V3()
|
||||||
{
|
{
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
{
|
{
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
var c1 = await testEnvironment.ConnectClient();
|
var client = testEnvironment.CreateClient();
|
||||||
|
|
||||||
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
|
var connectResult = await client.ConnectAsync(
|
||||||
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload((byte[])null).WithRetainFlag().Build());
|
testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||||
|
.WithProtocolVersion(MqttProtocolVersion.V311)
|
||||||
|
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||||
|
.Build());
|
||||||
|
|
||||||
await c1.DisconnectAsync();
|
Assert.IsTrue(connectResult.RetainAvailable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var c2 = await testEnvironment.ConnectClient();
|
[TestMethod]
|
||||||
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
|
public async Task Server_Reports_Retained_Messages_Supported_V5()
|
||||||
|
{
|
||||||
await Task.Delay(200);
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce });
|
{
|
||||||
await Task.Delay(500);
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
messageHandler.AssertReceivedCountEquals(0);
|
var client = testEnvironment.CreateClient();
|
||||||
|
var connectResult = await client.ConnectAsync(
|
||||||
|
testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||||
|
.WithProtocolVersion(MqttProtocolVersion.V500)
|
||||||
|
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||||
|
.Build());
|
||||||
|
|
||||||
|
Assert.IsTrue(connectResult.RetainAvailable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
@@ -15,6 +16,104 @@ namespace MQTTnet.Tests.Server
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class Security_Tests : BaseTestClass
|
public sealed class Security_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Do_Not_Affect_Authorized_Clients()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
testEnvironment.IgnoreClientLogErrors = true;
|
||||||
|
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var publishedApplicationMessages = new List<MqttApplicationMessage>();
|
||||||
|
testEnvironment.Server.InterceptingPublishAsync += eventArgs =>
|
||||||
|
{
|
||||||
|
lock (publishedApplicationMessages)
|
||||||
|
{
|
||||||
|
publishedApplicationMessages.Add(eventArgs.ApplicationMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
testEnvironment.Server.ValidatingConnectionAsync += eventArgs =>
|
||||||
|
{
|
||||||
|
if (eventArgs.UserName == "SECRET")
|
||||||
|
{
|
||||||
|
eventArgs.ReasonCode = MqttConnectReasonCode.Success;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
eventArgs.ReasonCode = MqttConnectReasonCode.NotAuthorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var validClient = testEnvironment.CreateClient())
|
||||||
|
{
|
||||||
|
await validClient.ConnectAsync(
|
||||||
|
testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||||
|
.WithTcpServer("localhost", testEnvironment.ServerPort)
|
||||||
|
.WithCredentials("SECRET")
|
||||||
|
.WithClientId("CLIENT")
|
||||||
|
.Build());
|
||||||
|
|
||||||
|
await validClient.PublishStringAsync("HELLO 1");
|
||||||
|
|
||||||
|
// The following code tries to connect a new client with the same client ID but invalid
|
||||||
|
// credentials. This should block the second client but keep the first one up and running.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var invalidClient = testEnvironment.CreateClient())
|
||||||
|
{
|
||||||
|
await invalidClient.ConnectAsync(
|
||||||
|
testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||||
|
.WithTcpServer("localhost", testEnvironment.ServerPort)
|
||||||
|
.WithCredentials("???")
|
||||||
|
.WithClientId("CLIENT")
|
||||||
|
.Build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors from the second client.
|
||||||
|
}
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
await validClient.PublishStringAsync("HELLO 2");
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
await validClient.PublishStringAsync("HELLO 3");
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
Assert.AreEqual(3, publishedApplicationMessages.Count);
|
||||||
|
Assert.AreEqual(1, testEnvironment.Server.GetClientsAsync().GetAwaiter().GetResult().Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public Task Handle_Wrong_Password()
|
||||||
|
{
|
||||||
|
return TestCredentials("UserName", "x");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public Task Handle_Wrong_UserName()
|
||||||
|
{
|
||||||
|
return TestCredentials("x", "Password1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public Task Handle_Wrong_UserName_And_Password()
|
||||||
|
{
|
||||||
|
return TestCredentials("x", "x");
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Use_Username_Null_Password_Empty()
|
public async Task Use_Username_Null_Password_Empty()
|
||||||
{
|
{
|
||||||
@@ -29,10 +128,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
var client = testEnvironment.CreateClient();
|
var client = testEnvironment.CreateClient();
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder()
|
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(username, password).Build();
|
||||||
.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>(async () => await client.ConnectAsync(clientOptions));
|
||||||
Assert.IsInstanceOfType(ex.InnerException, typeof(MqttProtocolViolationException));
|
Assert.IsInstanceOfType(ex.InnerException, typeof(MqttProtocolViolationException));
|
||||||
@@ -41,25 +137,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
async Task TestCredentials(string userName, string password)
|
||||||
public Task Handle_Wrong_UserName()
|
|
||||||
{
|
|
||||||
return TestCredentials("x", "Password1");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public Task Handle_Wrong_Password()
|
|
||||||
{
|
|
||||||
return TestCredentials("UserName", "x");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public Task Handle_Wrong_UserName_And_Password()
|
|
||||||
{
|
|
||||||
return TestCredentials("x", "x");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task TestCredentials(string userName, string password)
|
|
||||||
{
|
{
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
{
|
{
|
||||||
@@ -69,7 +147,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
server.ValidatingConnectionAsync += e =>
|
server.ValidatingConnectionAsync += e =>
|
||||||
{
|
{
|
||||||
if (e.Username != "UserName1")
|
if (e.UserName != "UserName1")
|
||||||
{
|
{
|
||||||
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
|
||||||
}
|
}
|
||||||
@@ -84,10 +162,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
var client = testEnvironment.CreateClient();
|
var client = testEnvironment.CreateClient();
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder()
|
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build();
|
||||||
.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>(async () => await client.ConnectAsync(clientOptions));
|
||||||
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
|
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ using MQTTnet.Implementations;
|
|||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
using MqttClient = MQTTnet.Client.MqttClient;
|
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
{
|
{
|
||||||
@@ -220,6 +219,37 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Session_Takeover()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var options = new MqttClientOptionsBuilder().WithCleanSession(false)
|
||||||
|
.WithProtocolVersion(MqttProtocolVersion.V500) // Disconnect reason is only available in MQTT 5+
|
||||||
|
.WithClientId("a");
|
||||||
|
|
||||||
|
var client1 = await testEnvironment.ConnectClient(options);
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
var disconnectReason = MqttClientDisconnectReason.NormalDisconnection;
|
||||||
|
client1.DisconnectedAsync += c =>
|
||||||
|
{
|
||||||
|
disconnectReason = c.Reason;
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client2 = await testEnvironment.ConnectClient(options);
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
Assert.IsFalse(client1.IsConnected);
|
||||||
|
Assert.IsTrue(client2.IsConnected);
|
||||||
|
|
||||||
|
Assert.AreEqual(MqttClientDisconnectReason.SessionTakenOver, disconnectReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Set_Session_Item()
|
public async Task Set_Session_Item()
|
||||||
{
|
{
|
||||||
@@ -277,6 +307,59 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Use_Clean_Session()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var client = testEnvironment.CreateClient();
|
||||||
|
var connectResult = await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).WithCleanSession().Build());
|
||||||
|
|
||||||
|
// Create the session including the subscription.
|
||||||
|
var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a").WithCleanSession(false));
|
||||||
|
await client1.SubscribeAsync("x");
|
||||||
|
await client1.DisconnectAsync();
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
Assert.IsFalse(connectResult.IsSessionPresent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Will_Message_Do_Not_Send_On_Takeover()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var receivedMessagesCount = 0;
|
||||||
|
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
// C1 will receive the last will!
|
||||||
|
var c1 = await testEnvironment.ConnectClient();
|
||||||
|
c1.ApplicationMessageReceivedAsync += e =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref receivedMessagesCount);
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
|
|
||||||
|
// C2 has the last will defined.
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner");
|
||||||
|
|
||||||
|
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
|
||||||
|
// C3 will do the connection takeover.
|
||||||
|
var c3 = await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(0, receivedMessagesCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async Task<IMqttClient> ConnectAndSubscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive)
|
async Task<IMqttClient> ConnectAndSubscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using MQTTnet.Formatter;
|
|||||||
using MQTTnet.Implementations;
|
using MQTTnet.Implementations;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
{
|
{
|
||||||
@@ -45,6 +46,30 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Enqueue_Message_After_Subscription()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
server.ClientSubscribedTopicAsync += e =>
|
||||||
|
{
|
||||||
|
server.InjectApplicationMessage(new InjectedMqttApplicationMessage(new MqttApplicationMessageBuilder().WithTopic("test_topic").Build()));
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(client);
|
||||||
|
|
||||||
|
await client.SubscribeAsync("test_topic");
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Intercept_Subscription()
|
public async Task Intercept_Subscription()
|
||||||
{
|
{
|
||||||
@@ -101,7 +126,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
.WithTopicFilter("c", MqttQualityOfServiceLevel.ExactlyOnce)
|
.WithTopicFilter("c", MqttQualityOfServiceLevel.ExactlyOnce)
|
||||||
.WithTopicFilter("d")
|
.WithTopicFilter("d")
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var response = await client.SubscribeAsync(subscribeOptions);
|
var response = await client.SubscribeAsync(subscribeOptions);
|
||||||
|
|
||||||
Assert.AreEqual(subscribeOptions.TopicFilters.Count, response.Items.Count);
|
Assert.AreEqual(subscribeOptions.TopicFilters.Count, response.Items.Count);
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class Will_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var receivedMessagesCount = 0;
|
||||||
|
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
|
||||||
|
|
||||||
|
var c1 = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
c1.ApplicationMessageReceivedAsync += e =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref receivedMessagesCount);
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
|
|
||||||
|
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
await c2.DisconnectAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(0, receivedMessagesCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Will_Message_Send()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
|
||||||
|
|
||||||
|
var receivedMessagesCount = 0;
|
||||||
|
c1.ApplicationMessageReceivedAsync += e =>
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref receivedMessagesCount);
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
|
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
|
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||||
|
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
Assert.AreEqual(1, receivedMessagesCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -98,6 +98,16 @@ namespace MQTTnet.Client
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort TopicAliasMaximum { get; set; }
|
public ushort TopicAliasMaximum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set to true, the bridge will attempt to indicate to the remote broker that it is a bridge not an ordinary
|
||||||
|
/// client.
|
||||||
|
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated
|
||||||
|
/// correctly.
|
||||||
|
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect
|
||||||
|
/// properly.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryPrivate { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the user properties.
|
/// Gets or sets the user properties.
|
||||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||||
|
|||||||
@@ -96,16 +96,6 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
public MqttClientOptionsBuilder WithTimeout(TimeSpan value)
|
|
||||||
{
|
|
||||||
_options.Timeout = value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithConnectionUri(Uri uri)
|
public MqttClientOptionsBuilder WithConnectionUri(Uri uri)
|
||||||
{
|
{
|
||||||
if (uri == null)
|
if (uri == null)
|
||||||
@@ -274,8 +264,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Consider creating _MqttClientTcpOptionsBuilder_ as overload.
|
|
||||||
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
|
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
|
||||||
{
|
{
|
||||||
if (optionsBuilder == null)
|
if (optionsBuilder == null)
|
||||||
@@ -289,6 +278,16 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public MqttClientOptionsBuilder WithTimeout(TimeSpan value)
|
||||||
|
{
|
||||||
|
_options.Timeout = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters)
|
public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters)
|
||||||
{
|
{
|
||||||
_tlsParameters = parameters;
|
_tlsParameters = parameters;
|
||||||
@@ -322,6 +321,20 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If set to true, the bridge will attempt to indicate to the remote broker that it is a bridge not an ordinary
|
||||||
|
/// client.
|
||||||
|
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated
|
||||||
|
/// correctly.
|
||||||
|
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect
|
||||||
|
/// properly.
|
||||||
|
/// </summary>
|
||||||
|
public MqttClientOptionsBuilder WithTryPrivate(bool tryPrivate = true)
|
||||||
|
{
|
||||||
|
_options.TryPrivate = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithUserProperty(string name, string value)
|
public MqttClientOptionsBuilder WithUserProperty(string name, string value)
|
||||||
{
|
{
|
||||||
if (name is null)
|
if (name is null)
|
||||||
@@ -379,6 +392,17 @@ namespace MQTTnet.Client
|
|||||||
_options.WillPayload = willPayload;
|
_options.WillPayload = willPayload;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWillPayload(string willPayload)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(willPayload))
|
||||||
|
{
|
||||||
|
return WithWillPayload((byte[])null);
|
||||||
|
}
|
||||||
|
|
||||||
|
_options.WillPayload = Encoding.UTF8.GetBytes(willPayload);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel)
|
public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel)
|
||||||
{
|
{
|
||||||
@@ -391,5 +415,46 @@ namespace MQTTnet.Client
|
|||||||
_options.WillTopic = willTopic;
|
_options.WillTopic = willTopic;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWillRetain(bool willRetain = true)
|
||||||
|
{
|
||||||
|
_options.WillRetain = willRetain;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
|
||||||
|
{
|
||||||
|
_options.WillContentType = willContentType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWillCorrelationData(byte[] willCorrelationData)
|
||||||
|
{
|
||||||
|
_options.WillCorrelationData = willCorrelationData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic)
|
||||||
|
{
|
||||||
|
_options.WillResponseTopic = willResponseTopic;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator)
|
||||||
|
{
|
||||||
|
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttClientOptionsBuilder WithWillUserProperty(string name, string value)
|
||||||
|
{
|
||||||
|
if (_options.WillUserProperties == null)
|
||||||
|
{
|
||||||
|
_options.WillUserProperties = new List<MqttUserProperty>();
|
||||||
|
}
|
||||||
|
|
||||||
|
_options.WillUserProperties.Add(new MqttUserProperty(name, value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
namespace MQTTnet.Client
|
||||||
@@ -10,8 +11,19 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public AddressFamily AddressFamily { get; set; } = AddressFamily.Unspecified;
|
public AddressFamily AddressFamily { get; set; } = AddressFamily.Unspecified;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the local endpoint (network card) which is used by the client.
|
||||||
|
/// Set it to _null_ to let the OS select the network card.
|
||||||
|
/// </summary>
|
||||||
|
public EndPoint LocalEndpoint { get; set; }
|
||||||
|
|
||||||
public int BufferSize { get; set; } = 8192;
|
public int BufferSize { get; set; } = 8192;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether the underlying socket should run in dual mode.
|
||||||
|
/// Leaving this _null_ will avoid setting this value at socket level.
|
||||||
|
/// Setting this a value other than _null_ will throw an exception when only IPv4 is supported on the machine.
|
||||||
|
/// </summary>
|
||||||
public bool? DualMode { get; set; }
|
public bool? DualMode { get; set; }
|
||||||
|
|
||||||
public LingerOption LingerState { get; set; } = new LingerOption(true, 0);
|
public LingerOption LingerState { get; set; } = new LingerOption(true, 0);
|
||||||
|
|||||||
@@ -7,23 +7,23 @@ using System.Net;
|
|||||||
|
|
||||||
namespace MQTTnet.Client
|
namespace MQTTnet.Client
|
||||||
{
|
{
|
||||||
public class MqttClientWebSocketOptions : IMqttClientChannelOptions
|
public sealed class MqttClientWebSocketOptions : IMqttClientChannelOptions
|
||||||
{
|
{
|
||||||
public string Uri { get; set; }
|
public CookieContainer CookieContainer { get; set; }
|
||||||
|
|
||||||
|
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
|
||||||
|
|
||||||
public IDictionary<string, string> RequestHeaders { get; set; }
|
public IDictionary<string, string> RequestHeaders { get; set; }
|
||||||
|
|
||||||
public ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" };
|
public ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" };
|
||||||
|
|
||||||
public CookieContainer CookieContainer { get; set; }
|
|
||||||
|
|
||||||
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
|
|
||||||
|
|
||||||
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
||||||
|
|
||||||
|
public string Uri { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Uri;
|
return Uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace MQTTnet.Client
|
namespace MQTTnet.Client
|
||||||
{
|
{
|
||||||
public class MqttClientWebSocketProxyOptions
|
public sealed class MqttClientWebSocketProxyOptions
|
||||||
{
|
{
|
||||||
public string Address { get; set; }
|
public string Address { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace MQTTnet.Client
|
|||||||
ClientId = clientId;
|
ClientId = clientId;
|
||||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
PublishPacket = publishPacket;
|
PublishPacket = publishPacket;
|
||||||
|
PacketIdentifier = publishPacket.PacketIdentifier;
|
||||||
_acknowledgeHandler = acknowledgeHandler;
|
_acknowledgeHandler = acknowledgeHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,12 +63,17 @@ namespace MQTTnet.Client
|
|||||||
public bool IsHandled { get; set; }
|
public bool IsHandled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets ir sets whether the library should send MQTT ACK packets automatically if required.
|
/// Gets or sets whether the library should send MQTT ACK packets automatically if required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AutoAcknowledge { get; set; } = true;
|
public bool AutoAcknowledge { get; set; } = true;
|
||||||
|
|
||||||
public object Tag { get; set; }
|
public object Tag { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the indentifier of MQTT packet
|
||||||
|
/// </summary>
|
||||||
|
public ushort PacketIdentifier { get; }
|
||||||
|
|
||||||
public Task AcknowledgeAsync(CancellationToken cancellationToken)
|
public Task AcknowledgeAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_acknowledgeHandler == null)
|
if (_acknowledgeHandler == null)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Formatter
|
namespace MQTTnet.Formatter
|
||||||
@@ -25,6 +26,7 @@ namespace MQTTnet.Formatter
|
|||||||
SubscriptionIdentifiersAvailable = true,
|
SubscriptionIdentifiersAvailable = true,
|
||||||
SharedSubscriptionAvailable = false,
|
SharedSubscriptionAvailable = false,
|
||||||
TopicAliasMaximum = ushort.MaxValue,
|
TopicAliasMaximum = ushort.MaxValue,
|
||||||
|
MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce,
|
||||||
WildcardSubscriptionAvailable = true,
|
WildcardSubscriptionAvailable = true,
|
||||||
|
|
||||||
AuthenticationMethod = validatingConnectionEventArgs.AuthenticationMethod,
|
AuthenticationMethod = validatingConnectionEventArgs.AuthenticationMethod,
|
||||||
@@ -32,7 +34,11 @@ namespace MQTTnet.Formatter
|
|||||||
AssignedClientIdentifier = validatingConnectionEventArgs.AssignedClientIdentifier,
|
AssignedClientIdentifier = validatingConnectionEventArgs.AssignedClientIdentifier,
|
||||||
ReasonString = validatingConnectionEventArgs.ReasonString,
|
ReasonString = validatingConnectionEventArgs.ReasonString,
|
||||||
ServerReference = validatingConnectionEventArgs.ServerReference,
|
ServerReference = validatingConnectionEventArgs.ServerReference,
|
||||||
UserProperties = validatingConnectionEventArgs.ResponseUserProperties
|
UserProperties = validatingConnectionEventArgs.ResponseUserProperties,
|
||||||
|
|
||||||
|
ResponseInformation = null,
|
||||||
|
MaximumPacketSize = 0, // Unlimited,
|
||||||
|
ReceiveMaximum = 0 // Unlimited
|
||||||
};
|
};
|
||||||
|
|
||||||
return connAckPacket;
|
return connAckPacket;
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ namespace MQTTnet.Formatter
|
|||||||
RequestResponseInformation = clientOptions.RequestResponseInformation,
|
RequestResponseInformation = clientOptions.RequestResponseInformation,
|
||||||
SessionExpiryInterval = clientOptions.SessionExpiryInterval,
|
SessionExpiryInterval = clientOptions.SessionExpiryInterval,
|
||||||
TopicAliasMaximum = clientOptions.TopicAliasMaximum,
|
TopicAliasMaximum = clientOptions.TopicAliasMaximum,
|
||||||
UserProperties = clientOptions.UserProperties
|
UserProperties = clientOptions.UserProperties,
|
||||||
|
TryPrivate = clientOptions.TryPrivate
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(clientOptions.WillTopic))
|
if (!string.IsNullOrEmpty(clientOptions.WillTopic))
|
||||||
|
|||||||
@@ -95,6 +95,10 @@ namespace MQTTnet.Formatter
|
|||||||
var protocolName = _bufferReader.ReadString();
|
var protocolName = _bufferReader.ReadString();
|
||||||
var protocolLevel = _bufferReader.ReadByte();
|
var protocolLevel = _bufferReader.ReadByte();
|
||||||
|
|
||||||
|
// Remove the mosquitto try_private flag (MQTT 3.1.1 Bridge).
|
||||||
|
// This flag is accepted but not yet used.
|
||||||
|
protocolLevel &= 0x7F;
|
||||||
|
|
||||||
if (protocolName == "MQTT")
|
if (protocolName == "MQTT")
|
||||||
{
|
{
|
||||||
if (protocolLevel == 5)
|
if (protocolLevel == 5)
|
||||||
|
|||||||
@@ -175,12 +175,18 @@ namespace MQTTnet.Formatter.V3
|
|||||||
throw new MqttProtocolViolationException("MQTT protocol name do not match MQTT v3.");
|
throw new MqttProtocolViolationException("MQTT protocol name do not match MQTT v3.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tryPrivate = (protocolVersion & 0x80) > 0;
|
||||||
|
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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var packet = new MqttConnectPacket();
|
var packet = new MqttConnectPacket
|
||||||
|
{
|
||||||
|
TryPrivate = tryPrivate
|
||||||
|
};
|
||||||
|
|
||||||
var connectFlags = _bufferReader.ReadByte();
|
var connectFlags = _bufferReader.ReadByte();
|
||||||
if ((connectFlags & 0x1) > 0)
|
if ((connectFlags & 0x1) > 0)
|
||||||
@@ -409,7 +415,14 @@ namespace MQTTnet.Formatter.V3
|
|||||||
ValidateConnectPacket(packet);
|
ValidateConnectPacket(packet);
|
||||||
|
|
||||||
bufferWriter.WriteString("MQIsdp");
|
bufferWriter.WriteString("MQIsdp");
|
||||||
bufferWriter.WriteByte(3); // Protocol Level 3
|
|
||||||
|
var protocolVersion = 3;
|
||||||
|
if (packet.TryPrivate)
|
||||||
|
{
|
||||||
|
protocolVersion |= 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferWriter.WriteByte((byte)protocolVersion);
|
||||||
|
|
||||||
byte connectFlags = 0x0;
|
byte connectFlags = 0x0;
|
||||||
if (packet.CleanSession)
|
if (packet.CleanSession)
|
||||||
|
|||||||
@@ -72,11 +72,11 @@ namespace MQTTnet.Formatter.V5
|
|||||||
|
|
||||||
if (value == MqttQualityOfServiceLevel.AtLeastOnce)
|
if (value == MqttQualityOfServiceLevel.AtLeastOnce)
|
||||||
{
|
{
|
||||||
Write(MqttPropertyId.MaximumQoS, true);
|
Write(MqttPropertyId.MaximumQoS, 0x1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Write(MqttPropertyId.MaximumQoS, false);
|
Write(MqttPropertyId.MaximumQoS, 0x0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +84,11 @@ namespace MQTTnet.Formatter.V5
|
|||||||
{
|
{
|
||||||
// If absent, the Application Message does not expire.
|
// If absent, the Application Message does not expire.
|
||||||
// This library uses 0 to indicate no expiration.
|
// This library uses 0 to indicate no expiration.
|
||||||
|
if (value == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
WriteAsFourByteInteger(MqttPropertyId.MessageExpiryInterval, value);
|
WriteAsFourByteInteger(MqttPropertyId.MessageExpiryInterval, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
// 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.Channel;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
@@ -11,9 +14,6 @@ using System.Runtime.ExceptionServices;
|
|||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Channel;
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Exceptions;
|
|
||||||
|
|
||||||
namespace MQTTnet.Implementations
|
namespace MQTTnet.Implementations
|
||||||
{
|
{
|
||||||
@@ -68,6 +68,11 @@ namespace MQTTnet.Implementations
|
|||||||
socket = new CrossPlatformSocket(_tcpOptions.AddressFamily);
|
socket = new CrossPlatformSocket(_tcpOptions.AddressFamily);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_tcpOptions.LocalEndpoint != null)
|
||||||
|
{
|
||||||
|
socket.Bind(_tcpOptions.LocalEndpoint);
|
||||||
|
}
|
||||||
|
|
||||||
socket.ReceiveBufferSize = _tcpOptions.BufferSize;
|
socket.ReceiveBufferSize = _tcpOptions.BufferSize;
|
||||||
socket.SendBufferSize = _tcpOptions.BufferSize;
|
socket.SendBufferSize = _tcpOptions.BufferSize;
|
||||||
socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds;
|
socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds;
|
||||||
@@ -77,7 +82,7 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
socket.LingerState = _tcpOptions.LingerState;
|
socket.LingerState = _tcpOptions.LingerState;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_tcpOptions.DualMode.HasValue)
|
if (_tcpOptions.DualMode.HasValue)
|
||||||
{
|
{
|
||||||
// It is important to avoid setting the flag if no specific value is set by the user
|
// It is important to avoid setting the flag if no specific value is set by the user
|
||||||
@@ -103,15 +108,20 @@ namespace MQTTnet.Implementations
|
|||||||
ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols,
|
ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols,
|
||||||
ClientCertificates = LoadCertificates(),
|
ClientCertificates = LoadCertificates(),
|
||||||
EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol,
|
EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol,
|
||||||
CertificateRevocationCheckMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode,
|
CertificateRevocationCheckMode =
|
||||||
|
_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode,
|
||||||
TargetHost = _tcpOptions.Server,
|
TargetHost = _tcpOptions.Server,
|
||||||
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy
|
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy
|
||||||
};
|
};
|
||||||
|
|
||||||
await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
|
await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
|
||||||
#else
|
#else
|
||||||
await sslStream.AuthenticateAsClientAsync(_tcpOptions.Server, LoadCertificates(), _tcpOptions.TlsOptions.SslProtocol,
|
await sslStream.AuthenticateAsClientAsync(
|
||||||
!_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
|
_tcpOptions.Server,
|
||||||
|
LoadCertificates(),
|
||||||
|
_tcpOptions.TlsOptions.SslProtocol,
|
||||||
|
!_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors)
|
||||||
|
.ConfigureAwait(false);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -269,6 +279,11 @@ namespace MQTTnet.Implementations
|
|||||||
return certificateValidationHandler(eventArgs);
|
return certificateValidationHandler(eventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_tcpOptions?.TlsOptions?.IgnoreCertificateChainErrors ?? false)
|
||||||
|
{
|
||||||
|
sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
|
||||||
|
}
|
||||||
|
|
||||||
return sslPolicyErrors == SslPolicyErrors.None;
|
return sslPolicyErrors == SslPolicyErrors.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
void SetupClientWebSocket(ClientWebSocket clientWebSocket)
|
void SetupClientWebSocket(ClientWebSocket clientWebSocket)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (_options.ProxyOptions != null)
|
if (_options.ProxyOptions != null)
|
||||||
{
|
{
|
||||||
clientWebSocket.Options.Proxy = CreateProxy();
|
clientWebSocket.Options.Proxy = CreateProxy();
|
||||||
|
|||||||
@@ -21,12 +21,14 @@ namespace MQTTnet.Internal
|
|||||||
|
|
||||||
public bool WrapsHandler(Action<TEventArgs> handler)
|
public bool WrapsHandler(Action<TEventArgs> handler)
|
||||||
{
|
{
|
||||||
return ReferenceEquals(_handler, handler);
|
// Do not use ReferenceEquals! It will not work with delegates.
|
||||||
|
return handler == _handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool WrapsHandler(Func<TEventArgs, Task> handler)
|
public bool WrapsHandler(Func<TEventArgs, Task> handler)
|
||||||
{
|
{
|
||||||
return ReferenceEquals(_asyncHandler, handler);
|
// Do not use ReferenceEquals! It will not work with delegates.
|
||||||
|
return handler == _asyncHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task InvokeAsync(TEventArgs eventArgs)
|
public Task InvokeAsync(TEventArgs eventArgs)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<Copyright>Christian Kratky 2016-2022</Copyright>
|
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
|
||||||
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<PackageIcon>nuget.png</PackageIcon>
|
<PackageIcon>nuget.png</PackageIcon>
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -76,14 +76,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\..\LICENSE">
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||||
<Pack>True</Pack>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
|
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ namespace MQTTnet.Packets
|
|||||||
|
|
||||||
public List<MqttUserProperty> WillUserProperties { get; set; }
|
public List<MqttUserProperty> WillUserProperties { get; set; }
|
||||||
|
|
||||||
|
public bool TryPrivate { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var passwordText = string.Empty;
|
var passwordText = string.Empty;
|
||||||
|
|||||||
@@ -8,14 +8,20 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs
|
public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId)
|
||||||
|
{
|
||||||
|
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
|
SenderId = senderId;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the application message which was not consumed by any client.
|
/// Gets the application message which was not consumed by any client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttApplicationMessage ApplicationMessage { get; internal set; }
|
public MqttApplicationMessage ApplicationMessage { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ID of the client which has sent the affected application message.
|
/// Gets the ID of the client which has sent the affected application message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SenderId { get; internal set; }
|
public string SenderId { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// 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;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class ClientAcknowledgedPublishPacketEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet.
|
||||||
|
/// </summary>
|
||||||
|
public MqttPacketWithIdentifier AcknowledgePacket { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ID of the client which acknowledged a PUBLISH packet.
|
||||||
|
/// </summary>
|
||||||
|
public string ClientId { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCompleted => AcknowledgePacket is MqttPubAckPacket || AcknowledgePacket is MqttPubCompPacket;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the PUBLISH packet which was acknowledged.
|
||||||
|
/// </summary>
|
||||||
|
public MqttPublishPacket PublishPacket { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the session items which contain custom user data per session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,31 +3,46 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class ClientConnectedEventArgs : EventArgs
|
public sealed class ClientConnectedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
public ClientConnectedEventArgs(string clientId, string userName, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems)
|
||||||
/// Gets the client identifier of the connected client.
|
{
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
/// </summary>
|
UserName = userName;
|
||||||
public string ClientId { get; internal set; }
|
ProtocolVersion = protocolVersion;
|
||||||
|
Endpoint = endpoint;
|
||||||
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the user name of the connected client.
|
/// Gets the client identifier of the connected client.
|
||||||
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string UserName { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the protocol version which is used by the connected client.
|
/// Gets the endpoint of the connected client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttProtocolVersion ProtocolVersion { get; internal set; }
|
public string Endpoint { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the endpoint of the connected client.
|
/// Gets the protocol version which is used by the connected client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Endpoint { get; internal set; }
|
public MqttProtocolVersion ProtocolVersion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the user name of the connected client.
|
||||||
|
/// </summary>
|
||||||
|
public string UserName { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,19 +3,33 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class ClientDisconnectedEventArgs : EventArgs
|
public sealed class ClientDisconnectedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public ClientDisconnectedEventArgs(string clientId, MqttClientDisconnectType disconnectType, string endpoint, IDictionary sessionItems)
|
||||||
|
{
|
||||||
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
|
DisconnectType = disconnectType;
|
||||||
|
Endpoint = endpoint;
|
||||||
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
public MqttClientDisconnectType DisconnectType { get; internal set; }
|
public MqttClientDisconnectType DisconnectType { get; }
|
||||||
|
|
||||||
public string Endpoint { get; internal set; }
|
public string Endpoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,22 +3,35 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class ClientSubscribedTopicEventArgs : EventArgs
|
public sealed class ClientSubscribedTopicEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
public ClientSubscribedTopicEventArgs(string clientId, MqttTopicFilter topicFilter, IDictionary sessionItems)
|
||||||
/// Gets the client identifier.
|
{
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
/// </summary>
|
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
|
||||||
public string ClientId { get; internal set; }
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the topic filter.
|
/// Gets the client identifier.
|
||||||
/// The topic filter can contain topics and wildcards.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttTopicFilter TopicFilter { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the topic filter.
|
||||||
|
/// The topic filter can contain topics and wildcards.
|
||||||
|
/// </summary>
|
||||||
|
public MqttTopicFilter TopicFilter { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,21 +3,34 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class ClientUnsubscribedTopicEventArgs : EventArgs
|
public sealed class ClientUnsubscribedTopicEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
public ClientUnsubscribedTopicEventArgs(string clientId, string topicFilter, IDictionary sessionItems)
|
||||||
/// Gets the client identifier.
|
{
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
/// </summary>
|
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
|
||||||
public string ClientId { get; internal set; }
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the topic filter.
|
/// Gets the client identifier.
|
||||||
/// The topic filter can contain topics and wildcards.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string TopicFilter { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the topic filter.
|
||||||
|
/// The topic filter can contain topics and wildcards.
|
||||||
|
/// </summary>
|
||||||
|
public string TopicFilter { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
@@ -10,30 +11,44 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class InterceptingPacketEventArgs : EventArgs
|
public sealed class InterceptingPacketEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public InterceptingPacketEventArgs(CancellationToken cancellationToken, string clientId, string endpoint, MqttPacket packet, IDictionary sessionItems)
|
||||||
|
{
|
||||||
|
CancellationToken = cancellationToken;
|
||||||
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
|
Endpoint = endpoint;
|
||||||
|
Packet = packet ?? throw new ArgumentNullException(nameof(packet));
|
||||||
|
SessionItems = sessionItems;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client ID which has sent the packet or will receive the packet.
|
/// Gets the cancellation token from the connection managing thread.
|
||||||
|
/// Use this in further event processing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; internal set; }
|
public CancellationToken CancellationToken { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the endpoint of the sending or receiving client.
|
/// Gets the client ID which has sent the packet or will receive the packet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Endpoint { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MQTT packet which was received or will be sent.
|
/// Gets the endpoint of the sending or receiving client.
|
||||||
|
/// </summary>
|
||||||
|
public string Endpoint { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the MQTT packet which was received or will be sent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttPacket Packet { get; set; }
|
public MqttPacket Packet { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether the packet should be processed or not.
|
/// Gets or sets whether the packet should be processed or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ProcessPacket { get; set; } = true;
|
public bool ProcessPacket { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cancellation token from the connection managing thread.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// Use this in further event processing.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationToken CancellationToken { get; internal set; }
|
public IDictionary SessionItems { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,18 +10,26 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class InterceptingPublishEventArgs : EventArgs
|
public sealed class InterceptingPublishEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public InterceptingPublishEventArgs(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken, string clientId, IDictionary sessionItems)
|
||||||
|
{
|
||||||
|
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
|
CancellationToken = cancellationToken;
|
||||||
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cancellation token which can indicate that the client connection gets down.
|
/// Gets the cancellation token which can indicate that the client connection gets down.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationToken CancellationToken { get; internal set; }
|
public CancellationToken CancellationToken { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
public bool CloseConnection { get; set; }
|
public bool CloseConnection { get; set; }
|
||||||
|
|
||||||
@@ -38,6 +46,6 @@ namespace MQTTnet.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary SessionItems { get; internal set; }
|
public IDictionary SessionItems { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,16 +12,28 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class InterceptingSubscriptionEventArgs : EventArgs
|
public sealed class InterceptingSubscriptionEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public InterceptingSubscriptionEventArgs(
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
string clientId,
|
||||||
|
MqttSessionStatus session,
|
||||||
|
MqttTopicFilter topicFilter)
|
||||||
|
{
|
||||||
|
CancellationToken = cancellationToken;
|
||||||
|
ClientId = clientId;
|
||||||
|
Session = session;
|
||||||
|
TopicFilter = topicFilter;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cancellation token which can indicate that the client connection gets down.
|
/// Gets the cancellation token which can indicate that the client connection gets down.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationToken CancellationToken { get; internal set; }
|
public CancellationToken CancellationToken { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether the broker should close the client connection.
|
/// Gets or sets whether the broker should close the client connection.
|
||||||
@@ -48,12 +60,12 @@ namespace MQTTnet.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current client session.
|
/// Gets the current client session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttSessionStatus Session { get; internal set; }
|
public MqttSessionStatus Session { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary SessionItems { get; internal set; }
|
public IDictionary SessionItems => Session.Items;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the topic filter.
|
/// Gets or sets the topic filter.
|
||||||
|
|||||||
@@ -10,16 +10,24 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs
|
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, IDictionary sessionItems, string topic)
|
||||||
|
{
|
||||||
|
CancellationToken = cancellationToken;
|
||||||
|
ClientId = clientId;
|
||||||
|
SessionItems = sessionItems;
|
||||||
|
Topic = topic;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cancellation token which can indicate that the client connection gets down.
|
/// Gets the cancellation token which can indicate that the client connection gets down.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationToken CancellationToken { get; internal set; }
|
public CancellationToken CancellationToken { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId { get; internal set; }
|
public string ClientId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether the broker should close the client connection.
|
/// Gets or sets whether the broker should close the client connection.
|
||||||
@@ -41,7 +49,7 @@ namespace MQTTnet.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDictionary SessionItems { get; internal set; }
|
public IDictionary SessionItems { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the MQTT topic.
|
/// Gets or sets the MQTT topic.
|
||||||
@@ -50,6 +58,6 @@ namespace MQTTnet.Server
|
|||||||
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level
|
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level
|
||||||
/// separator).
|
/// separator).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Topic { get; internal set; }
|
public string Topic { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,10 +9,17 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class RetainedMessageChangedEventArgs : EventArgs
|
public sealed class RetainedMessageChangedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public string ClientId { get; internal set; }
|
public RetainedMessageChangedEventArgs(string clientId, MqttApplicationMessage changedRetainedMessage, List<MqttApplicationMessage> storedRetainedMessages)
|
||||||
|
{
|
||||||
public MqttApplicationMessage ChangedRetainedMessage { get; internal set; }
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
|
ChangedRetainedMessage = changedRetainedMessage ?? throw new ArgumentNullException(nameof(changedRetainedMessage));
|
||||||
public List<MqttApplicationMessage> StoredRetainedMessages { get; internal set; }
|
StoredRetainedMessages = storedRetainedMessages ?? throw new ArgumentNullException(nameof(storedRetainedMessages));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttApplicationMessage ChangedRetainedMessage { get; }
|
||||||
|
|
||||||
|
public string ClientId { get; }
|
||||||
|
|
||||||
|
public List<MqttApplicationMessage> StoredRetainedMessages { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,14 +3,26 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class SessionDeletedEventArgs : EventArgs
|
public sealed class SessionDeletedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
|
public SessionDeletedEventArgs(string id, IDictionary sessionItems)
|
||||||
|
{
|
||||||
|
Id = id ?? throw new ArgumentNullException(nameof(id));
|
||||||
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ID of the session.
|
/// Gets the ID of the session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Id { get; internal set; }
|
public string Id { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary SessionItems { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,13 +17,12 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class ValidatingConnectionEventArgs : EventArgs
|
public sealed class ValidatingConnectionEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
readonly IMqttChannelAdapter _clientAdapter;
|
|
||||||
readonly MqttConnectPacket _connectPacket;
|
readonly MqttConnectPacket _connectPacket;
|
||||||
|
|
||||||
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
|
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
|
||||||
{
|
{
|
||||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||||
_clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -44,6 +43,12 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the channel adapter. This can be a _MqttConnectionContext_ (used in ASP.NET), a _MqttChannelAdapter_ (used for
|
||||||
|
/// TCP or WebSockets) or a custom implementation.
|
||||||
|
/// </summary>
|
||||||
|
public IMqttChannelAdapter ChannelAdapter { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether clean sessions are used or not.
|
/// Gets or sets a value indicating whether clean sessions are used or not.
|
||||||
/// When a client connects to a broker it can connect using either a non persistent connection (clean session) or a
|
/// When a client connects to a broker it can connect using either a non persistent connection (clean session) or a
|
||||||
@@ -56,7 +61,7 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool? CleanSession => _connectPacket.CleanSession;
|
public bool? CleanSession => _connectPacket.CleanSession;
|
||||||
|
|
||||||
public X509Certificate2 ClientCertificate => _clientAdapter.ClientCertificate;
|
public X509Certificate2 ClientCertificate => ChannelAdapter.ClientCertificate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier.
|
/// Gets the client identifier.
|
||||||
@@ -64,9 +69,9 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId => _connectPacket.ClientId;
|
public string ClientId => _connectPacket.ClientId;
|
||||||
|
|
||||||
public string Endpoint => _clientAdapter.Endpoint;
|
public string Endpoint => ChannelAdapter.Endpoint;
|
||||||
|
|
||||||
public bool IsSecureConnection => _clientAdapter.IsSecureConnection;
|
public bool IsSecureConnection => ChannelAdapter.IsSecureConnection;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the keep alive period.
|
/// Gets or sets the keep alive period.
|
||||||
@@ -85,7 +90,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
|
public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
|
||||||
|
|
||||||
public MqttProtocolVersion ProtocolVersion => _clientAdapter.PacketFormatterAdapter.ProtocolVersion;
|
public MqttProtocolVersion ProtocolVersion => ChannelAdapter.PacketFormatterAdapter.ProtocolVersion;
|
||||||
|
|
||||||
public byte[] RawPassword => _connectPacket.Password;
|
public byte[] RawPassword => _connectPacket.Password;
|
||||||
|
|
||||||
@@ -161,7 +166,10 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort TopicAliasMaximum => _connectPacket.TopicAliasMaximum;
|
public ushort TopicAliasMaximum => _connectPacket.TopicAliasMaximum;
|
||||||
|
|
||||||
|
[Obsolete("This property name has a typo. Use 'UserName' instead. This one will be removed soon.")]
|
||||||
public string Username => _connectPacket.Username;
|
public string Username => _connectPacket.Username;
|
||||||
|
|
||||||
|
public string UserName => _connectPacket.Username;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the user properties.
|
/// Gets or sets the user properties.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using MQTTnet.Client;
|
|||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.PacketDispatcher;
|
using MQTTnet.PacketDispatcher;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
@@ -20,20 +21,20 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class MqttClient
|
public sealed class MqttClient
|
||||||
{
|
{
|
||||||
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
|
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
|
||||||
|
readonly MqttConnectPacket _connectPacket;
|
||||||
|
readonly MqttServerEventContainer _eventContainer;
|
||||||
|
readonly MqttNetSourceLogger _logger;
|
||||||
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
||||||
readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
|
readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
|
||||||
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
|
|
||||||
|
|
||||||
readonly MqttClientSessionsManager _sessionsManager;
|
|
||||||
readonly MqttNetSourceLogger _logger;
|
|
||||||
|
|
||||||
readonly MqttServerOptions _serverOptions;
|
readonly MqttServerOptions _serverOptions;
|
||||||
readonly MqttServerEventContainer _eventContainer;
|
|
||||||
readonly MqttConnectPacket _connectPacket;
|
readonly MqttClientSessionsManager _sessionsManager;
|
||||||
|
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
|
||||||
|
|
||||||
CancellationTokenSource _cancellationToken;
|
CancellationTokenSource _cancellationToken;
|
||||||
|
|
||||||
public MqttClient(
|
public MqttClient(
|
||||||
MqttConnectPacket connectPacket,
|
MqttConnectPacket connectPacket,
|
||||||
IMqttChannelAdapter channelAdapter,
|
IMqttChannelAdapter channelAdapter,
|
||||||
@@ -53,43 +54,31 @@ namespace MQTTnet.Server
|
|||||||
Session = session ?? throw new ArgumentNullException(nameof(session));
|
Session = session ?? throw new ArgumentNullException(nameof(session));
|
||||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||||
|
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
_logger = logger.WithSource(nameof(MqttClient));
|
_logger = logger.WithSource(nameof(MqttClient));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id => _connectPacket.ClientId;
|
public IMqttChannelAdapter ChannelAdapter { get; }
|
||||||
|
|
||||||
public string Endpoint { get; }
|
public string Endpoint { get; }
|
||||||
|
|
||||||
public IMqttChannelAdapter ChannelAdapter { get; }
|
public string Id => _connectPacket.ClientId;
|
||||||
|
|
||||||
public MqttClientStatistics Statistics { get; } = new MqttClientStatistics();
|
public bool IsCleanDisconnect { get; private set; }
|
||||||
|
|
||||||
public bool IsRunning { get; private set; }
|
public bool IsRunning { get; private set; }
|
||||||
|
|
||||||
|
public bool IsTakenOver { get; set; }
|
||||||
|
|
||||||
public ushort KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
|
public ushort KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
|
||||||
|
|
||||||
public MqttSession Session { get; }
|
public MqttSession Session { get; }
|
||||||
|
|
||||||
public bool IsTakenOver { get; set; }
|
public MqttClientStatistics Statistics { get; } = new MqttClientStatistics();
|
||||||
|
|
||||||
public bool IsCleanDisconnect { get; private set; }
|
|
||||||
|
|
||||||
public async Task StopAsync(MqttDisconnectReasonCode reason)
|
|
||||||
{
|
|
||||||
IsRunning = false;
|
|
||||||
|
|
||||||
if (reason == MqttDisconnectReasonCode.SessionTakenOver || reason == MqttDisconnectReasonCode.KeepAliveTimeout)
|
|
||||||
{
|
|
||||||
// Is is very important to send the DISCONNECT packet here BEFORE cancelling the
|
|
||||||
// token because the entire connection is closed (disposed) as soon as the cancellation
|
|
||||||
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
|
|
||||||
// at the client!
|
|
||||||
await TrySendDisconnectPacket(reason).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
StopInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetStatistics()
|
public void ResetStatistics()
|
||||||
{
|
{
|
||||||
@@ -110,7 +99,7 @@ namespace MQTTnet.Server
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Task.Run(() => SendPacketsLoop(cancellationToken), cancellationToken).RunInBackground(_logger);
|
Task.Run(() => SendPacketsLoop(cancellationToken), cancellationToken).RunInBackground(_logger);
|
||||||
|
|
||||||
IsRunning = true;
|
IsRunning = true;
|
||||||
|
|
||||||
await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
|
await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
|
||||||
@@ -130,10 +119,10 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
var willPublishPacket = _packetFactories.Publish.Create(Session.LatestConnectPacket);
|
var willPublishPacket = _packetFactories.Publish.Create(Session.LatestConnectPacket);
|
||||||
var willApplicationMessage = _applicationMessageFactory.Create(willPublishPacket);
|
var willApplicationMessage = _applicationMessageFactory.Create(willPublishPacket);
|
||||||
|
|
||||||
_= _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
|
_ = _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
|
||||||
Session.WillMessageSent = true;
|
Session.WillMessageSent = true;
|
||||||
|
|
||||||
_logger.Info("Client '{0}': Published will message.", Id);
|
_logger.Info("Client '{0}': Published will message.", Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,33 +138,233 @@ namespace MQTTnet.Server
|
|||||||
// This might break the protocol but the user wants that.
|
// This might break the protocol but the user wants that.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ChannelAdapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false);
|
await ChannelAdapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false);
|
||||||
Statistics.HandleSentPacket(packet);
|
Statistics.HandleSentPacket(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(MqttDisconnectReasonCode reason)
|
||||||
|
{
|
||||||
|
IsRunning = false;
|
||||||
|
|
||||||
|
if (reason == MqttDisconnectReasonCode.SessionTakenOver || reason == MqttDisconnectReasonCode.KeepAliveTimeout)
|
||||||
|
{
|
||||||
|
// Is is very important to send the DISCONNECT packet here BEFORE cancelling the
|
||||||
|
// token because the entire connection is closed (disposed) as soon as the cancellation
|
||||||
|
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
|
||||||
|
// at the client!
|
||||||
|
await TrySendDisconnectPacket(reason).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
StopInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
Task ClientAcknowledgedPublishPacket(MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket)
|
||||||
|
{
|
||||||
|
if (_eventContainer.ClientAcknowledgedPublishPacketEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
var eventArgs = new ClientAcknowledgedPublishPacketEventArgs
|
||||||
|
{
|
||||||
|
PublishPacket = publishPacket,
|
||||||
|
AcknowledgePacket = acknowledgePacket,
|
||||||
|
ClientId = Id,
|
||||||
|
SessionItems = Session.Items
|
||||||
|
};
|
||||||
|
|
||||||
|
return _eventContainer.ClientAcknowledgedPublishPacketEvent.TryInvokeAsync(eventArgs, _logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket)
|
||||||
|
{
|
||||||
|
var acknowledgedPublishPacket = Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier);
|
||||||
|
|
||||||
|
if (acknowledgedPublishPacket != null)
|
||||||
|
{
|
||||||
|
return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubAckPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task HandleIncomingPubCompPacket(MqttPubCompPacket pubCompPacket)
|
||||||
|
{
|
||||||
|
var acknowledgedPublishPacket = Session.AcknowledgePublishPacket(pubCompPacket.PacketIdentifier);
|
||||||
|
|
||||||
|
if (acknowledgedPublishPacket != null)
|
||||||
|
{
|
||||||
|
return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubCompPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformAbstractionLayer.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
HandleTopicAlias(publishPacket);
|
||||||
|
|
||||||
|
InterceptingPublishEventArgs interceptingPublishEventArgs = null;
|
||||||
|
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
|
||||||
|
var closeConnection = false;
|
||||||
|
var processPublish = true;
|
||||||
|
|
||||||
|
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, Id, Session.Items);
|
||||||
|
if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
|
||||||
|
{
|
||||||
|
// This can happen if a topic alias us used but the topic is
|
||||||
|
// unknown to the server.
|
||||||
|
interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
|
||||||
|
interceptingPublishEventArgs.ProcessPublish = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
||||||
|
|
||||||
|
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
||||||
|
closeConnection = interceptingPublishEventArgs.CloseConnection;
|
||||||
|
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeConnection)
|
||||||
|
{
|
||||||
|
await StopAsync(MqttDisconnectReasonCode.UnspecifiedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processPublish && applicationMessage != null)
|
||||||
|
{
|
||||||
|
await _sessionsManager.DispatchApplicationMessage(Id, applicationMessage).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (publishPacket.QualityOfServiceLevel)
|
||||||
|
{
|
||||||
|
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||||
|
{
|
||||||
|
// Do nothing since QoS 0 has no ACK at all!
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||||
|
{
|
||||||
|
var pubAckPacket = _packetFactories.PubAck.Create(publishPacket, interceptingPublishEventArgs);
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||||
|
{
|
||||||
|
var pubRecPacket = _packetFactories.PubRec.Create(publishPacket, interceptingPublishEventArgs);
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRecPacket));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("Received a not supported QoS level.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task HandleIncomingPubRecPacket(MqttPubRecPacket pubRecPacket)
|
||||||
|
{
|
||||||
|
var acknowledgedPublishPacket = Session.PeekAcknowledgePublishPacket(pubRecPacket.PacketIdentifier);
|
||||||
|
|
||||||
|
if (acknowledgedPublishPacket != null)
|
||||||
|
{
|
||||||
|
await ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubRecPacket).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket)
|
||||||
|
{
|
||||||
|
var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubCompPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var subscribeResult = await Session.SubscriptionsManager.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var subAckPacket = _packetFactories.SubAck.Create(subscribePacket, subscribeResult);
|
||||||
|
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(subAckPacket));
|
||||||
|
|
||||||
|
if (subscribeResult.CloseConnection)
|
||||||
|
{
|
||||||
|
StopInternal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscribeResult.RetainedMessages != null)
|
||||||
|
{
|
||||||
|
foreach (var retainedApplicationMessage in subscribeResult.RetainedMessages)
|
||||||
|
{
|
||||||
|
var publishPacket = _packetFactories.Publish.Create(retainedApplicationMessage.ApplicationMessage);
|
||||||
|
Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var unsubscribeResult = await Session.SubscriptionsManager.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var unsubAckPacket = _packetFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult);
|
||||||
|
|
||||||
|
Session.EnqueueControlPacket(new MqttPacketBusItem(unsubAckPacket));
|
||||||
|
|
||||||
|
if (unsubscribeResult.CloseConnection)
|
||||||
|
{
|
||||||
|
StopInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleTopicAlias(MqttPublishPacket publishPacket)
|
||||||
|
{
|
||||||
|
if (publishPacket.TopicAlias == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_topicAlias)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(publishPacket.Topic))
|
||||||
|
{
|
||||||
|
_topicAlias[publishPacket.TopicAlias] = publishPacket.Topic;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_topicAlias.TryGetValue(publishPacket.TopicAlias, out var topic))
|
||||||
|
{
|
||||||
|
publishPacket.Topic = topic;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Warning("Client '{0}': Received invalid topic alias ({1}).", Id, publishPacket.TopicAlias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async Task<MqttPacket> InterceptPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
async Task<MqttPacket> InterceptPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!_eventContainer.InterceptingOutboundPacketEvent.HasHandlers)
|
if (!_eventContainer.InterceptingOutboundPacketEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
var interceptingPacketEventArgs = new InterceptingPacketEventArgs
|
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items);
|
||||||
{
|
|
||||||
ClientId = Id,
|
|
||||||
Endpoint = Endpoint,
|
|
||||||
Packet = packet,
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.InterceptingOutboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
await _eventContainer.InterceptingOutboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!interceptingPacketEventArgs.ProcessPacket || packet == null)
|
if (!interceptingPacketEventArgs.ProcessPacket || packet == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return interceptingPacketEventArgs.Packet;
|
return interceptingPacketEventArgs.Packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +377,8 @@ namespace MQTTnet.Server
|
|||||||
// own exception in the reading loop!
|
// own exception in the reading loop!
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
var packet = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
var packet = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||||
if (packet == null)
|
if (packet == null)
|
||||||
{
|
{
|
||||||
@@ -195,22 +386,15 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
var processPacket = true;
|
var processPacket = true;
|
||||||
|
|
||||||
if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
|
if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var interceptingPacketEventArgs = new InterceptingPacketEventArgs
|
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items);
|
||||||
{
|
|
||||||
ClientId = Id,
|
|
||||||
Endpoint = Endpoint,
|
|
||||||
Packet = packet,
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
||||||
packet = interceptingPacketEventArgs.Packet;
|
packet = interceptingPacketEventArgs.Packet;
|
||||||
processPacket = interceptingPacketEventArgs.ProcessPacket;
|
processPacket = interceptingPacketEventArgs.ProcessPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!processPacket || packet == null)
|
if (!processPacket || packet == null)
|
||||||
{
|
{
|
||||||
// Restart the receiving process to get the next packet ignoring the current one..
|
// Restart the receiving process to get the next packet ignoring the current one..
|
||||||
@@ -225,15 +409,15 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
else if (packet is MqttPubAckPacket pubAckPacket)
|
else if (packet is MqttPubAckPacket pubAckPacket)
|
||||||
{
|
{
|
||||||
Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier);
|
await HandleIncomingPubAckPacket(pubAckPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPubCompPacket pubCompPacket)
|
else if (packet is MqttPubCompPacket pubCompPacket)
|
||||||
{
|
{
|
||||||
Session.AcknowledgePublishPacket(pubCompPacket.PacketIdentifier);
|
await HandleIncomingPubCompPacket(pubCompPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPubRecPacket pubRecPacket)
|
else if (packet is MqttPubRecPacket pubRecPacket)
|
||||||
{
|
{
|
||||||
HandleIncomingPubRecPacket(pubRecPacket);
|
await HandleIncomingPubRecPacket(pubRecPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPubRelPacket pubRelPacket)
|
else if (packet is MqttPubRelPacket pubRelPacket)
|
||||||
{
|
{
|
||||||
@@ -315,6 +499,10 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
packetBusItem.MarkAsFailed(exception);
|
packetBusItem.MarkAsFailed(exception);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -347,7 +535,7 @@ namespace MQTTnet.Server
|
|||||||
StopInternal();
|
StopInternal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StopInternal()
|
void StopInternal()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -361,162 +549,12 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleIncomingPubRecPacket(MqttPubRecPacket pubRecPacket)
|
|
||||||
{
|
|
||||||
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket));
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket)
|
|
||||||
{
|
|
||||||
var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubCompPacket));
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var subscribeResult = await Session.SubscriptionsManager.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var subAckPacket = _packetFactories.SubAck.Create(subscribePacket, subscribeResult);
|
|
||||||
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(subAckPacket));
|
|
||||||
|
|
||||||
if (subscribeResult.CloseConnection)
|
|
||||||
{
|
|
||||||
StopInternal();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subscribeResult.RetainedMessages != null)
|
|
||||||
{
|
|
||||||
foreach (var retainedApplicationMessage in subscribeResult.RetainedMessages)
|
|
||||||
{
|
|
||||||
var publishPacket = _packetFactories.Publish.Create(retainedApplicationMessage.ApplicationMessage);
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(publishPacket));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var unsubscribeResult = await Session.SubscriptionsManager.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var unsubAckPacket = _packetFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult);
|
|
||||||
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(unsubAckPacket));
|
|
||||||
|
|
||||||
if (unsubscribeResult.CloseConnection)
|
|
||||||
{
|
|
||||||
StopInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
HandleTopicAlias(publishPacket);
|
|
||||||
|
|
||||||
InterceptingPublishEventArgs interceptingPublishEventArgs = null;
|
|
||||||
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
|
|
||||||
var closeConnection = false;
|
|
||||||
var processPublish = true;
|
|
||||||
|
|
||||||
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
|
||||||
{
|
|
||||||
interceptingPublishEventArgs = new InterceptingPublishEventArgs
|
|
||||||
{
|
|
||||||
ClientId = Id,
|
|
||||||
ApplicationMessage = applicationMessage,
|
|
||||||
SessionItems = Session.Items,
|
|
||||||
ProcessPublish = true,
|
|
||||||
CloseConnection = false,
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
|
|
||||||
{
|
|
||||||
// This can happen if a topic alias us used but the topic is
|
|
||||||
// unknown to the server.
|
|
||||||
interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
|
|
||||||
interceptingPublishEventArgs.ProcessPublish = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
|
||||||
|
|
||||||
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
|
||||||
closeConnection = interceptingPublishEventArgs.CloseConnection;
|
|
||||||
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closeConnection)
|
|
||||||
{
|
|
||||||
await StopAsync(MqttDisconnectReasonCode.UnspecifiedError);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (processPublish && applicationMessage != null)
|
|
||||||
{
|
|
||||||
await _sessionsManager.DispatchApplicationMessage(Id, applicationMessage).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (publishPacket.QualityOfServiceLevel)
|
|
||||||
{
|
|
||||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
|
||||||
{
|
|
||||||
// Do nothing since QoS 0 has no ack at all!
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
|
||||||
{
|
|
||||||
var pubAckPacket = _packetFactories.PubAck.Create(publishPacket, interceptingPublishEventArgs);
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
|
||||||
{
|
|
||||||
var pubRecPacket = _packetFactories.PubRec.Create(publishPacket, interceptingPublishEventArgs);
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRecPacket));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new MqttCommunicationException("Received a not supported QoS level.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleTopicAlias(MqttPublishPacket publishPacket)
|
|
||||||
{
|
|
||||||
if (publishPacket.TopicAlias == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_topicAlias)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(publishPacket.Topic))
|
|
||||||
{
|
|
||||||
_topicAlias[publishPacket.TopicAlias] = publishPacket.Topic;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_topicAlias.TryGetValue(publishPacket.TopicAlias, out var topic))
|
|
||||||
{
|
|
||||||
publishPacket.Topic = topic;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.Warning("Client '{0}': Received invalid topic alias ({1}).", Id, publishPacket.TopicAlias);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task TrySendDisconnectPacket(MqttDisconnectReasonCode reasonCode)
|
async Task TrySendDisconnectPacket(MqttDisconnectReasonCode reasonCode)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var disconnectPacket = _packetFactories.Disconnect.Create(reasonCode);
|
var disconnectPacket = _packetFactories.Disconnect.Create(reasonCode);
|
||||||
|
|
||||||
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
|
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
|
||||||
{
|
{
|
||||||
await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -109,13 +109,9 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_eventContainer.SessionDeletedEvent.HasHandlers)
|
if (_eventContainer.SessionDeletedEvent.HasHandlers && session != null)
|
||||||
{
|
{
|
||||||
var eventArgs = new SessionDeletedEventArgs
|
var eventArgs = new SessionDeletedEventArgs(clientId, session.Items);
|
||||||
{
|
|
||||||
Id = session?.Id
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.SessionDeletedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
await _eventContainer.SessionDeletedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,13 +291,12 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientConnectedEventArgs
|
var eventArgs = new ClientConnectedEventArgs(
|
||||||
{
|
connectPacket.ClientId,
|
||||||
ClientId = connectPacket.ClientId,
|
connectPacket.Username,
|
||||||
UserName = connectPacket.Username,
|
channelAdapter.PacketFormatterAdapter.ProtocolVersion,
|
||||||
ProtocolVersion = channelAdapter.PacketFormatterAdapter.ProtocolVersion,
|
channelAdapter.Endpoint,
|
||||||
Endpoint = channelAdapter.Endpoint
|
client.Session.Items);
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ClientConnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ClientConnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -340,12 +335,8 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (client.Id != null && !client.IsTakenOver && _eventContainer.ClientDisconnectedEvent.HasHandlers)
|
if (client.Id != null && !client.IsTakenOver && _eventContainer.ClientDisconnectedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientDisconnectedEventArgs
|
var disconnectType = client.IsCleanDisconnect ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean;
|
||||||
{
|
var eventArgs = new ClientDisconnectedEventArgs(client.Id, disconnectType, endpoint, client.Session.Items);
|
||||||
ClientId = client.Id,
|
|
||||||
DisconnectType = client.IsCleanDisconnect ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean,
|
|
||||||
Endpoint = endpoint
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -533,13 +524,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientDisconnectedEventArgs
|
var eventArgs = new ClientDisconnectedEventArgs(existing.Id, MqttClientDisconnectType.Takeover, existing.Endpoint, existing.Session.Items);
|
||||||
{
|
|
||||||
ClientId = existing.Id,
|
|
||||||
DisconnectType = MqttClientDisconnectType.Takeover,
|
|
||||||
Endpoint = existing.Endpoint
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -572,12 +557,7 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var eventArgs = new ApplicationMessageNotConsumedEventArgs
|
var eventArgs = new ApplicationMessageNotConsumedEventArgs(applicationMessage, senderId);
|
||||||
{
|
|
||||||
ApplicationMessage = applicationMessage,
|
|
||||||
SenderId = senderId
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ApplicationMessageNotConsumedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ApplicationMessageNotConsumedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,25 +603,25 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
async Task<ValidatingConnectionEventArgs> ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
|
async Task<ValidatingConnectionEventArgs> ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
|
||||||
{
|
{
|
||||||
var context = new ValidatingConnectionEventArgs(connectPacket, channelAdapter)
|
var eventArgs = new ValidatingConnectionEventArgs(connectPacket, channelAdapter)
|
||||||
{
|
{
|
||||||
SessionItems = new ConcurrentDictionary<object, object>()
|
SessionItems = new ConcurrentDictionary<object, object>()
|
||||||
};
|
};
|
||||||
|
|
||||||
await _eventContainer.ValidatingConnectionEvent.InvokeAsync(context).ConfigureAwait(false);
|
await _eventContainer.ValidatingConnectionEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
|
|
||||||
// Check the client ID and set a random one if supported.
|
// Check the client ID and set a random one if supported.
|
||||||
if (string.IsNullOrEmpty(connectPacket.ClientId) && channelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
|
if (string.IsNullOrEmpty(connectPacket.ClientId) && channelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
|
||||||
{
|
{
|
||||||
connectPacket.ClientId = context.AssignedClientIdentifier;
|
connectPacket.ClientId = eventArgs.AssignedClientIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(connectPacket.ClientId))
|
if (string.IsNullOrEmpty(connectPacket.ClientId))
|
||||||
{
|
{
|
||||||
context.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
eventArgs.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return eventArgs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,13 +175,14 @@ namespace MQTTnet.Server
|
|||||||
};
|
};
|
||||||
|
|
||||||
var addedSubscriptions = new List<string>();
|
var addedSubscriptions = new List<string>();
|
||||||
|
var finalTopicFilters = new List<MqttTopicFilter>();
|
||||||
|
|
||||||
// The topic filters are order by its QoS so that the higher QoS will win over a
|
// The topic filters are order by its QoS so that the higher QoS will win over a
|
||||||
// lower one.
|
// lower one.
|
||||||
foreach (var originalTopicFilter in subscribePacket.TopicFilters.OrderByDescending(f => f.QualityOfServiceLevel))
|
foreach (var topicFilterItem in subscribePacket.TopicFilters.OrderByDescending(f => f.QualityOfServiceLevel))
|
||||||
{
|
{
|
||||||
var subscriptionEventArgs = await InterceptSubscribe(originalTopicFilter, cancellationToken).ConfigureAwait(false);
|
var subscriptionEventArgs = await InterceptSubscribe(topicFilterItem, cancellationToken).ConfigureAwait(false);
|
||||||
var finalTopicFilter = subscriptionEventArgs.TopicFilter;
|
var topicFilter = subscriptionEventArgs.TopicFilter;
|
||||||
var processSubscription = subscriptionEventArgs.ProcessSubscription && subscriptionEventArgs.Response.ReasonCode <= MqttSubscribeReasonCode.GrantedQoS2;
|
var processSubscription = subscriptionEventArgs.ProcessSubscription && subscriptionEventArgs.Response.ReasonCode <= MqttSubscribeReasonCode.GrantedQoS2;
|
||||||
|
|
||||||
result.UserProperties = subscriptionEventArgs.UserProperties;
|
result.UserProperties = subscriptionEventArgs.UserProperties;
|
||||||
@@ -195,31 +196,32 @@ namespace MQTTnet.Server
|
|||||||
result.CloseConnection = true;
|
result.CloseConnection = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!processSubscription || string.IsNullOrEmpty(finalTopicFilter.Topic))
|
if (!processSubscription || string.IsNullOrEmpty(topicFilter.Topic))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var createSubscriptionResult = CreateSubscription(finalTopicFilter, subscribePacket.SubscriptionIdentifier, subscriptionEventArgs.Response.ReasonCode);
|
var createSubscriptionResult = CreateSubscription(topicFilter, subscribePacket.SubscriptionIdentifier, subscriptionEventArgs.Response.ReasonCode);
|
||||||
|
|
||||||
addedSubscriptions.Add(finalTopicFilter.Topic);
|
addedSubscriptions.Add(topicFilter.Topic);
|
||||||
|
finalTopicFilters.Add(topicFilter);
|
||||||
if (_eventContainer.ClientSubscribedTopicEvent.HasHandlers)
|
|
||||||
{
|
|
||||||
var eventArgs = new ClientSubscribedTopicEventArgs
|
|
||||||
{
|
|
||||||
ClientId = _session.Id,
|
|
||||||
TopicFilter = finalTopicFilter
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ClientSubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
FilterRetainedApplicationMessages(retainedApplicationMessages, createSubscriptionResult, result);
|
FilterRetainedApplicationMessages(retainedApplicationMessages, createSubscriptionResult, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This call will add the new subscription to the internal storage.
|
||||||
|
// So the event _ClientSubscribedTopicEvent_ must be called afterwards.
|
||||||
_subscriptionChangedNotification?.OnSubscriptionsAdded(_session, addedSubscriptions);
|
_subscriptionChangedNotification?.OnSubscriptionsAdded(_session, addedSubscriptions);
|
||||||
|
|
||||||
|
if (_eventContainer.ClientSubscribedTopicEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
foreach (var finalTopicFilter in finalTopicFilters)
|
||||||
|
{
|
||||||
|
var eventArgs = new ClientSubscribedTopicEventArgs(_session.Id, finalTopicFilter, _session.Items);
|
||||||
|
await _eventContainer.ClientSubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,26 +265,30 @@ namespace MQTTnet.Server
|
|||||||
_subscriptions.Remove(topicFilter);
|
_subscriptions.Remove(topicFilter);
|
||||||
|
|
||||||
// must remove subscription object from topic hash dictionary also
|
// must remove subscription object from topic hash dictionary also
|
||||||
|
if (existingSubscription != null)
|
||||||
if (existingSubscription.TopicHasWildcard)
|
|
||||||
{
|
{
|
||||||
if (_wildcardSubscriptionsByTopicHash.TryGetValue(existingSubscription.TopicHash, out var subs))
|
var topicHash = existingSubscription.TopicHash;
|
||||||
|
|
||||||
|
if (existingSubscription.TopicHasWildcard)
|
||||||
{
|
{
|
||||||
subs.Subscriptions.Remove(existingSubscription);
|
if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
|
||||||
if (subs.Subscriptions.Count == 0)
|
|
||||||
{
|
{
|
||||||
_wildcardSubscriptionsByTopicHash.Remove(existingSubscription.TopicHash);
|
subscriptions.Subscriptions.Remove(existingSubscription);
|
||||||
|
if (subscriptions.Subscriptions.Count == 0)
|
||||||
|
{
|
||||||
|
_wildcardSubscriptionsByTopicHash.Remove(topicHash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(existingSubscription.TopicHash, out var subs))
|
|
||||||
{
|
{
|
||||||
subs.Remove(existingSubscription);
|
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
|
||||||
if (subs.Count == 0)
|
|
||||||
{
|
{
|
||||||
_noWildcardSubscriptionsByTopicHash.Remove(existingSubscription.TopicHash);
|
subscriptions.Remove(existingSubscription);
|
||||||
|
if (subscriptions.Count == 0)
|
||||||
|
{
|
||||||
|
_noWildcardSubscriptionsByTopicHash.Remove(topicHash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,23 +300,14 @@ namespace MQTTnet.Server
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_subscriptionsLock.Release();
|
_subscriptionsLock.Release();
|
||||||
|
_subscriptionChangedNotification?.OnSubscriptionsRemoved(_session, removedSubscriptions);
|
||||||
if (_subscriptionChangedNotification != null)
|
|
||||||
{
|
|
||||||
_subscriptionChangedNotification.OnSubscriptionsRemoved(_session, removedSubscriptions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_eventContainer.ClientUnsubscribedTopicEvent.HasHandlers)
|
if (_eventContainer.ClientUnsubscribedTopicEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientUnsubscribedTopicEventArgs
|
var eventArgs = new ClientUnsubscribedTopicEventArgs(_session.Id, topicFilter, _session.Items);
|
||||||
{
|
|
||||||
ClientId = _session.Id,
|
|
||||||
TopicFilter = topicFilter
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.ClientUnsubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ClientUnsubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,14 +462,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
async Task<InterceptingSubscriptionEventArgs> InterceptSubscribe(MqttTopicFilter topicFilter, CancellationToken cancellationToken)
|
async Task<InterceptingSubscriptionEventArgs> InterceptSubscribe(MqttTopicFilter topicFilter, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var eventArgs = new InterceptingSubscriptionEventArgs
|
var eventArgs = new InterceptingSubscriptionEventArgs(cancellationToken, _session.Id, new MqttSessionStatus(_session), topicFilter);
|
||||||
{
|
|
||||||
ClientId = _session.Id,
|
|
||||||
TopicFilter = topicFilter,
|
|
||||||
SessionItems = _session.Items,
|
|
||||||
Session = new MqttSessionStatus(_session),
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||||
{
|
{
|
||||||
@@ -501,13 +491,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
async Task<InterceptingUnsubscriptionEventArgs> InterceptUnsubscribe(string topicFilter, MqttSubscription mqttSubscription, CancellationToken cancellationToken)
|
async Task<InterceptingUnsubscriptionEventArgs> InterceptUnsubscribe(string topicFilter, MqttSubscription mqttSubscription, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs
|
var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs(cancellationToken, topicFilter, _session.Items, topicFilter);
|
||||||
{
|
|
||||||
ClientId = _session.Id,
|
|
||||||
Topic = topicFilter,
|
|
||||||
SessionItems = _session.Items,
|
|
||||||
CancellationToken = cancellationToken
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mqttSubscription == null)
|
if (mqttSubscription == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -105,13 +105,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
|
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var eventArgs = new RetainedMessageChangedEventArgs
|
var eventArgs = new RetainedMessageChangedEventArgs(clientId, applicationMessage, messagesForSave);
|
||||||
{
|
|
||||||
ClientId = clientId,
|
|
||||||
ChangedRetainedMessage = applicationMessage,
|
|
||||||
StoredRetainedMessages = messagesForSave
|
|
||||||
};
|
|
||||||
|
|
||||||
await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,40 +9,42 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class MqttServerEventContainer
|
public sealed class MqttServerEventContainer
|
||||||
{
|
{
|
||||||
public AsyncEvent<InterceptingSubscriptionEventArgs> InterceptingSubscriptionEvent { get; } = new AsyncEvent<InterceptingSubscriptionEventArgs>();
|
public AsyncEvent<ApplicationMessageNotConsumedEventArgs> ApplicationMessageNotConsumedEvent { get; } = new AsyncEvent<ApplicationMessageNotConsumedEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<InterceptingUnsubscriptionEventArgs> InterceptingUnsubscriptionEvent { get; } = new AsyncEvent<InterceptingUnsubscriptionEventArgs>();
|
public AsyncEvent<ClientAcknowledgedPublishPacketEventArgs> ClientAcknowledgedPublishPacketEvent { get; } = new AsyncEvent<ClientAcknowledgedPublishPacketEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<ClientConnectedEventArgs> ClientConnectedEvent { get; } = new AsyncEvent<ClientConnectedEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<ClientDisconnectedEventArgs> ClientDisconnectedEvent { get; } = new AsyncEvent<ClientDisconnectedEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<ClientSubscribedTopicEventArgs> ClientSubscribedTopicEvent { get; } = new AsyncEvent<ClientSubscribedTopicEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<ClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopicEvent { get; } = new AsyncEvent<ClientUnsubscribedTopicEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<InterceptingPacketEventArgs> InterceptingInboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<InterceptingPacketEventArgs> InterceptingOutboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<InterceptingPublishEventArgs> InterceptingPublishEvent { get; } = new AsyncEvent<InterceptingPublishEventArgs>();
|
public AsyncEvent<InterceptingPublishEventArgs> InterceptingPublishEvent { get; } = new AsyncEvent<InterceptingPublishEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<ValidatingConnectionEventArgs> ValidatingConnectionEvent { get; } = new AsyncEvent<ValidatingConnectionEventArgs>();
|
public AsyncEvent<InterceptingSubscriptionEventArgs> InterceptingSubscriptionEvent { get; } = new AsyncEvent<InterceptingSubscriptionEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<ClientConnectedEventArgs> ClientConnectedEvent { get; } = new AsyncEvent<ClientConnectedEventArgs>();
|
public AsyncEvent<InterceptingUnsubscriptionEventArgs> InterceptingUnsubscriptionEvent { get; } = new AsyncEvent<InterceptingUnsubscriptionEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<ClientDisconnectedEventArgs> ClientDisconnectedEvent { get; } = new AsyncEvent<ClientDisconnectedEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<ClientSubscribedTopicEventArgs> ClientSubscribedTopicEvent { get; } = new AsyncEvent<ClientSubscribedTopicEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<ClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopicEvent { get; } = new AsyncEvent<ClientUnsubscribedTopicEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<EventArgs> PreparingSessionEvent { get; } = new AsyncEvent<EventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<SessionDeletedEventArgs> SessionDeletedEvent { get; } = new AsyncEvent<SessionDeletedEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<ApplicationMessageNotConsumedEventArgs> ApplicationMessageNotConsumedEvent { get; } = new AsyncEvent<ApplicationMessageNotConsumedEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<RetainedMessageChangedEventArgs> RetainedMessageChangedEvent { get; } = new AsyncEvent<RetainedMessageChangedEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<LoadingRetainedMessagesEventArgs> LoadingRetainedMessagesEvent { get; } = new AsyncEvent<LoadingRetainedMessagesEventArgs>();
|
public AsyncEvent<LoadingRetainedMessagesEventArgs> LoadingRetainedMessagesEvent { get; } = new AsyncEvent<LoadingRetainedMessagesEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<EventArgs> PreparingSessionEvent { get; } = new AsyncEvent<EventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<RetainedMessageChangedEventArgs> RetainedMessageChangedEvent { get; } = new AsyncEvent<RetainedMessageChangedEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<EventArgs> RetainedMessagesClearedEvent { get; } = new AsyncEvent<EventArgs>();
|
public AsyncEvent<EventArgs> RetainedMessagesClearedEvent { get; } = new AsyncEvent<EventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<InterceptingPacketEventArgs> InterceptingInboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
|
public AsyncEvent<SessionDeletedEventArgs> SessionDeletedEvent { get; } = new AsyncEvent<SessionDeletedEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<InterceptingPacketEventArgs> InterceptingOutboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
|
|
||||||
|
|
||||||
public AsyncEvent<EventArgs> StartedEvent { get; } = new AsyncEvent<EventArgs>();
|
public AsyncEvent<EventArgs> StartedEvent { get; } = new AsyncEvent<EventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<EventArgs> StoppedEvent { get; } = new AsyncEvent<EventArgs>();
|
public AsyncEvent<EventArgs> StoppedEvent { get; } = new AsyncEvent<EventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<ValidatingConnectionEventArgs> ValidatingConnectionEvent { get; } = new AsyncEvent<ValidatingConnectionEventArgs>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -19,10 +20,11 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
readonly MqttClientSessionsManager _clientSessionsManager;
|
readonly MqttClientSessionsManager _clientSessionsManager;
|
||||||
readonly MqttPacketBus _packetBus = new MqttPacketBus();
|
readonly MqttPacketBus _packetBus = new MqttPacketBus();
|
||||||
|
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
|
|
||||||
readonly MqttServerOptions _serverOptions;
|
readonly MqttServerOptions _serverOptions;
|
||||||
|
|
||||||
readonly Dictionary<ushort, MqttPublishPacket> _unacknowledgedPublishPackets = new Dictionary<ushort, MqttPublishPacket>();
|
readonly ConcurrentDictionary<ushort, MqttPublishPacket> _unacknowledgedPublishPackets = new ConcurrentDictionary<ushort, MqttPublishPacket>();
|
||||||
|
|
||||||
// Bookkeeping to know if this is a subscribing client; lazy initialize later.
|
// Bookkeeping to know if this is a subscribing client; lazy initialize later.
|
||||||
HashSet<string> _subscribedTopics;
|
HashSet<string> _subscribedTopics;
|
||||||
@@ -69,9 +71,18 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public bool WillMessageSent { get; set; }
|
public bool WillMessageSent { get; set; }
|
||||||
|
|
||||||
public void AcknowledgePublishPacket(ushort packetIdentifier)
|
public MqttPublishPacket PeekAcknowledgePublishPacket(ushort packetIdentifier)
|
||||||
{
|
{
|
||||||
_unacknowledgedPublishPackets.Remove(packetIdentifier);
|
// This will only return the matching PUBLISH packet but does not remove it.
|
||||||
|
// This is required for QoS 2.
|
||||||
|
_unacknowledgedPublishPackets.TryGetValue(packetIdentifier, out var publishPacket);
|
||||||
|
return publishPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttPublishPacket AcknowledgePublishPacket(ushort packetIdentifier)
|
||||||
|
{
|
||||||
|
_unacknowledgedPublishPackets.TryRemove(packetIdentifier, out var publishPacket);
|
||||||
|
return publishPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddSubscribedTopic(string topic)
|
public void AddSubscribedTopic(string topic)
|
||||||
@@ -100,6 +111,11 @@ namespace MQTTnet.Server
|
|||||||
SubscriptionsManager.Dispose();
|
SubscriptionsManager.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void EnqueueControlPacket(MqttPacketBusItem packetBusItem)
|
||||||
|
{
|
||||||
|
_packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Control);
|
||||||
|
}
|
||||||
|
|
||||||
public void EnqueueDataPacket(MqttPacketBusItem packetBusItem)
|
public void EnqueueDataPacket(MqttPacketBusItem packetBusItem)
|
||||||
{
|
{
|
||||||
if (_packetBus.ItemsCount(MqttPacketBusPartition.Data) >= _serverOptions.MaxPendingMessagesPerClient)
|
if (_packetBus.ItemsCount(MqttPacketBusPartition.Data) >= _serverOptions.MaxPendingMessagesPerClient)
|
||||||
@@ -118,28 +134,27 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
var publishPacket = (MqttPublishPacket)packetBusItem.Packet;
|
var publishPacket = (MqttPublishPacket)packetBusItem.Packet;
|
||||||
|
|
||||||
if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
if (publishPacket.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||||
{
|
{
|
||||||
|
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||||
|
|
||||||
_unacknowledgedPublishPackets[publishPacket.PacketIdentifier] = publishPacket;
|
_unacknowledgedPublishPackets[publishPacket.PacketIdentifier] = publishPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
_packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Data);
|
_packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnqueueControlPacket(MqttPacketBusItem packetBusItem)
|
|
||||||
{
|
|
||||||
_packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Control);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnqueueHealthPacket(MqttPacketBusItem packetBusItem)
|
public void EnqueueHealthPacket(MqttPacketBusItem packetBusItem)
|
||||||
{
|
{
|
||||||
_packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Health);
|
_packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Health);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Recover()
|
public void Recover()
|
||||||
{
|
{
|
||||||
// TODO: Keep the bus and only insert pending items again.
|
// TODO: Keep the bus and only insert pending items again.
|
||||||
// TODO: Check if packet identifier must be restarted or not.
|
// TODO: Check if packet identifier must be restarted or not.
|
||||||
|
// TODO: Recover package identifier.
|
||||||
_packetBus.Clear();
|
_packetBus.Clear();
|
||||||
|
|
||||||
foreach (var publishPacket in _unacknowledgedPublishPackets.Values.ToList())
|
foreach (var publishPacket in _unacknowledgedPublishPackets.Values.ToList())
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -19,17 +21,18 @@ namespace MQTTnet.Server
|
|||||||
public class MqttServer : Disposable
|
public class MqttServer : Disposable
|
||||||
{
|
{
|
||||||
readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer();
|
readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer();
|
||||||
|
|
||||||
|
readonly IDictionary _sessionItems = new ConcurrentDictionary<object, object>();
|
||||||
readonly ICollection<IMqttServerAdapter> _adapters;
|
readonly ICollection<IMqttServerAdapter> _adapters;
|
||||||
|
readonly MqttClientSessionsManager _clientSessionsManager;
|
||||||
|
readonly MqttServerKeepAliveMonitor _keepAliveMonitor;
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
readonly MqttServerOptions _options;
|
readonly MqttServerOptions _options;
|
||||||
readonly IMqttNetLogger _rootLogger;
|
|
||||||
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||||
readonly MqttServerKeepAliveMonitor _keepAliveMonitor;
|
readonly IMqttNetLogger _rootLogger;
|
||||||
readonly MqttClientSessionsManager _clientSessionsManager;
|
|
||||||
|
|
||||||
CancellationTokenSource _cancellationTokenSource;
|
CancellationTokenSource _cancellationTokenSource;
|
||||||
|
|
||||||
public MqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
public MqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
@@ -55,6 +58,12 @@ namespace MQTTnet.Server
|
|||||||
remove => _eventContainer.ApplicationMessageNotConsumedEvent.RemoveHandler(value);
|
remove => _eventContainer.ApplicationMessageNotConsumedEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event Func<ClientAcknowledgedPublishPacketEventArgs, Task> ClientAcknowledgedPublishPacketAsync
|
||||||
|
{
|
||||||
|
add => _eventContainer.ClientAcknowledgedPublishPacketEvent.AddHandler(value);
|
||||||
|
remove => _eventContainer.ClientAcknowledgedPublishPacketEvent.RemoveHandler(value);
|
||||||
|
}
|
||||||
|
|
||||||
public event Func<ClientConnectedEventArgs, Task> ClientConnectedAsync
|
public event Func<ClientConnectedEventArgs, Task> ClientConnectedAsync
|
||||||
{
|
{
|
||||||
add => _eventContainer.ClientConnectedEvent.AddHandler(value);
|
add => _eventContainer.ClientConnectedEvent.AddHandler(value);
|
||||||
@@ -158,7 +167,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool IsStarted => _cancellationTokenSource != null;
|
public bool IsStarted => _cancellationTokenSource != null;
|
||||||
|
|
||||||
public Task DeleteRetainedMessagesAsync()
|
public Task DeleteRetainedMessagesAsync()
|
||||||
{
|
{
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
@@ -174,10 +183,10 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
|
|
||||||
return _clientSessionsManager.GetClient(id).StopAsync(reasonCode);
|
return _clientSessionsManager.GetClient(id).StopAsync(reasonCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IList<MqttClientStatus>> GetClientsAsync()
|
public Task<IList<MqttClientStatus>> GetClientsAsync()
|
||||||
{
|
{
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
@@ -199,7 +208,7 @@ namespace MQTTnet.Server
|
|||||||
return _clientSessionsManager.GetSessionStatusAsync();
|
return _clientSessionsManager.GetSessionStatusAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage)
|
public async Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage)
|
||||||
{
|
{
|
||||||
if (injectedApplicationMessage == null)
|
if (injectedApplicationMessage == null)
|
||||||
{
|
{
|
||||||
@@ -215,7 +224,34 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
|
|
||||||
return _clientSessionsManager.DispatchApplicationMessage(injectedApplicationMessage.SenderClientId, injectedApplicationMessage.ApplicationMessage);
|
var processPublish = true;
|
||||||
|
var applicationMessage = injectedApplicationMessage.ApplicationMessage;
|
||||||
|
|
||||||
|
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
var interceptingPublishEventArgs = new InterceptingPublishEventArgs(
|
||||||
|
applicationMessage,
|
||||||
|
_cancellationTokenSource.Token,
|
||||||
|
injectedApplicationMessage.SenderClientId,
|
||||||
|
_sessionItems);
|
||||||
|
|
||||||
|
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
||||||
|
|
||||||
|
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
||||||
|
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!processPublish)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(applicationMessage.Topic))
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Injected application messages must contain a topic. Topic alias is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await _clientSessionsManager.DispatchApplicationMessage(injectedApplicationMessage.SenderClientId, applicationMessage).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync()
|
public async Task StartAsync()
|
||||||
@@ -236,7 +272,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _eventContainer.StartedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
await _eventContainer.StartedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.Info("Started.");
|
_logger.Info("Started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +302,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _eventContainer.StoppedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
await _eventContainer.StoppedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.Info("Stopped.");
|
_logger.Info("Stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +346,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
|
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task UpdateRetainedMessageAsync(MqttApplicationMessage retainedMessage)
|
public Task UpdateRetainedMessageAsync(MqttApplicationMessage retainedMessage)
|
||||||
{
|
{
|
||||||
if (retainedMessage == null)
|
if (retainedMessage == null)
|
||||||
@@ -347,7 +383,7 @@ namespace MQTTnet.Server
|
|||||||
void ThrowIfNotStarted()
|
void ThrowIfNotStarted()
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
if (_cancellationTokenSource == null)
|
if (_cancellationTokenSource == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The MQTT server is not started.");
|
throw new InvalidOperationException("The MQTT server is not started.");
|
||||||
@@ -357,7 +393,7 @@ namespace MQTTnet.Server
|
|||||||
void ThrowIfStarted()
|
void ThrowIfStarted()
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
if (_cancellationTokenSource != null)
|
if (_cancellationTokenSource != null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The MQTT server is already started.");
|
throw new InvalidOperationException("The MQTT server is already started.");
|
||||||
|
|||||||
Reference in New Issue
Block a user