Compare commits

..
Author SHA1 Message Date
Christian c0c5f20c5a Add samples. 2022-08-07 12:34:10 +02:00
Christian 9152fe637e Reset release notes. 2022-08-07 09:56:25 +02:00
Christian b976e9c177 Fix build errors. 2022-08-03 20:44:58 +02:00
Christian 262a4681ad Merge branch '1291-access-client-status-in-client-disconnected-handler' 2022-08-03 20:40:53 +02:00
Christian ad546adafe Fix removal of event handlers from async event. 2022-08-03 20:27:53 +02:00
Christian 29a9941d86 Fix breaking changes. 2022-07-31 11:10:38 +02:00
Christian 5a21939e29 Squashed commit of the following:
commit 469caf656e
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sun Jul 31 10:55:40 2022 +0200

    Fix build errors.

commit 42402fba04
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sun Jul 31 10:22:23 2022 +0200

    Allow interception of injected application messages.

commit a0dc8ab0d7
Author: William Lee <31092948+william19821101@users.noreply.github.com>
Date:   Sun Jul 31 15:50:14 2022 +0800

    Update Server_Simple_Samples.cs (#1474)

    Change e.UserName to e.Username
2022-07-31 11:09:39 +02:00
Christian 469caf656e Fix build errors. 2022-07-31 10:55:40 +02:00
Christian 42402fba04 Allow interception of injected application messages. 2022-07-31 10:22:23 +02:00
William Lee a0dc8ab0d7 Update Server_Simple_Samples.cs (#1474)
Change e.UserName to e.Username
2022-07-31 09:50:14 +02:00
Christian 02ede5a2d8 Update MqttClient.cs 2022-07-24 10:44:27 +02:00
Christian cef61900df Merge branch 'master' into 1291-access-client-status-in-client-disconnected-handler 2022-07-23 18:09:05 +02:00
Christian 09385b1b36 Pass session item to several more event handers. 2022-07-23 18:07:49 +02:00
Christian cbf413f04e Squashed commit of the following: 1125-expose-http-context-in-validateconnectionasync
commit 353c9671a04a273798b0b3775be5f0764bfd7edf
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 23 15:33:01 2022 +0200

    Remove usage of obsolete stuff.

commit 1f3c35685c7a65453c37a37e8c54a2ca4305ff6d
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 23 15:30:54 2022 +0200

    Update ValidatingConnectionEventArgs.cs

commit a5e1b8938974b5b57e95ec41d74634ca130a8ad5
Merge: 8878efee 11249d00
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 23 15:30:30 2022 +0200

    Merge branch 'master' into 1125-expose-http-context-in-validateconnectionasync

commit 8878efee0da3c67d90bb7fb6946453f62329cd8e
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 23 15:30:03 2022 +0200

    Expose channel adapter when validating connections.
2022-07-23 15:33:53 +02:00
Christian 11249d006f Add samples. 2022-07-23 15:10:20 +02:00
Christian 0d7b020994 Squashed commit of the following: 487-any-way-to-generate-events-on-receipt-of-puback-or-pubrec-packets
commit 3f2f8d4b0a31b20734eca3fff86c43bd1ac3e3ad
Merge: 34babbf2 d71ff3d6
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 23 11:06:22 2022 +0200

    Merge branch 'master' into 487-any-way-to-generate-events-on-receipt-of-puback-or-pubrec-packets

commit 34babbf2714b742bc4f3802804a1a07159d38e22
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Mon Jul 11 22:56:01 2022 +0200

    Fix typo.

commit add6277458f528fc03bf4a1ba228a55c5a8d1a80
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jul 2 12:50:13 2022 +0200

    Add new event for client acknowledges.
2022-07-23 11:08:53 +02:00
Christian d71ff3d615 Update ReleaseNotes.md 2022-07-23 11:05:14 +02:00
Christian 57745e8324 Merge branch 'master' into 1311-how-does-mqttclient-specify-the-network-card-to-send-data 2022-07-23 11:03:06 +02:00
Christian a94776eeea Update version information. 2022-07-23 11:02:00 +02:00
Christian d3848d8cb0 Add comments. 2022-07-23 11:00:21 +02:00
Alexander Köpke 6c29ea56fc Add PackageIdentifier to MqttApplicationMessageReceivedEventArgs (#1466)
Provide `PackageIdentifier` as read only property in `MqttApplicationMessageReceivedEventArgs`
2022-07-23 10:37:57 +02:00
Christian ed7c150cc5 Add new options. 2022-07-19 20:47:16 +02:00
Christian 13762eedd4 Update Server_Asp_Net_Samples.cs 2022-07-19 17:33:01 +02:00
Christian e33495010e Update samples. 2022-07-13 15:29:26 +02:00
Christian b0d1fcd161 Refactor code. 2022-07-12 16:37:32 +02:00
Christian f7a739e162 Fix check for skipped application messages. 2022-07-12 12:02:00 +02:00
Christian 422945929e Merge branch 'Fix-high-CPU-usage' 2022-07-12 11:57:03 +02:00
Christian 76e54dc49b Update ReleaseNotes.md 2022-07-12 11:56:50 +02:00
Christian b10a69a0ae Update release notes. 2022-07-01 17:03:33 +02:00
Torstein Lundervold Nesheim 4b1c7cf772 Use IgnoreCertificateChainErrors option in TcpChannel (#1447) 2022-07-01 17:00:13 +02:00
Christian 3f67df007b Remove obsolete files etc. 2022-07-01 16:55:13 +02:00
Christian 36b1ed06f0 Update MqttClient.cs 2022-07-01 16:54:53 +02:00
Christian 4a982ea228 Add Tests. 2022-06-29 18:58:02 +02:00
Christian 9fe5073186 Use TopicFilter instead of QoS only. 2022-06-29 18:17:48 +02:00
Christian 305c552474 Squashed commit of the following:
commit 0cbf50427ca6fb343cfa22ccbcd06aad5ea2b4c2
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Mon Jun 20 21:02:50 2022 +0200

    Fix firing of _ClientSubscribedTopicAsync_ event.
2022-06-27 20:41:34 +02:00
Christian f632fbb447 Squashed commit of the following:
commit c3ca0ef7da4bcc2103cda2bb175ea3c5a49da2e6
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sat Jun 25 22:58:18 2022 +0200

    Update nugets and fix breaking changes.
2022-06-27 20:40:37 +02:00
Christian 8231d62f9f Squashed commit of the following:
commit 9f289152736a682a8c31fbe7720dade37dbb1cb5
Author: Christian <6939810+chkr1011@users.noreply.github.com>
Date:   Sun Jun 26 13:38:35 2022 +0200

    Fix message storage.
2022-06-27 20:39:53 +02:00
Christian a52a717965 Update ReleaseNotes.md 2022-06-26 13:40:21 +02:00
Christian 2b653f2379 Update ReleaseNotes.md 2022-06-25 23:00:16 +02:00
Christian 725cfb643f Set MaximumQoS properly. 2022-06-24 18:52:50 +02:00
Christian 29ec6edbf4 Update Server_Simple_Samples.cs 2022-06-24 18:46:38 +02:00
Christian f3ef2dbf7c Add samples. 2022-06-18 11:56:19 +02:00
Christian 14eb202873 Add new samples. 2022-06-18 11:38:36 +02:00
85 changed files with 1731 additions and 775 deletions
-1
View File
@@ -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 -->
- Client
- ManagedClient
- MQTTnet.Server standalone
- Server
- 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 -->
- Client
- ManagedClient
- MQTTnet.Server standalone
- Server
- Generic
-6
View File
@@ -1,6 +0,0 @@
* [Client] Added the flag _try_private_ (MQTT 3.1.1 Bridge) to client options (#1413).
* [Client] Fixed MQTTv5 protocol violation in PUBLISH packets (#1423).
* [Client] Added missing "WithWill..." methods in _MqttClientOptionsBuilder_.
* [ManagedClient] Fixed wrong event args type for connected and disconnected events (#1432).
* [Server] Fixed wrong handling of retained messages with QoS > 0 (#1434).
* [Server] Connections with _try_private_ flag (MQTT 3.1.1 Bridge) are now accepted (#1413).
+1 -1
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request]
env:
VERSION: "4.0.1.${{github.run_number}}"
VERSION: "4.1.0.${{github.run_number}}"
jobs:
build:
+4 -1
View File
@@ -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/TagAttributesFormat/@EntryValue">OnDifferentLines</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;
&lt;Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"&gt;
&lt;Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"&gt;
&lt;TypePattern DisplayName="Non-reorderable types"&gt;
&lt;TypePattern.Match&gt;
&lt;Or&gt;
@@ -226,6 +226,9 @@
&lt;/Entry&gt;
&lt;/TypePattern&gt;
&lt;/Patterns&gt;</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_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
+85 -8
View File
@@ -2,10 +2,15 @@
// 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.Security.Authentication;
using MQTTnet.Client;
using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter;
using MQTTnet.Samples.Helpers;
using MQTTnet.Samples.Shared;
namespace MQTTnet.Samples.Client;
@@ -111,13 +116,21 @@ public static class Client_Connection_Samples
.WithTls(
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;
})
.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();
}
}
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()
{
@@ -159,19 +196,59 @@ public static class Client_Connection_Samples
.WithTls(
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();
// 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()
{
/*
+4
View File
@@ -2,6 +2,10 @@
// 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;
namespace MQTTnet.Samples.Client;
+5 -1
View File
@@ -2,8 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using MQTTnet.Client;
using MQTTnet.Samples.Helpers;
using MQTTnet.Samples.Shared;
namespace MQTTnet.Samples.Client;
+29 -11
View File
@@ -2,8 +2,13 @@
// 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;
using MQTTnet.Diagnostics;
using MQTTnet.Samples.Shared;
namespace MQTTnet.Samples.Diagnostics;
@@ -18,9 +23,7 @@ public static class Logger_Samples
var mqttFactory = new MqttFactory(new MyLogger());
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
@@ -28,13 +31,31 @@ public static class Logger_Samples
Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build();
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
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()
{
/*
@@ -60,9 +81,7 @@ public static class Logger_Samples
var mqttFactory = new MqttFactory(mqttEventLogger);
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
@@ -70,8 +89,7 @@ public static class Logger_Samples
Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build();
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
@@ -79,7 +97,7 @@ public static class Logger_Samples
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)
{
@@ -2,6 +2,10 @@
// 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.Diagnostics;
namespace MQTTnet.Samples.Diagnostics;
+1 -2
View File
@@ -15,8 +15,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.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.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.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.Extensions.ManagedClient;
+10 -3
View File
@@ -8,6 +8,7 @@ Console.WriteLine("Welcome to MQTTnet samples!");
Console.WriteLine();
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
sampleClasses.RemoveAll(c => c.Namespace.Contains(".Shared"));
var index = 0;
foreach (var sampleClass in sampleClasses)
@@ -39,10 +40,16 @@ Console.WriteLine();
try
{
var task = selectedSampleMethod.Invoke(null, null) as Task;
task?.Wait();
if (selectedSampleMethod.Invoke(null, null) is Task task)
{
await task;
}
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
}
Console.WriteLine();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
+4
View File
@@ -2,6 +2,10 @@
// 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.Extensions.Rpc;
using MQTTnet.Protocol;
+110
View File
@@ -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();
}
}
}
+97 -15
View File
@@ -2,34 +2,107 @@
// 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.Protocol;
using MQTTnet.Samples.Shared;
using MQTTnet.Server;
namespace MQTTnet.Samples.Server;
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()
{
/*
* This sample starts a simple MQTT server which will accept any TCP connection.
*/
var mqttFactory = new MqttFactory();
// The port for the default endpoint is 1883.
// The default endpoint is NOT encrypted!
// Use the builder classes where possible.
var mqttServerOptions = new MqttServerOptionsBuilder()
.WithDefaultEndpoint()
.Build();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
// The port can be changed using the following API (not used in this example).
new MqttServerOptionsBuilder()
.WithDefaultEndpoint()
.WithDefaultEndpointPort(1234)
.Build();
// new MqttServerOptionsBuilder()
// .WithDefaultEndpoint()
// .WithDefaultEndpointPort(1234)
// .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))
{
await mqttServer.StartAsync();
@@ -52,9 +125,7 @@ public static class Server_Simple_Samples
var mqttFactory = new MqttFactory();
var mqttServerOptions = new MqttServerOptionsBuilder()
.WithDefaultEndpoint()
.Build();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
{
@@ -67,7 +138,7 @@ public static class Server_Simple_Samples
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
}
if (e.Username != "ValidUser")
if (e.UserName != "ValidUser")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
@@ -88,4 +159,15 @@ public static class Server_Simple_Samples
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;
}
}
+53
View File
@@ -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;
namespace MQTTnet.Samples.Helpers;
namespace MQTTnet.Samples.Shared;
internal static class ObjectExtensions
{
@@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
</ItemGroup>
@@ -43,7 +43,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -46,7 +46,7 @@ namespace MQTTnet.Benchmarks
_stream.Position = 0;
var channel = new TestMqttChannel(_stream);
var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
}
+1 -1
View File
@@ -8,7 +8,7 @@ using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class LoggerBenchmark
@@ -12,7 +12,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
</ItemGroup>
@@ -6,11 +6,10 @@ using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Client;
using MQTTnet.Server;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class MessageProcessingBenchmark
@@ -13,7 +13,7 @@ using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MessageProcessingMqttConnectionContextBenchmark
{
@@ -7,7 +7,7 @@ using MQTTnet.Tests.Mockups;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark
{
@@ -14,7 +14,7 @@ using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public sealed class MqttTcpChannelBenchmark
{
@@ -5,7 +5,7 @@ using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class RoundtripProcessingBenchmark
@@ -17,7 +17,7 @@ using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class SerializerBenchmark : BaseBenchmark
@@ -5,7 +5,7 @@ using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ServerProcessingBenchmark
@@ -13,7 +13,7 @@ using MQTTnet.AspNetCore.Client.Tcp;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class TcpPipesBenchmark
{
@@ -8,7 +8,7 @@ using BenchmarkDotNet.Jobs;
namespace MQTTnet.Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark
@@ -57,7 +57,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
@@ -51,9 +51,9 @@ namespace MQTTnet.Extensions.ManagedClient
/// cref="MaintainConnectionAsync" />
/// .
/// </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 HashSet<string> _unsubscriptions = new HashSet<string>();
@@ -201,7 +201,7 @@ namespace MQTTnet.Extensions.ManagedClient
}
finally
{
if (_applicationMessageSkippedEvent.HasHandlers)
if (applicationMessageSkippedEventArgs != null && _applicationMessageSkippedEvent.HasHandlers)
{
await _applicationMessageSkippedEvent.InvokeAsync(applicationMessageSkippedEventArgs).ConfigureAwait(false);
}
@@ -289,7 +289,7 @@ namespace MQTTnet.Extensions.ManagedClient
{
foreach (var topicFilter in topicFilters)
{
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
_subscriptions[topicFilter.Topic] = topicFilter;
_unsubscriptions.Remove(topicFilter.Topic);
}
}
@@ -455,18 +455,11 @@ namespace MQTTnet.Extensions.ManagedClient
{
if (_reconnectSubscriptions.Any())
{
var subscriptions = _reconnectSubscriptions.Select(
i => new MqttTopicFilter
{
Topic = i.Key,
QualityOfServiceLevel = i.Value
});
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)
{
@@ -495,14 +488,7 @@ namespace MQTTnet.Extensions.ManagedClient
lock (_subscriptions)
{
subscriptions = _subscriptions.Select(
i => new MqttTopicFilter
{
Topic = i.Key,
QualityOfServiceLevel = i.Value
})
.ToList();
subscriptions = _subscriptions.Values.ToList();
_subscriptions.Clear();
unsubscriptions = new HashSet<string>(_unsubscriptions);
@@ -523,7 +509,7 @@ namespace MQTTnet.Extensions.ManagedClient
foreach (var subscription in subscriptions)
{
_reconnectSubscriptions[subscription.Topic] = subscription.QualityOfServiceLevel;
_reconnectSubscriptions[subscription.Topic] = subscription;
}
var addedTopicFilters = new List<MqttTopicFilter>();
@@ -57,7 +57,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
@@ -57,15 +57,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="WebSocket4Net" Version="0.15.2"/>
<PackageReference Include="WebSocket4Net" Version="0.15.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj" />
</ItemGroup>
</Project>
@@ -98,7 +98,6 @@ namespace MQTTnet.Extensions.WebSocket4Net
#else
certificates.Add(certificate);
#endif
}
}
@@ -12,7 +12,7 @@
</PropertyGroup>
<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.Net.Compilers" Version="4.0.1" />
</ItemGroup>
@@ -23,10 +23,4 @@
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Start.bat">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+1 -1
View File
@@ -118,7 +118,7 @@ namespace MQTTnet.TestApp
{
if (e.ClientId == "SpecialClient")
{
if (e.Username != "USER" || e.Password != "PASS")
if (e.UserName != "USER" || e.Password != "PASS")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
-1
View File
@@ -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 channel = new TestMqttChannel(headerStream))
using (var channel = new MemoryMqttChannel(headerStream))
{
using (var adapter = new MqttChannelAdapter(
channel,
@@ -583,7 +583,7 @@ namespace MQTTnet.Tests
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());
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
@@ -603,7 +603,7 @@ namespace MQTTnet.Tests
var serializer = MqttPacketFormatterAdapter.GetMqttPacketFormatter(protocolVersion, writer);
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());
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);
}
}
}
}
+3 -3
View File
@@ -10,9 +10,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
+1 -1
View File
@@ -212,7 +212,7 @@ namespace MQTTnet.Tests.MQTTv5
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");
@@ -10,16 +10,16 @@ using MQTTnet.Channel;
namespace MQTTnet.Tests.Mockups
{
public sealed class TestMqttChannel : IMqttChannel
public sealed class MemoryMqttChannel : IMqttChannel
{
readonly MemoryStream _stream;
public TestMqttChannel(MemoryStream stream)
public MemoryMqttChannel(MemoryStream stream)
{
_stream = stream;
}
public TestMqttChannel(byte[] buffer)
public MemoryMqttChannel(byte[] 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)
{
using (var channel = new TestMqttChannel(buffer.ToArray()))
using (var channel = new MemoryMqttChannel(buffer.ToArray()))
{
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
+2 -144
View File
@@ -798,38 +798,7 @@ namespace MQTTnet.Tests.Server
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]
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]
public async Task Use_Empty_Client_ID()
{
@@ -986,98 +935,7 @@ namespace MQTTnet.Tests.Server
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)
{
if (_connected.ContainsKey(eventArgs.ClientId))
+124
View File
@@ -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);
}
}
}
}
+103 -28
View File
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter;
@@ -15,6 +16,104 @@ namespace MQTTnet.Tests.Server
[TestClass]
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]
public async Task Use_Username_Null_Password_Empty()
{
@@ -29,10 +128,7 @@ namespace MQTTnet.Tests.Server
var client = testEnvironment.CreateClient();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithCredentials(username, password)
.Build();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(username, password).Build();
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions));
Assert.IsInstanceOfType(ex.InnerException, typeof(MqttProtocolViolationException));
@@ -41,25 +137,7 @@ namespace MQTTnet.Tests.Server
}
}
[TestMethod]
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)
async Task TestCredentials(string userName, string password)
{
using (var testEnvironment = CreateTestEnvironment())
{
@@ -69,7 +147,7 @@ namespace MQTTnet.Tests.Server
server.ValidatingConnectionAsync += e =>
{
if (e.Username != "UserName1")
if (e.UserName != "UserName1")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
@@ -84,10 +162,7 @@ namespace MQTTnet.Tests.Server
var client = testEnvironment.CreateClient();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithCredentials(userName, password)
.Build();
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build();
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions));
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
+84 -1
View File
@@ -14,7 +14,6 @@ using MQTTnet.Implementations;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
using MqttClient = MQTTnet.Client.MqttClient;
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]
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)
{
try
+26 -1
View File
@@ -12,6 +12,7 @@ using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.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]
public async Task Intercept_Subscription()
{
@@ -101,7 +126,7 @@ namespace MQTTnet.Tests.Server
.WithTopicFilter("c", MqttQualityOfServiceLevel.ExactlyOnce)
.WithTopicFilter("d")
.Build();
var response = await client.SubscribeAsync(subscribeOptions);
Assert.AreEqual(subscribeOptions.TopicFilters.Count, response.Items.Count);
+71
View File
@@ -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);
}
}
}
}
@@ -264,8 +264,7 @@ namespace MQTTnet.Client
return this;
}
// TODO: Consider creating _MqttClientTcpOptionsBuilder_ as overload.
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
{
if (optionsBuilder == null)
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Net;
using System.Net.Sockets;
namespace MQTTnet.Client
@@ -10,8 +11,19 @@ namespace MQTTnet.Client
{
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;
/// <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 LingerOption LingerState { get; set; } = new LingerOption(true, 0);
@@ -7,23 +7,23 @@ using System.Net;
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 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 string Uri { get; set; }
public override string ToString()
{
return Uri;
}
}
}
}
@@ -4,7 +4,7 @@
namespace MQTTnet.Client
{
public class MqttClientWebSocketProxyOptions
public sealed class MqttClientWebSocketProxyOptions
{
public string Address { get; set; }
@@ -25,6 +25,7 @@ namespace MQTTnet.Client
ClientId = clientId;
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
PublishPacket = publishPacket;
PacketIdentifier = publishPacket.PacketIdentifier;
_acknowledgeHandler = acknowledgeHandler;
}
@@ -62,12 +63,17 @@ namespace MQTTnet.Client
public bool IsHandled { get; set; }
/// <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>
public bool AutoAcknowledge { get; set; } = true;
public object Tag { get; set; }
/// <summary>
/// Gets the indentifier of MQTT packet
/// </summary>
public ushort PacketIdentifier { get; }
public Task AcknowledgeAsync(CancellationToken cancellationToken)
{
if (_acknowledgeHandler == null)
@@ -4,6 +4,7 @@
using System;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Formatter
@@ -25,6 +26,7 @@ namespace MQTTnet.Formatter
SubscriptionIdentifiersAvailable = true,
SharedSubscriptionAvailable = false,
TopicAliasMaximum = ushort.MaxValue,
MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce,
WildcardSubscriptionAvailable = true,
AuthenticationMethod = validatingConnectionEventArgs.AuthenticationMethod,
@@ -32,7 +34,11 @@ namespace MQTTnet.Formatter
AssignedClientIdentifier = validatingConnectionEventArgs.AssignedClientIdentifier,
ReasonString = validatingConnectionEventArgs.ReasonString,
ServerReference = validatingConnectionEventArgs.ServerReference,
UserProperties = validatingConnectionEventArgs.ResponseUserProperties
UserProperties = validatingConnectionEventArgs.ResponseUserProperties,
ResponseInformation = null,
MaximumPacketSize = 0, // Unlimited,
ReceiveMaximum = 0 // Unlimited
};
return connAckPacket;
@@ -72,11 +72,11 @@ namespace MQTTnet.Formatter.V5
if (value == MqttQualityOfServiceLevel.AtLeastOnce)
{
Write(MqttPropertyId.MaximumQoS, true);
Write(MqttPropertyId.MaximumQoS, 0x1);
}
else
{
Write(MqttPropertyId.MaximumQoS, false);
Write(MqttPropertyId.MaximumQoS, 0x0);
}
}
@@ -3,6 +3,9 @@
// See the LICENSE file in the project root for more information.
#if !WINDOWS_UWP
using MQTTnet.Channel;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using System;
using System.IO;
using System.Net.Security;
@@ -11,9 +14,6 @@ using System.Runtime.ExceptionServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Client;
using MQTTnet.Exceptions;
namespace MQTTnet.Implementations
{
@@ -68,6 +68,11 @@ namespace MQTTnet.Implementations
socket = new CrossPlatformSocket(_tcpOptions.AddressFamily);
}
if (_tcpOptions.LocalEndpoint != null)
{
socket.Bind(_tcpOptions.LocalEndpoint);
}
socket.ReceiveBufferSize = _tcpOptions.BufferSize;
socket.SendBufferSize = _tcpOptions.BufferSize;
socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds;
@@ -77,7 +82,7 @@ namespace MQTTnet.Implementations
{
socket.LingerState = _tcpOptions.LingerState;
}
if (_tcpOptions.DualMode.HasValue)
{
// 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,
ClientCertificates = LoadCertificates(),
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,
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy
};
await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
#else
await sslStream.AuthenticateAsClientAsync(_tcpOptions.Server, LoadCertificates(), _tcpOptions.TlsOptions.SslProtocol,
!_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
await sslStream.AuthenticateAsClientAsync(
_tcpOptions.Server,
LoadCertificates(),
_tcpOptions.TlsOptions.SslProtocol,
!_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors)
.ConfigureAwait(false);
#endif
}
catch
@@ -269,6 +279,11 @@ namespace MQTTnet.Implementations
return certificateValidationHandler(eventArgs);
}
if (_tcpOptions?.TlsOptions?.IgnoreCertificateChainErrors ?? false)
{
sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
}
return sslPolicyErrors == SslPolicyErrors.None;
}
@@ -118,7 +118,6 @@ namespace MQTTnet.Implementations
void SetupClientWebSocket(ClientWebSocket clientWebSocket)
{
if (_options.ProxyOptions != null)
{
clientWebSocket.Options.Proxy = CreateProxy();
@@ -21,12 +21,14 @@ namespace MQTTnet.Internal
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)
{
return ReferenceEquals(_asyncHandler, handler);
// Do not use ReferenceEquals! It will not work with delegates.
return handler == _asyncHandler;
}
public Task InvokeAsync(TEventArgs eventArgs)
+1 -1
View File
@@ -76,7 +76,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
@@ -8,14 +8,20 @@ namespace MQTTnet.Server
{
public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs
{
public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
SenderId = senderId;
}
/// <summary>
/// Gets the application message which was not consumed by any client.
/// </summary>
public MqttApplicationMessage ApplicationMessage { get; internal set; }
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Gets the ID of the client which has sent the affected application message.
/// </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.
using System;
using System.Collections;
using MQTTnet.Formatter;
namespace MQTTnet.Server
{
public sealed class ClientConnectedEventArgs : EventArgs
{
/// <summary>
/// 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>
public string ClientId { get; internal set; }
public ClientConnectedEventArgs(string clientId, string userName, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
ProtocolVersion = protocolVersion;
Endpoint = endpoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <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>
public string UserName { get; internal set; }
public string ClientId { get; }
/// <summary>
/// Gets the protocol version which is used by the connected client.
/// Gets the endpoint of the connected client.
/// </summary>
public MqttProtocolVersion ProtocolVersion { get; internal set; }
public string Endpoint { get; }
/// <summary>
/// Gets the endpoint of the connected client.
/// Gets the protocol version which is used by the connected client.
/// </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.
using System;
using System.Collections;
namespace MQTTnet.Server
{
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>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </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.
using System;
using System.Collections;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public sealed class ClientSubscribedTopicEventArgs : EventArgs
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public ClientSubscribedTopicEventArgs(string clientId, MqttTopicFilter topicFilter, IDictionary sessionItems)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <summary>
/// Gets the topic filter.
/// The topic filter can contain topics and wildcards.
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </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.
using System;
using System.Collections;
namespace MQTTnet.Server
{
public sealed class ClientUnsubscribedTopicEventArgs : EventArgs
{
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public ClientUnsubscribedTopicEventArgs(string clientId, string topicFilter, IDictionary sessionItems)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <summary>
/// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards.
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </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.
using System;
using System.Collections;
using System.Threading;
using MQTTnet.Packets;
@@ -10,30 +11,44 @@ namespace MQTTnet.Server
{
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>
/// 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>
public string ClientId { get; internal set; }
public CancellationToken CancellationToken { get; }
/// <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>
public string Endpoint { get; internal set; }
public string ClientId { get; }
/// <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>
public MqttPacket Packet { get; set; }
/// <summary>
/// Gets or sets whether the packet should be processed or not.
/// Gets or sets whether the packet should be processed or not.
/// </summary>
public bool ProcessPacket { get; set; } = true;
/// <summary>
/// Gets the cancellation token from the connection managing thread.
/// Use this in further event processing.
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
public IDictionary SessionItems { get; }
}
}
@@ -10,18 +10,26 @@ namespace MQTTnet.Server
{
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; }
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
public CancellationToken CancellationToken { get; }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public string ClientId { get; }
public bool CloseConnection { get; set; }
@@ -38,6 +46,6 @@ namespace MQTTnet.Server
/// <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; internal set; }
public IDictionary SessionItems { get; }
}
}
@@ -12,16 +12,28 @@ namespace MQTTnet.Server
{
public sealed class InterceptingSubscriptionEventArgs : EventArgs
{
public InterceptingSubscriptionEventArgs(
CancellationToken cancellationToken,
string clientId,
MqttSessionStatus session,
MqttTopicFilter topicFilter)
{
CancellationToken = cancellationToken;
ClientId = clientId;
Session = session;
TopicFilter = topicFilter;
}
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
public CancellationToken CancellationToken { get; }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public string ClientId { get; }
/// <summary>
/// Gets or sets whether the broker should close the client connection.
@@ -48,12 +60,12 @@ namespace MQTTnet.Server
/// <summary>
/// Gets the current client session.
/// </summary>
public MqttSessionStatus Session { get; internal set; }
public MqttSessionStatus Session { 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; internal set; }
public IDictionary SessionItems => Session.Items;
/// <summary>
/// Gets or sets the topic filter.
@@ -10,16 +10,24 @@ namespace MQTTnet.Server
{
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs
{
public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, IDictionary sessionItems, string topic)
{
CancellationToken = cancellationToken;
ClientId = clientId;
SessionItems = sessionItems;
Topic = topic;
}
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; internal set; }
public CancellationToken CancellationToken { get; }
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; internal set; }
public string ClientId { get; }
/// <summary>
/// Gets or sets whether the broker should close the client connection.
@@ -41,7 +49,7 @@ namespace MQTTnet.Server
/// <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; internal set; }
public IDictionary SessionItems { get; }
/// <summary>
/// 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
/// separator).
/// </summary>
public string Topic { get; internal set; }
public string Topic { get; }
}
}
@@ -9,10 +9,17 @@ namespace MQTTnet.Server
{
public sealed class RetainedMessageChangedEventArgs : EventArgs
{
public string ClientId { get; internal set; }
public MqttApplicationMessage ChangedRetainedMessage { get; internal set; }
public List<MqttApplicationMessage> StoredRetainedMessages { get; internal set; }
public RetainedMessageChangedEventArgs(string clientId, MqttApplicationMessage changedRetainedMessage, List<MqttApplicationMessage> storedRetainedMessages)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
ChangedRetainedMessage = changedRetainedMessage ?? throw new ArgumentNullException(nameof(changedRetainedMessage));
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.
using System;
using System.Collections;
namespace MQTTnet.Server
{
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>
/// Gets the ID of the session.
/// Gets the ID of the session.
/// </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
{
readonly IMqttChannelAdapter _clientAdapter;
readonly MqttConnectPacket _connectPacket;
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
{
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
_clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
}
/// <summary>
@@ -44,6 +43,12 @@ namespace MQTTnet.Server
/// </summary>
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>
/// 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
@@ -56,7 +61,7 @@ namespace MQTTnet.Server
/// </summary>
public bool? CleanSession => _connectPacket.CleanSession;
public X509Certificate2 ClientCertificate => _clientAdapter.ClientCertificate;
public X509Certificate2 ClientCertificate => ChannelAdapter.ClientCertificate;
/// <summary>
/// Gets the client identifier.
@@ -64,9 +69,9 @@ namespace MQTTnet.Server
/// </summary>
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>
/// Gets or sets the keep alive period.
@@ -85,7 +90,7 @@ namespace MQTTnet.Server
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;
@@ -161,7 +166,10 @@ namespace MQTTnet.Server
/// </summary>
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;
/// <summary>
/// Gets or sets the user properties.
+251 -213
View File
@@ -11,6 +11,7 @@ using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.PacketDispatcher;
using MQTTnet.Packets;
@@ -20,20 +21,20 @@ namespace MQTTnet.Server
{
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 MqttPacketFactories _packetFactories = new MqttPacketFactories();
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
readonly MqttClientSessionsManager _sessionsManager;
readonly MqttNetSourceLogger _logger;
readonly MqttServerOptions _serverOptions;
readonly MqttServerEventContainer _eventContainer;
readonly MqttConnectPacket _connectPacket;
readonly MqttClientSessionsManager _sessionsManager;
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
CancellationTokenSource _cancellationToken;
public MqttClient(
MqttConnectPacket connectPacket,
IMqttChannelAdapter channelAdapter,
@@ -53,43 +54,31 @@ namespace MQTTnet.Server
Session = session ?? throw new ArgumentNullException(nameof(session));
_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));
}
public string Id => _connectPacket.ClientId;
public IMqttChannelAdapter ChannelAdapter { 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 IsTakenOver { get; set; }
public ushort KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
public MqttSession Session { get; }
public bool IsTakenOver { get; set; }
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 MqttClientStatistics Statistics { get; } = new MqttClientStatistics();
public void ResetStatistics()
{
@@ -110,7 +99,7 @@ namespace MQTTnet.Server
try
{
Task.Run(() => SendPacketsLoop(cancellationToken), cancellationToken).RunInBackground(_logger);
IsRunning = true;
await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
@@ -130,10 +119,10 @@ namespace MQTTnet.Server
{
var willPublishPacket = _packetFactories.Publish.Create(Session.LatestConnectPacket);
var willApplicationMessage = _applicationMessageFactory.Create(willPublishPacket);
_= _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
_ = _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
Session.WillMessageSent = true;
_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.
return;
}
await ChannelAdapter.SendPacketAsync(packet, cancellationToken).ConfigureAwait(false);
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)
{
if (!_eventContainer.InterceptingOutboundPacketEvent.HasHandlers)
{
return packet;
}
var interceptingPacketEventArgs = new InterceptingPacketEventArgs
{
ClientId = Id,
Endpoint = Endpoint,
Packet = packet,
CancellationToken = cancellationToken
};
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items);
await _eventContainer.InterceptingOutboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
if (!interceptingPacketEventArgs.ProcessPacket || packet == null)
{
return null;
}
return interceptingPacketEventArgs.Packet;
}
@@ -188,6 +377,8 @@ namespace MQTTnet.Server
// own exception in the reading loop!
while (!cancellationToken.IsCancellationRequested)
{
await Task.Yield();
var packet = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
if (packet == null)
{
@@ -195,22 +386,15 @@ namespace MQTTnet.Server
}
var processPacket = true;
if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
{
var interceptingPacketEventArgs = new InterceptingPacketEventArgs
{
ClientId = Id,
Endpoint = Endpoint,
Packet = packet,
CancellationToken = cancellationToken
};
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items);
await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
packet = interceptingPacketEventArgs.Packet;
processPacket = interceptingPacketEventArgs.ProcessPacket;
}
if (!processPacket || packet == null)
{
// 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)
{
Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier);
await HandleIncomingPubAckPacket(pubAckPacket).ConfigureAwait(false);
}
else if (packet is MqttPubCompPacket pubCompPacket)
{
Session.AcknowledgePublishPacket(pubCompPacket.PacketIdentifier);
await HandleIncomingPubCompPacket(pubCompPacket).ConfigureAwait(false);
}
else if (packet is MqttPubRecPacket pubRecPacket)
{
HandleIncomingPubRecPacket(pubRecPacket);
await HandleIncomingPubRecPacket(pubRecPacket).ConfigureAwait(false);
}
else if (packet is MqttPubRelPacket pubRelPacket)
{
@@ -315,6 +499,10 @@ namespace MQTTnet.Server
{
packetBusItem.MarkAsFailed(exception);
}
finally
{
await Task.Yield();
}
}
}
catch (OperationCanceledException)
@@ -347,7 +535,7 @@ namespace MQTTnet.Server
StopInternal();
}
}
void StopInternal()
{
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.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();
}
}
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)
{
try
{
var disconnectPacket = _packetFactories.Disconnect.Create(reasonCode);
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
{
await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
@@ -109,13 +109,9 @@ namespace MQTTnet.Server
try
{
if (_eventContainer.SessionDeletedEvent.HasHandlers)
if (_eventContainer.SessionDeletedEvent.HasHandlers && session != null)
{
var eventArgs = new SessionDeletedEventArgs
{
Id = session?.Id
};
var eventArgs = new SessionDeletedEventArgs(clientId, session.Items);
await _eventContainer.SessionDeletedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
}
}
@@ -295,13 +291,12 @@ namespace MQTTnet.Server
if (_eventContainer.ClientConnectedEvent.HasHandlers)
{
var eventArgs = new ClientConnectedEventArgs
{
ClientId = connectPacket.ClientId,
UserName = connectPacket.Username,
ProtocolVersion = channelAdapter.PacketFormatterAdapter.ProtocolVersion,
Endpoint = channelAdapter.Endpoint
};
var eventArgs = new ClientConnectedEventArgs(
connectPacket.ClientId,
connectPacket.Username,
channelAdapter.PacketFormatterAdapter.ProtocolVersion,
channelAdapter.Endpoint,
client.Session.Items);
await _eventContainer.ClientConnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
@@ -340,12 +335,8 @@ namespace MQTTnet.Server
if (client.Id != null && !client.IsTakenOver && _eventContainer.ClientDisconnectedEvent.HasHandlers)
{
var eventArgs = new ClientDisconnectedEventArgs
{
ClientId = client.Id,
DisconnectType = client.IsCleanDisconnect ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean,
Endpoint = endpoint
};
var disconnectType = client.IsCleanDisconnect ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean;
var eventArgs = new ClientDisconnectedEventArgs(client.Id, disconnectType, endpoint, client.Session.Items);
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
@@ -533,13 +524,7 @@ namespace MQTTnet.Server
if (_eventContainer.ClientConnectedEvent.HasHandlers)
{
var eventArgs = new ClientDisconnectedEventArgs
{
ClientId = existing.Id,
DisconnectType = MqttClientDisconnectType.Takeover,
Endpoint = existing.Endpoint
};
var eventArgs = new ClientDisconnectedEventArgs(existing.Id, MqttClientDisconnectType.Takeover, existing.Endpoint, existing.Session.Items);
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
@@ -572,12 +557,7 @@ namespace MQTTnet.Server
return;
}
var eventArgs = new ApplicationMessageNotConsumedEventArgs
{
ApplicationMessage = applicationMessage,
SenderId = senderId
};
var eventArgs = new ApplicationMessageNotConsumedEventArgs(applicationMessage, senderId);
await _eventContainer.ApplicationMessageNotConsumedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
@@ -623,25 +603,25 @@ namespace MQTTnet.Server
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>()
};
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.
if (string.IsNullOrEmpty(connectPacket.ClientId) && channelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
{
connectPacket.ClientId = context.AssignedClientIdentifier;
connectPacket.ClientId = eventArgs.AssignedClientIdentifier;
}
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 finalTopicFilters = new List<MqttTopicFilter>();
// The topic filters are order by its QoS so that the higher QoS will win over a
// 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 finalTopicFilter = subscriptionEventArgs.TopicFilter;
var subscriptionEventArgs = await InterceptSubscribe(topicFilterItem, cancellationToken).ConfigureAwait(false);
var topicFilter = subscriptionEventArgs.TopicFilter;
var processSubscription = subscriptionEventArgs.ProcessSubscription && subscriptionEventArgs.Response.ReasonCode <= MqttSubscribeReasonCode.GrantedQoS2;
result.UserProperties = subscriptionEventArgs.UserProperties;
@@ -195,31 +196,32 @@ namespace MQTTnet.Server
result.CloseConnection = true;
}
if (!processSubscription || string.IsNullOrEmpty(finalTopicFilter.Topic))
if (!processSubscription || string.IsNullOrEmpty(topicFilter.Topic))
{
continue;
}
var createSubscriptionResult = CreateSubscription(finalTopicFilter, subscribePacket.SubscriptionIdentifier, subscriptionEventArgs.Response.ReasonCode);
var createSubscriptionResult = CreateSubscription(topicFilter, subscribePacket.SubscriptionIdentifier, subscriptionEventArgs.Response.ReasonCode);
addedSubscriptions.Add(finalTopicFilter.Topic);
if (_eventContainer.ClientSubscribedTopicEvent.HasHandlers)
{
var eventArgs = new ClientSubscribedTopicEventArgs
{
ClientId = _session.Id,
TopicFilter = finalTopicFilter
};
await _eventContainer.ClientSubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
addedSubscriptions.Add(topicFilter.Topic);
finalTopicFilters.Add(topicFilter);
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);
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;
}
@@ -263,26 +265,30 @@ namespace MQTTnet.Server
_subscriptions.Remove(topicFilter);
// must remove subscription object from topic hash dictionary also
if (existingSubscription.TopicHasWildcard)
if (existingSubscription != null)
{
if (_wildcardSubscriptionsByTopicHash.TryGetValue(existingSubscription.TopicHash, out var subs))
var topicHash = existingSubscription.TopicHash;
if (existingSubscription.TopicHasWildcard)
{
subs.Subscriptions.Remove(existingSubscription);
if (subs.Subscriptions.Count == 0)
if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
{
_wildcardSubscriptionsByTopicHash.Remove(existingSubscription.TopicHash);
subscriptions.Subscriptions.Remove(existingSubscription);
if (subscriptions.Subscriptions.Count == 0)
{
_wildcardSubscriptionsByTopicHash.Remove(topicHash);
}
}
}
}
else
{
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(existingSubscription.TopicHash, out var subs))
else
{
subs.Remove(existingSubscription);
if (subs.Count == 0)
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
{
_noWildcardSubscriptionsByTopicHash.Remove(existingSubscription.TopicHash);
subscriptions.Remove(existingSubscription);
if (subscriptions.Count == 0)
{
_noWildcardSubscriptionsByTopicHash.Remove(topicHash);
}
}
}
}
@@ -294,23 +300,14 @@ namespace MQTTnet.Server
finally
{
_subscriptionsLock.Release();
if (_subscriptionChangedNotification != null)
{
_subscriptionChangedNotification.OnSubscriptionsRemoved(_session, removedSubscriptions);
}
_subscriptionChangedNotification?.OnSubscriptionsRemoved(_session, removedSubscriptions);
}
if (_eventContainer.ClientUnsubscribedTopicEvent.HasHandlers)
{
foreach (var topicFilter in unsubscribePacket.TopicFilters)
{
var eventArgs = new ClientUnsubscribedTopicEventArgs
{
ClientId = _session.Id,
TopicFilter = topicFilter
};
var eventArgs = new ClientUnsubscribedTopicEventArgs(_session.Id, topicFilter, _session.Items);
await _eventContainer.ClientUnsubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
@@ -465,14 +462,7 @@ namespace MQTTnet.Server
async Task<InterceptingSubscriptionEventArgs> InterceptSubscribe(MqttTopicFilter topicFilter, CancellationToken cancellationToken)
{
var eventArgs = new InterceptingSubscriptionEventArgs
{
ClientId = _session.Id,
TopicFilter = topicFilter,
SessionItems = _session.Items,
Session = new MqttSessionStatus(_session),
CancellationToken = cancellationToken
};
var eventArgs = new InterceptingSubscriptionEventArgs(cancellationToken, _session.Id, new MqttSessionStatus(_session), topicFilter);
if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
@@ -501,13 +491,7 @@ namespace MQTTnet.Server
async Task<InterceptingUnsubscriptionEventArgs> InterceptUnsubscribe(string topicFilter, MqttSubscription mqttSubscription, CancellationToken cancellationToken)
{
var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs
{
ClientId = _session.Id,
Topic = topicFilter,
SessionItems = _session.Items,
CancellationToken = cancellationToken
};
var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs(cancellationToken, topicFilter, _session.Items, topicFilter);
if (mqttSubscription == null)
{
@@ -105,13 +105,7 @@ namespace MQTTnet.Server
{
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{
var eventArgs = new RetainedMessageChangedEventArgs
{
ClientId = clientId,
ChangedRetainedMessage = applicationMessage,
StoredRetainedMessages = messagesForSave
};
var eventArgs = new RetainedMessageChangedEventArgs(clientId, applicationMessage, messagesForSave);
await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
}
@@ -9,40 +9,42 @@ namespace MQTTnet.Server
{
public sealed class MqttServerEventContainer
{
public AsyncEvent<InterceptingSubscriptionEventArgs> InterceptingSubscriptionEvent { get; } = new AsyncEvent<InterceptingSubscriptionEventArgs>();
public AsyncEvent<InterceptingUnsubscriptionEventArgs> InterceptingUnsubscriptionEvent { get; } = new AsyncEvent<InterceptingUnsubscriptionEventArgs>();
public AsyncEvent<ApplicationMessageNotConsumedEventArgs> ApplicationMessageNotConsumedEvent { get; } = new AsyncEvent<ApplicationMessageNotConsumedEventArgs>();
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<ValidatingConnectionEventArgs> ValidatingConnectionEvent { get; } = new AsyncEvent<ValidatingConnectionEventArgs>();
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<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<InterceptingSubscriptionEventArgs> InterceptingSubscriptionEvent { get; } = new AsyncEvent<InterceptingSubscriptionEventArgs>();
public AsyncEvent<InterceptingUnsubscriptionEventArgs> InterceptingUnsubscriptionEvent { get; } = new AsyncEvent<InterceptingUnsubscriptionEventArgs>();
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<InterceptingPacketEventArgs> InterceptingInboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
public AsyncEvent<InterceptingPacketEventArgs> InterceptingOutboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
public AsyncEvent<SessionDeletedEventArgs> SessionDeletedEvent { get; } = new AsyncEvent<SessionDeletedEventArgs>();
public AsyncEvent<EventArgs> StartedEvent { get; } = new AsyncEvent<EventArgs>();
public AsyncEvent<EventArgs> StoppedEvent { get; } = new AsyncEvent<EventArgs>();
public AsyncEvent<ValidatingConnectionEventArgs> ValidatingConnectionEvent { get; } = new AsyncEvent<ValidatingConnectionEventArgs>();
}
}
}
+13 -3
View File
@@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -23,7 +24,7 @@ namespace MQTTnet.Server
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.
HashSet<string> _subscribedTopics;
@@ -70,9 +71,18 @@ namespace MQTTnet.Server
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)
+52 -16
View File
@@ -3,6 +3,8 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -19,17 +21,18 @@ namespace MQTTnet.Server
public class MqttServer : Disposable
{
readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer();
readonly IDictionary _sessionItems = new ConcurrentDictionary<object, object>();
readonly ICollection<IMqttServerAdapter> _adapters;
readonly MqttClientSessionsManager _clientSessionsManager;
readonly MqttServerKeepAliveMonitor _keepAliveMonitor;
readonly MqttNetSourceLogger _logger;
readonly MqttServerOptions _options;
readonly IMqttNetLogger _rootLogger;
readonly MqttRetainedMessagesManager _retainedMessagesManager;
readonly MqttServerKeepAliveMonitor _keepAliveMonitor;
readonly MqttClientSessionsManager _clientSessionsManager;
readonly IMqttNetLogger _rootLogger;
CancellationTokenSource _cancellationTokenSource;
public MqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
@@ -55,6 +58,12 @@ namespace MQTTnet.Server
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
{
add => _eventContainer.ClientConnectedEvent.AddHandler(value);
@@ -158,7 +167,7 @@ namespace MQTTnet.Server
}
public bool IsStarted => _cancellationTokenSource != null;
public Task DeleteRetainedMessagesAsync()
{
ThrowIfNotStarted();
@@ -174,10 +183,10 @@ namespace MQTTnet.Server
}
ThrowIfNotStarted();
return _clientSessionsManager.GetClient(id).StopAsync(reasonCode);
}
public Task<IList<MqttClientStatus>> GetClientsAsync()
{
ThrowIfNotStarted();
@@ -199,7 +208,7 @@ namespace MQTTnet.Server
return _clientSessionsManager.GetSessionStatusAsync();
}
public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage)
public async Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage)
{
if (injectedApplicationMessage == null)
{
@@ -215,7 +224,34 @@ namespace MQTTnet.Server
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()
@@ -236,7 +272,7 @@ namespace MQTTnet.Server
}
await _eventContainer.StartedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
_logger.Info("Started.");
}
@@ -266,7 +302,7 @@ namespace MQTTnet.Server
}
await _eventContainer.StoppedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
_logger.Info("Stopped.");
}
@@ -310,7 +346,7 @@ namespace MQTTnet.Server
return _clientSessionsManager.UnsubscribeAsync(clientId, topicFilters);
}
public Task UpdateRetainedMessageAsync(MqttApplicationMessage retainedMessage)
{
if (retainedMessage == null)
@@ -347,7 +383,7 @@ namespace MQTTnet.Server
void ThrowIfNotStarted()
{
ThrowIfDisposed();
if (_cancellationTokenSource == null)
{
throw new InvalidOperationException("The MQTT server is not started.");
@@ -357,7 +393,7 @@ namespace MQTTnet.Server
void ThrowIfStarted()
{
ThrowIfDisposed();
if (_cancellationTokenSource != null)
{
throw new InvalidOperationException("The MQTT server is already started.");