Compare commits

..
123 changed files with 1987 additions and 3210 deletions
+4 -3
View File
@@ -1,3 +1,4 @@
* [Server] Added new events for delivered and dropped messages (#1866, thanks to @kallayj).
* [Server] The server will no longer treat a client which is receiving a large payload as alive. The packet must be received completely within the keep alive boundaries (BREAKING CHANGE!, #1883).
* [Server] Fixed "service not registered" exception in ASP.NET integration (#1889).
* [Core] Add validation of maximum string lengths (#1718).
* [Client] Added overloads for setting packet payload and will payload (#1720).
* [Client] The proper connect result is now exposed in the _Disconnected_ event when authentication fails (#1139).
* [Server] Improved performance by changing internal locking strategy for subscriptions (#1716, thanks to @zeheng).
+2 -2
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request]
env:
VERSION: "4.3.3.${{github.run_number}}"
VERSION: "4.2.1.${{github.run_number}}"
jobs:
build:
@@ -54,7 +54,7 @@ jobs:
uses: darenm/Setup-VSTest@v1
- name: Core Tests
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net7.0\MQTTnet.Tests.dll
run: vstest.console.exe Source\MQTTnet.Tests\bin\Release\net6.0\MQTTnet.Tests.dll
- name: ASP.NET Tests
run: vstest.console.exe Source\MQTTnet.AspNetCore.Tests\bin\Release\netcoreapp3.1\MQTTnet.AspNetCore.Tests.dll
-1
View File
@@ -240,7 +240,6 @@ 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_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=catched/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
+47 -99
View File
@@ -7,7 +7,6 @@
// ReSharper disable InconsistentNaming
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using MQTTnet.Client;
using MQTTnet.Extensions.WebSocket4Net;
using MQTTnet.Formatter;
@@ -71,6 +70,32 @@ public static class Client_Connection_Samples
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
}
public static async Task Connect_With_Amazon_AWS()
{
/*
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
*
* The broker requires special settings which are set here.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("amazon.web.services.broker")
// Disabling packet fragmentation is very important!
.WithoutPacketFragmentation()
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("The MQTT client is connected.");
await mqttClient.DisconnectAsync();
}
}
public static async Task Connect_Client_Timeout()
{
@@ -136,15 +161,15 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
.WithTlsOptions(
.WithTls(
o =>
{
// The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments.
o.WithCertificateValidationHandler(_ => true);
o.CertificateValidationHandler = _ => true;
// The default value is determined by the OS. Set manually to force version.
o.WithSslProtocols(SslProtocols.Tls12);
o.SslProtocol = SslProtocols.Tls12;
})
.Build();
@@ -171,7 +196,7 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -193,7 +218,7 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").Build();
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -216,11 +241,13 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithTlsOptions(
o => o.WithCertificateValidationHandler(
.WithTls(
o =>
{
// The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments.
_ => true))
o.CertificateValidationHandler = _ => true;
})
.Build();
// In MQTTv5 the response contains much more information.
@@ -235,31 +262,6 @@ public static class Client_Connection_Samples
}
}
public static async Task Connect_With_Amazon_AWS()
{
/*
* This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
*
* The broker requires special settings which are set here.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
// Disabling packet fragmentation is very important!
.WithoutPacketFragmentation()
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("The MQTT client is connected.");
await mqttClient.DisconnectAsync();
}
}
public static async Task Disconnect_Clean()
{
/*
@@ -315,19 +317,18 @@ public static class Client_Connection_Samples
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
.WithTlsOptions(
.WithTls(
o =>
{
o.WithCertificateValidationHandler(
eventArgs =>
{
eventArgs.Certificate.Subject.DumpToConsole();
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
eventArgs.Chain.ChainStatus.DumpToConsole();
eventArgs.SslPolicyErrors.DumpToConsole();
return true;
});
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();
@@ -431,59 +432,6 @@ public static class Client_Connection_Samples
}
}
});
Console.WriteLine("Press <Enter> to exit");
Console.ReadLine();
}
}
public static async Task ConnectTls_WithCaFile()
{
var mqttFactory = new MqttFactory();
X509Certificate2Collection caChain = new X509Certificate2Collection();
caChain.ImportFromPem(mosquitto_org); // from https://test.mosquitto.org/ssl/mosquitto.org.crt
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("test.mosquitto.org", 8883)
.WithTlsOptions(new MqttClientTlsOptionsBuilder()
.WithTrustChain(caChain)
.Build())
.Build();
var connAck = await mqttClient.ConnectAsync(mqttClientOptions);
Console.WriteLine("Connected to test.moquitto.org:8883 with CaFile mosquitto.org.crt: " + connAck.ResultCode);
}
}
const string mosquitto_org = @"
-----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL
BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG
A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU
BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv
by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE
BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES
MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp
dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg
UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW
Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA
s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH
3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo
E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT
MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV
6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC
6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf
+pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK
sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839
LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE
m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
-----END CERTIFICATE-----
";
}
}
@@ -5,7 +5,6 @@
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
using MQTTnet.Client;
using MQTTnet.Packets;
@@ -177,62 +176,4 @@ public static class Client_Subscribe_Samples
response.DumpToConsole();
}
}
static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient)
{
/*
* This sample shows how to achieve concurrent processing and not have message AutoAcknowledged
* This to have a proper QoS1 (at-least-once) experience for what at least MQTT specification can provide
*/
mqttClient.ApplicationMessageReceivedAsync += ea =>
{
ea.AutoAcknowledge = false;
async Task ProcessAsync()
{
// DO YOUR WORK HERE!
await Task.Delay(1000, shutdownToken);
await ea.AcknowledgeAsync(shutdownToken);
// WARNING: If process failures are not transient the message will be retried on every restart of the client
// A failed message will not be dispatched again to the client as MQTT does not have a NACK packet to let
// the broker know processing failed
//
// Optionally: Use a framework like Polly to create a retry policy: https://github.com/App-vNext/Polly#retry
}
_ = Task.Run(ProcessAsync, shutdownToken);
return Task.CompletedTask;
};
}
static void ConcurrentProcessingWithLimit(CancellationToken shutdownToken, IMqttClient mqttClient)
{
/*
* This sample shows how to achieve concurrent processing, with:
* - a maximum concurrency limit based on Environment.ProcessorCount
*/
var concurrent = new SemaphoreSlim(Environment.ProcessorCount);
mqttClient.ApplicationMessageReceivedAsync += async ea =>
{
await concurrent.WaitAsync(shutdownToken).ConfigureAwait(false);
async Task ProcessAsync()
{
try
{
// DO YOUR WORK HERE!
await Task.Delay(1000, shutdownToken);
}
finally
{
concurrent.Release();
}
}
_ = Task.Run(ProcessAsync, shutdownToken);
};
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
@@ -7,8 +7,6 @@
// ReSharper disable InconsistentNaming
using System.Text.Json;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Samples.Server;
@@ -34,10 +32,7 @@ public static class Server_Retained_Messages_Samples
{
try
{
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
eventArgs.LoadedRetainedMessages = retainedMessages;
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
Console.WriteLine("Retained messages loaded.");
}
catch (FileNotFoundException)
@@ -61,9 +56,7 @@ public static class Server_Retained_Messages_Samples
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
// of retained messages must be provided in the loaded event.
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
await File.WriteAllBytesAsync(storePath, buffer);
Console.WriteLine("Retained messages saved.");
}
@@ -86,59 +79,4 @@ public static class Server_Retained_Messages_Samples
Console.ReadLine();
}
}
sealed class MqttRetainedMessageModel
{
public string? ContentType { get; set; }
public byte[]? CorrelationData { get; set; }
public byte[]? Payload { get; set; }
public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public string? ResponseTopic { get; set; }
public string? Topic { get; set; }
public List<MqttUserProperty>? UserProperties { get; set; }
public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return new MqttRetainedMessageModel
{
Topic = message.Topic,
// Create a copy of the buffer from the payload segment because
// it cannot be serialized and deserialized with the JSON serializer.
Payload = message.PayloadSegment.ToArray(),
UserProperties = message.UserProperties,
ResponseTopic = message.ResponseTopic,
CorrelationData = message.CorrelationData,
ContentType = message.ContentType,
PayloadFormatIndicator = message.PayloadFormatIndicator,
QualityOfServiceLevel = message.QualityOfServiceLevel
// Other properties like "Retain" are not if interest in the storage.
// That's why a custom model makes sense.
};
}
public MqttApplicationMessage ToApplicationMessage()
{
return new MqttApplicationMessage
{
Topic = Topic,
PayloadSegment = new ArraySegment<byte>(Payload ?? Array.Empty<byte>()),
PayloadFormatIndicator = PayloadFormatIndicator,
ResponseTopic = ResponseTopic,
CorrelationData = CorrelationData,
ContentType = ContentType,
UserProperties = UserProperties,
QualityOfServiceLevel = QualityOfServiceLevel,
Dup = false,
Retain = true
};
}
}
}
@@ -33,11 +33,8 @@ namespace MQTTnet.AspNetCore
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
if (!(_connection is TcpConnection tcp) || tcp.IsConnected)
{
_input = connection.Transport.Input;
_output = connection.Transport.Output;
}
_input = connection.Transport.Input;
_output = connection.Transport.Output;
}
public long BytesReceived { get; private set; }
@@ -83,6 +80,8 @@ namespace MQTTnet.AspNetCore
}
}
public bool IsReadingPacket { get; private set; }
public bool IsSecureConnection
{
get
@@ -162,6 +161,11 @@ namespace MQTTnet.AspNetCore
BytesReceived += received;
return packet;
}
else
{
// we did receive something but the message is not yet complete
IsReadingPacket = true;
}
}
else if (readResult.IsCompleted)
{
@@ -182,9 +186,12 @@ namespace MQTTnet.AspNetCore
// completing the channel makes sure that there is no more data read after a protocol error
_input?.Complete(exception);
_output?.Complete(exception);
throw;
}
finally
{
IsReadingPacket = false;
}
cancellationToken.ThrowIfCancellationRequested();
return null;
+6 -14
View File
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -15,27 +14,20 @@ namespace MQTTnet.AspNetCore
{
public sealed class MqttHostedServer : MqttServer, IHostedService
{
readonly MqttFactory _mqttFactory;
public MqttHostedServer(MqttFactory mqttFactory, MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger) : base(
options,
adapters,
logger)
public MqttHostedServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
: base(options, adapters, logger)
{
_mqttFactory = mqttFactory ?? throw new ArgumentNullException(nameof(mqttFactory));
}
public async Task StartAsync(CancellationToken cancellationToken)
public Task StartAsync(CancellationToken cancellationToken)
{
// The yield makes sure that the hosted service is considered up and running.
await Task.Yield();
_ = StartAsync();
_ = StartAsync();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return StopAsync(_mqttFactory.CreateMqttServerStopOptionsBuilder().Build());
return StopAsync();
}
}
}
@@ -4,7 +4,6 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
@@ -15,92 +14,6 @@ namespace MQTTnet.AspNetCore
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
services.AddSingleton(options);
services.AddHostedMqttServer();
return services;
}
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
var serverOptionsBuilder = new MqttServerOptionsBuilder();
configure.Invoke(serverOptionsBuilder);
var options = serverOptionsBuilder.Build();
return AddHostedMqttServer(services, options);
}
public static void AddHostedMqttServer(this IServiceCollection services)
{
// The user may have these services already registered.
services.TryAddSingleton<IMqttNetLogger>(MqttNetNullLogger.Instance);
services.TryAddSingleton(new MqttFactory());
services.AddSingleton<MqttHostedServer>();
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
}
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddSingleton(
s =>
{
var builder = new AspNetMqttServerOptionsBuilder(s);
configure(builder);
return builder.Build();
});
services.AddHostedMqttServer();
return services;
}
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
{
services.AddSingleton<MqttConnectionHandler>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
return services;
}
public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddSingleton(logger);
}
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
{
if (serviceCollection is null)
@@ -110,14 +23,57 @@ namespace MQTTnet.AspNetCore
serviceCollection.AddMqttConnectionHandler();
serviceCollection.AddHostedMqttServer(configure);
return serviceCollection;
}
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
{
services.AddSingleton<MqttTcpServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
if (options == null) throw new ArgumentNullException(nameof(options));
services.AddSingleton(options);
services.AddHostedMqttServer();
return services;
}
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure = null)
{
services.AddSingleton(s =>
{
var serverOptionsBuilder = new MqttServerOptionsBuilder();
configure?.Invoke(serverOptionsBuilder);
return serverOptionsBuilder.Build();
});
services.AddHostedMqttServer();
return services;
}
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
{
services.AddSingleton(s =>
{
var builder = new AspNetMqttServerOptionsBuilder(s);
configure(builder);
return builder.Build();
});
services.AddHostedMqttServer();
return services;
}
static IServiceCollection AddHostedMqttServer(this IServiceCollection services)
{
var logger = new MqttNetEventLogger();
services.AddSingleton<IMqttNetLogger>(logger);
services.AddSingleton<MqttHostedServer>();
services.AddSingleton<IHostedService>(s => s.GetService<MqttHostedServer>());
services.AddSingleton<MqttServer>(s => s.GetService<MqttHostedServer>());
return services;
}
@@ -129,5 +85,21 @@ namespace MQTTnet.AspNetCore
return services;
}
public static IServiceCollection AddMqttTcpServerAdapter(this IServiceCollection services)
{
services.AddSingleton<MqttTcpServerAdapter>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttTcpServerAdapter>());
return services;
}
public static IServiceCollection AddMqttConnectionHandler(this IServiceCollection services)
{
services.AddSingleton<MqttConnectionHandler>();
services.AddSingleton<IMqttServerAdapter>(s => s.GetService<MqttConnectionHandler>());
return services;
}
}
}
}
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Protocol;
@@ -8,8 +7,8 @@ namespace MQTTnet.Extensions.Rpc
{
public interface IMqttRpcClient : IDisposable
{
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null);
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel);
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default);
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default);
}
}
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
@@ -4,7 +4,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -43,13 +42,13 @@ namespace MQTTnet.Extensions.Rpc
_waitingCalls.Clear();
}
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
using (var timeoutToken = new CancellationTokenSource(timeout))
{
try
{
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, timeoutToken.Token).ConfigureAwait(false);
}
catch (OperationCanceledException exception)
{
@@ -63,14 +62,14 @@ namespace MQTTnet.Extensions.Rpc
}
}
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
{
if (methodName == null)
{
throw new ArgumentNullException(nameof(methodName));
}
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
var requestTopic = topicNames.RequestTopic;
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Protocol;
@@ -12,13 +11,13 @@ namespace MQTTnet.Extensions.Rpc
{
public static class MqttRpcClientExtensions
{
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null)
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
if (client == null) throw new ArgumentNullException(nameof(client));
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters);
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel);
}
}
}
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using MQTTnet.Client;
using MQTTnet.Protocol;
@@ -11,11 +10,9 @@ namespace MQTTnet.Extensions.Rpc
{
public sealed class TopicGenerationContext
{
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName,
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
Parameters = parameters;
QualityOfServiceLevel = qualityOfServiceLevel;
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
Options = options ?? throw new ArgumentNullException(nameof(options));
@@ -23,8 +20,6 @@ namespace MQTTnet.Extensions.Rpc
public string MethodName { get; }
public IDictionary<string, object> Parameters { get; }
public IMqttClient MqttClient { get; }
public MqttRpcClientOptions Options { get; }
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
@@ -47,13 +47,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
var uri = _webSocketOptions.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
{
if (_webSocketOptions.TlsOptions?.UseTls == true)
if (_webSocketOptions.TlsOptions?.UseTls == false)
{
uri = "wss://" + uri;
uri = "ws://" + uri;
}
else
{
uri = "ws://" + uri;
uri = "wss://" + uri;
}
}
@@ -92,7 +92,18 @@ namespace MQTTnet.Extensions.WebSocket4Net
var webSocketVersion = WebSocketVersion.None;
var receiveBufferSize = 0;
var certificates = _webSocketOptions.TlsOptions?.ClientCertificatesProvider?.GetCertificates();
var certificates = new X509CertificateCollection();
if (_webSocketOptions.TlsOptions?.Certificates != null)
{
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
{
#if WINDOWS_UWP
certificates.Add(new X509Certificate(certificate));
#else
certificates.Add(certificate);
#endif
}
}
_webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize)
{
+30 -34
View File
@@ -20,25 +20,21 @@ namespace MQTTnet.TestApp
{
#if NET5_0_OR_GREATER
// TLS13 is only available in Net5.0
var unsafeTls13 = new MqttClientTlsOptions
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
{
UseTls = true,
SslProtocol = SslProtocols.Tls13,
// Don't use this in production code. This handler simply allows any invalid certificate to work.
AllowUntrustedCertificates = true,
IgnoreCertificateChainErrors = true,
CertificateValidationHandler = _ => true
CertificateValidationHandler = w => true
};
#endif
// Also defining TLS12 for servers that don't seem no to support TLS13.
var unsafeTls12 = new MqttClientTlsOptions
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
{
UseTls = true,
SslProtocol = SslProtocols.Tls12,
// Don't use this in production code. This handler simply allows any invalid certificate to work.
AllowUntrustedCertificates = true,
IgnoreCertificateChainErrors = true,
CertificateValidationHandler = _ => true
CertificateValidationHandler = w => true
};
// mqtt.eclipseprojects.io
@@ -48,16 +44,16 @@ namespace MQTTnet.TestApp
await ExecuteTestAsync(
"mqtt.eclipseprojects.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:80/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build(),
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build(),
true);
#endif
@@ -72,12 +68,12 @@ namespace MQTTnet.TestApp
await ExecuteTestAsync(
"test.mosquitto.org TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif
await ExecuteTestAsync(
@@ -85,26 +81,26 @@ namespace MQTTnet.TestApp
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
.WithCredentials("rw", "readwrite")
.WithProtocolVersion(MqttProtocolVersion.V311)
.WithTlsOptions(unsafeTls12)
.WithTls(unsafeTls12)
.Build());
await ExecuteTestAsync(
"test.mosquitto.org WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"test.mosquitto.org WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8080/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
await ExecuteTestAsync(
"test.mosquitto.org WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestAsync(
"test.mosquitto.org WS TLS12 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
true);
// await ExecuteTestAsync(
// "test.mosquitto.org WS TLS12 (WebSocket4Net)",
// new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
// true);
// broker.emqx.io
await ExecuteTestAsync(
@@ -113,30 +109,30 @@ namespace MQTTnet.TestApp
await ExecuteTestAsync(
"broker.emqx.io TCP TLS12",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
#if NET5_0_OR_GREATER
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif
await ExecuteTestAsync(
"broker.emqx.io WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8083/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.emqx.io WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
await ExecuteTestAsync(
"broker.emqx.io WS TLS12",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
await ExecuteTestAsync(
"broker.emqx.io WS TLS12 (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.emqx.io:8084/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
true);
// broker.hivemq.com
@@ -146,14 +142,15 @@ namespace MQTTnet.TestApp
await ExecuteTestAsync(
"broker.hivemq.com WS",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build());
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync(
"broker.hivemq.com WS (WebSocket4Net)",
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).Build(),
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
true);
// mqtt.swifitch.cz: Does not seem to operate any more
// cloudmqtt.com: Cannot test because it does not offer a free plan any more.
Write("Finished.", ConsoleColor.White);
@@ -200,10 +197,9 @@ namespace MQTTnet.TestApp
Write("[OK]\n", ConsoleColor.Green);
}
catch (Exception exception)
catch (Exception e)
{
Write("[FAILED]" + Environment.NewLine, ConsoleColor.Red);
Write(exception + Environment.NewLine, ConsoleColor.Red);
Write("[FAILED] " + e.Message + "\n", ConsoleColor.Red);
}
}
@@ -13,7 +13,6 @@ using MQTTnet.Exceptions;
using MQTTnet.LowLevelClient;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
{
@@ -22,6 +22,99 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
[TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass
{
[TestMethod]
public async Task Expose_Custom_Connection_Error()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
};
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
MqttClientDisconnectedEventArgs disconnectedArgs = null;
managedClient.DisconnectedAsync += args =>
{
disconnectedArgs = args;
return CompletedTask.Instance;
};
var clientOptions = testEnvironment.Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(testEnvironment.CreateDefaultClientOptions()).Build();
await managedClient.StartAsync(clientOptions);
await LongTestDelay();
Assert.IsNotNull(disconnectedArgs);
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, disconnectedArgs.ConnectResult.ResultCode);
}
}
[TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer(o => o.WithPersistentSessions());
var senderClient = await testEnvironment.ConnectClient();
// Prepare managed client.
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
await managedClient.SubscribeAsync("#");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
var managedClientOptions = new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
.WithCleanSession(false)
.Build()
};
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
await LongTestDelay();
// Send test data.
await senderClient.PublishStringAsync("topic1");
await LongTestDelay();
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
// Stop the managed client but keep session at server (not clean disconnect required).
await managedClient.StopAsync(false);
await LongTestDelay();
// Send new messages in the meantime.
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
await LongTestDelay();
// Start the managed client, it should receive the new message.
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(2);
// Stop and start again, no new message should be received.
for (var i = 0; i < 3; i++)
{
await managedClient.StopAsync(false);
await LongTestDelay();
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
}
receivedMessages.AssertReceivedCountEquals(2);
}
}
[TestMethod]
public async Task Connect_To_Invalid_Server()
{
@@ -88,38 +181,6 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
}
}
[TestMethod]
public async Task Expose_Custom_Connection_Error()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
};
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
MqttClientDisconnectedEventArgs disconnectedArgs = null;
managedClient.DisconnectedAsync += args =>
{
disconnectedArgs = args;
return CompletedTask.Instance;
};
var clientOptions = testEnvironment.Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(testEnvironment.CreateDefaultClientOptions()).Build();
await managedClient.StartAsync(clientOptions);
await LongTestDelay();
Assert.IsNotNull(disconnectedArgs);
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, disconnectedArgs.ConnectResult.ResultCode);
}
}
[TestMethod]
public async Task ManagedClients_Will_Message_Send()
{
@@ -163,67 +224,6 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
}
}
[TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer(o => o.WithPersistentSessions());
var senderClient = await testEnvironment.ConnectClient();
// Prepare managed client.
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
await managedClient.SubscribeAsync("#");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
var managedClientOptions = new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
.WithCleanSession(false)
.Build()
};
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
await LongTestDelay();
// Send test data.
await senderClient.PublishStringAsync("topic1");
await LongTestDelay();
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(1);
// Stop the managed client but keep session at server (not clean disconnect required).
await managedClient.StopAsync(false);
await LongTestDelay();
// Send new messages in the meantime.
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
await LongTestDelay();
// Start the managed client, it should receive the new message.
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(2);
// Stop and start again, no new message should be received.
for (var i = 0; i < 3; i++)
{
await managedClient.StopAsync(false);
await LongTestDelay();
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
}
receivedMessages.AssertReceivedCountEquals(2);
}
}
[TestMethod]
public async Task Start_Stop()
{
@@ -375,7 +375,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort);
var receivedManagedMessages = new List<MqttApplicationMessage>();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
managedClient.ApplicationMessageReceivedAsync += e =>
{
@@ -403,7 +403,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
// Make sure that it gets received after subscribing again.
await managedClient.SubscribeAsync("topic");
await LongTestDelay();
Assert.AreEqual(2, receivedManagedMessages.Count);
}
}
@@ -421,7 +421,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
var sendingClient = await testEnvironment.ConnectClient();
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true });
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
var subscribeTime = DateTime.UtcNow;
@@ -454,7 +454,7 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
//wait a bit for the subscription to become established
await Task.Delay(500);
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment<byte>(new byte[] { 1 }), Retain = true });
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
var messages = await SetupReceivingOfMessages(managedClient, 1);
@@ -0,0 +1,159 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Clients.MqttClient
{
[TestClass]
public sealed class Extended_Authentication_Tests : BaseTestClass
{
[TestMethod]
public async Task ReAuthenticate()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += async args =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
{
// Just do a simple custom authentication.
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, cancellationToken: timeout.Token);
var clientResponse = await args.ReceiveAuthenticationDataAsync(timeout.Token);
CollectionAssert.AreEqual(clientResponse.AuthenticationData, Encoding.ASCII.GetBytes("TOKEN"));
var userProperties = new MqttUserPropertiesBuilder().With("x", "y").Build();
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, cancellationToken: timeout.Token, userProperties: userProperties);
args.ReasonCode = MqttConnectReasonCode.Success;
}
};
server.ClientReAuthenticatingAsync += args =>
{
return CompletedTask.Instance;
};
var client = testEnvironment.CreateClient();
client.ExtendedAuthenticationExchangeAsync += async args =>
{
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, Encoding.ASCII.GetBytes("TOKEN"));
var serverResponse = await args.ReceiveAuthenticationDataAsync(CancellationToken.None);
Assert.IsNotNull(serverResponse.UserProperties);
Assert.AreEqual("x", serverResponse.UserProperties[0].Name);
Assert.AreEqual("y", serverResponse.UserProperties[0].Value);
};
var clientOptions = testEnvironment.CreateDefaultClientOptionsBuilder().WithTimeout(TimeSpan.FromSeconds(10)).WithAuthentication("CUSTOM").Build();
await client.ConnectAsync(clientOptions);
await LongTestDelay();
await client.ReAuthenticateAsync();
await LongTestDelay();
var pingResult = await client.TryPingAsync();
Assert.IsTrue(pingResult);
}
}
[TestMethod]
public async Task Use_Extended_Authentication()
{
var initialContextToken = Encoding.ASCII.GetBytes("initial context token");
var replyContextToken = Encoding.ASCII.GetBytes("reply context token");
var outcomeOfAuthentication = Encoding.ASCII.GetBytes("outcome of authentication");
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
/*
Sample flow from RFC:
1. Client to Server CONNECT Authentication Method="GS2-KRB5"
2. Server to Client AUTH rc=0x18 Authentication Method="GS2-KRB5"
3. Client to Server AUTH rc=0x18 Authentication Method="GS2-KRB5" Authentication Data=initial context token
4. Server to Client AUTH rc=0x18 Authentication Method="GS2-KRB5" Authentication Data=reply context token
5. Client to Server AUTH rc=0x18 Authentication Method="GS2-KRB5"
6. Server to Client CONNACK rc=0 Authentication Method="GS2-KRB5" Authentication Data=outcome of authentication
*/
server.ValidatingConnectionAsync += async args =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
{
// 1.
if (args.AuthenticationMethod != "GS2-KRB5")
{
args.ReasonCode = MqttConnectReasonCode.BadAuthenticationMethod;
return;
}
// 2.
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, cancellationToken: timeout.Token);
// 3.
var clientResponse = await args.ReceiveAuthenticationDataAsync(timeout.Token);
CollectionAssert.AreEqual(clientResponse.AuthenticationData, initialContextToken);
Assert.AreEqual(MqttAuthenticateReasonCode.ContinueAuthentication, clientResponse.ReasonCode);
// 4.
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, replyContextToken, cancellationToken: timeout.Token);
// 5.
clientResponse = await args.ReceiveAuthenticationDataAsync(timeout.Token);
Assert.AreEqual(clientResponse.AuthenticationData, null);
Assert.AreEqual(MqttAuthenticateReasonCode.ContinueAuthentication, clientResponse.ReasonCode);
// 6.
args.ResponseAuthenticationData = outcomeOfAuthentication;
args.ReasonCode = MqttConnectReasonCode.Success;
}
};
var client = testEnvironment.CreateClient();
client.ExtendedAuthenticationExchangeAsync += async args =>
{
if (args.AuthenticationMethod != "GS2-KRB5")
{
Assert.Fail("Authentication method is wrong.");
}
// 2. THE FACT THAT THE SERVER SENDS THE AUTH PACKET WILL TRIGGER THIS EVENT SO THE INITIAL DATA IS IN THE EVENT ARGS!
Assert.AreEqual(MqttAuthenticateReasonCode.ContinueAuthentication, args.ReasonCode);
// 3.
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication, initialContextToken);
// 4.
var serverResponse = await args.ReceiveAuthenticationDataAsync(CancellationToken.None);
CollectionAssert.AreEqual(serverResponse.AuthenticationData, replyContextToken);
Assert.AreEqual(MqttAuthenticateReasonCode.ContinueAuthentication, serverResponse.ReasonCode);
// 5.
await args.SendAuthenticationDataAsync(MqttAuthenticateReasonCode.ContinueAuthentication);
};
var clientOptions = testEnvironment.CreateDefaultClientOptionsBuilder().WithTimeout(TimeSpan.FromSeconds(10)).WithAuthentication("GS2-KRB5").Build();
await client.ConnectAsync(clientOptions);
}
}
}
}
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -11,7 +10,6 @@ using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
@@ -130,7 +128,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
}
@@ -158,7 +156,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.IsNotNull(eventArgs.UserProperties);
Assert.AreEqual(1, eventArgs.UserProperties.Count);
@@ -166,89 +164,5 @@ namespace MQTTnet.Tests.Clients.MqttClient
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
}
}
[TestMethod]
public async Task No_Unobserved_Exception()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var client = testEnvironment.CreateClient();
var options = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").WithTimeout(TimeSpan.FromSeconds(2)).Build();
try
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(0.5)))
{
await client.ConnectAsync(options, timeout.Token);
}
}
catch (OperationCanceledException)
{
}
client.Dispose();
// These delays and GC calls are required in order to make calling the finalizer reproducible.
GC.Collect();
GC.WaitForPendingFinalizers();
await LongTestDelay();
await LongTestDelay();
await LongTestDelay();
}
}
[TestMethod]
public async Task Return_Non_Success()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ResponseUserProperties = new List<MqttUserProperty>
{
new MqttUserProperty("Property", "Value")
};
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
return CompletedTask.Instance;
};
var client = testEnvironment.CreateClient();
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().WithoutThrowOnNonSuccessfulConnectResponse().Build());
Assert.IsNotNull(response);
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
Assert.AreEqual(response.UserProperties[0].Name, "Property");
Assert.AreEqual(response.UserProperties[0].Value, "Value");
}
}
[TestMethod]
public async Task Throw_Proper_Exception_When_Not_Connected()
{
try
{
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
await mqttClient.SubscribeAsync("test", MqttQualityOfServiceLevel.AtLeastOnce);
}
}
catch (MqttCommunicationException exception)
{
if (exception.Message == "The client is not connected.")
{
return;
}
}
Assert.Fail();
}
}
}
@@ -17,7 +17,6 @@ using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
// ReSharper disable InconsistentNaming
@@ -297,7 +296,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
Assert.IsNotNull(receivedMessage);
Assert.AreEqual("A", receivedMessage.Topic);
Assert.AreEqual(null, receivedMessage.PayloadSegment.Array);
Assert.AreEqual(null, receivedMessage.Payload);
}
}
@@ -508,7 +507,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
client2.ApplicationMessageReceivedAsync += e =>
{
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()));
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
return CompletedTask.Instance;
};
+98
View File
@@ -0,0 +1,98 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
namespace MQTTnet.Tests
{
[TestClass]
public class Extension_Tests
{
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
[TestMethod]
public async Task TimeoutAfter()
{
await MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct),
TimeSpan.FromMilliseconds(100), CancellationToken.None);
}
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
[TestMethod]
public async Task TimeoutAfterWithResult()
{
await MqttTaskTimeout.WaitAsync(
ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct),
TimeSpan.FromMilliseconds(100), CancellationToken.None);
}
[TestMethod]
public async Task TimeoutAfterWithInnerException()
{
try
{
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
{
var iis = new int[0];
iis[1] = 0;
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
Assert.Fail();
}
catch (Exception e)
{
Assert.IsTrue(e is IndexOutOfRangeException);
}
}
[TestMethod]
public async Task TimeoutAfterWithInnerExceptionWithResult()
{
try
{
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
{
var iis = new int[0];
iis[1] = 0;
return iis[0];
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
Assert.Fail();
}
catch (Exception e)
{
Assert.IsTrue(e is IndexOutOfRangeException);
}
}
[TestMethod]
public async Task TimeoutAfterMemoryUsage()
{
var initialMemory = GC.GetTotalMemory(true);
var tasks = Enumerable.Range(0, 100000)
.Select(i =>
{
return MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct),
TimeSpan.FromMinutes(1), CancellationToken.None);
});
await Task.WhenAll(tasks);
AssertIsLess(initialMemory + 2_000_000, GC.GetTotalMemory(true));
}
static void AssertIsLess(long bound, long actual)
{
if (bound < actual)
{
Assert.Fail($"value must be less than {bound:N0} but is {actual:N0}");
}
}
}
}
+1 -57
View File
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -46,34 +45,6 @@ namespace MQTTnet.Tests.Extensions
}
}
[TestMethod]
public async Task Execute_Success_Parameters_Propagated_Correctly()
{
var paramValue = "123";
var parameters = new Dictionary<string, object>
{
{ TestParametersTopicGenerationStrategy.ExpectedParamName, "123" }
};
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
await responseSender.SubscribeAsync($"MQTTnet.RPC/+/ping/{paramValue}");
responseSender.ApplicationMessageReceivedAsync += e => responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
using (var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder()
.WithTopicGenerationStrategy(new TestParametersTopicGenerationStrategy()).Build()))
{
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce, parameters);
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
}
}
}
[TestMethod]
public Task Execute_Success_With_QoS_0()
{
@@ -164,7 +135,7 @@ namespace MQTTnet.Tests.Extensions
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
{
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
}
}
@@ -251,32 +222,5 @@ namespace MQTTnet.Tests.Extensions
};
}
}
class TestParametersTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
{
internal const string ExpectedParamName = "test_param_name";
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{
if (context.Parameters == null)
{
throw new InvalidOperationException("Parameters dictionary expected to be not null");
}
if (!context.Parameters.TryGetValue(ExpectedParamName, out var paramValue))
{
throw new InvalidOperationException($"Parameter with name {ExpectedParamName} not present");
}
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}/{paramValue}";
var responseTopic = requestTopic + "/response";
return new MqttRpcTopicPair
{
RequestTopic = requestTopic,
ResponseTopic = responseTopic
};
}
}
}
}
@@ -23,7 +23,7 @@ namespace MQTTnet.Tests.Extensions
using (var client = factory.CreateMqttClient())
{
var options = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("ws://a.b/mqtt")).WithTimeout(TimeSpan.FromSeconds(2)).Build();
var options = new MqttClientOptionsBuilder().WithWebSocketServer("ws://a.b/mqtt").WithTimeout(TimeSpan.FromSeconds(2)).Build();
await client.ConnectAsync(options).ConfigureAwait(false);
}
}
@@ -78,7 +78,7 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1
}
[TestMethod]
[TestMethod]
public void Serialize_Full_MqttConnAckPacket_V310()
{
var connAckPacket = new MqttConnAckPacket
+1 -2
View File
@@ -1,8 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net48</TargetFrameworks>
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0;net7.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>7.3</LangVersion>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
+1 -1
View File
@@ -273,7 +273,7 @@ namespace MQTTnet.Tests.MQTTv5
Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic);
Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval);
CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData);
CollectionAssert.AreEqual(applicationMessage.PayloadSegment.ToArray(), receivedMessage.PayloadSegment.ToArray());
CollectionAssert.AreEqual(applicationMessage.Payload, receivedMessage.Payload);
CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties);
}
}
@@ -10,7 +10,6 @@ using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.MQTTv5
{
+64 -124
View File
@@ -25,6 +25,7 @@ namespace MQTTnet.Tests.Mockups
{
readonly List<string> _clientErrors = new List<string>();
readonly List<IMqttClient> _clients = new List<IMqttClient>();
readonly List<Exception> _exceptions = new List<Exception>();
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
readonly MqttProtocolVersion _protocolVersion;
@@ -39,8 +40,6 @@ namespace MQTTnet.Tests.Mockups
_protocolVersion = protocolVersion;
TestContext = testContext;
TaskScheduler.UnobservedTaskException += TrackUnobservedTaskException;
ServerLogger.LogMessagePublished += (s, e) =>
{
if (Debugger.IsAttached)
@@ -66,12 +65,9 @@ namespace MQTTnet.Tests.Mockups
if (e.LogMessage.Level == MqttNetLogLevel.Error)
{
if (!IgnoreClientLogErrors)
lock (_clientErrors)
{
lock (_clientErrors)
{
_clientErrors.Add(e.LogMessage.ToString());
}
_clientErrors.Add(e.LogMessage.ToString());
}
}
};
@@ -216,31 +212,30 @@ namespace MQTTnet.Tests.Mockups
public IMqttClient CreateClient()
{
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance;
var client = Factory.CreateMqttClient(logger);
client.ConnectingAsync += e =>
{
if (TestContext != null)
{
var clientOptions = e.ClientOptions;
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
}
}
return CompletedTask.Instance;
};
lock (_clients)
{
_clients.Add(client);
}
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : new MqttNetNullLogger();
return client;
var client = Factory.CreateMqttClient(logger);
_clients.Add(client);
client.ConnectingAsync += e =>
{
if (TestContext != null)
{
var clientOptions = e.ClientOptions;
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
}
}
return CompletedTask.Instance;
};
return client;
}
}
public MqttClientOptions CreateDefaultClientOptions()
@@ -250,22 +245,18 @@ namespace MQTTnet.Tests.Mockups
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
{
return Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(_protocolVersion)
.WithTcpServer("127.0.0.1", ServerPort)
.WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort).WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
}
public ILowLevelMqttClient CreateLowLevelClient()
{
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
lock (_lowLevelClients)
lock (_clients)
{
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
_lowLevelClients.Add(client);
}
return client;
return client;
}
}
public MqttServer CreateServer(MqttServerOptions options)
@@ -286,8 +277,7 @@ namespace MQTTnet.Tests.Mockups
// Null is used when the client id is assigned from the server!
if (!string.IsNullOrEmpty(e.ClientId) && !e.ClientId.StartsWith(TestContext.TestName))
{
TrackException(new InvalidOperationException($"Invalid client ID used ({e.ClientId}). It must start with UnitTest name."));
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
Assert.Fail($"Client ID does not start with test name ({TestContext.TestName}).");
}
}
@@ -299,68 +289,34 @@ namespace MQTTnet.Tests.Mockups
public void Dispose()
{
foreach (var mqttClient in _clients)
{
mqttClient?.Dispose();
}
foreach (var lowLevelMqttClient in _lowLevelClients)
{
lowLevelMqttClient?.Dispose();
}
try
{
lock (_clients)
{
foreach (var mqttClient in _clients)
{
try
{
//mqttClient.DisconnectAsync().GetAwaiter().GetResult();
}
catch
{
// This can happen when the test already disconnected the client.
}
finally
{
mqttClient?.Dispose();
}
}
_clients.Clear();
}
lock (_lowLevelClients)
{
foreach (var lowLevelMqttClient in _lowLevelClients)
{
lowLevelMqttClient.Dispose();
}
_lowLevelClients.Clear();
}
try
{
Server?.StopAsync().GetAwaiter().GetResult();
}
catch
{
// This can happen when the test already stopped the server.
}
finally
{
Server?.Dispose();
}
Server = null;
ThrowIfLogErrors();
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
if (_exceptions.Any())
{
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
}
Server?.StopAsync().GetAwaiter().GetResult();
}
catch
{
// This can happen when the test already stopped the server.
}
finally
{
TaskScheduler.UnobservedTaskException -= TrackUnobservedTaskException;
Server?.Dispose();
}
ThrowIfLogErrors();
if (_exceptions.Any())
{
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
}
}
@@ -377,7 +333,7 @@ namespace MQTTnet.Tests.Mockups
var options = optionsBuilder.Build();
var server = CreateServer(options);
await server.StartAsync().ConfigureAwait(false);
await server.StartAsync();
// The OS has chosen the port to we have to properly expose it to the tests.
ServerPort = options.DefaultEndpointOptions.Port;
@@ -405,49 +361,33 @@ namespace MQTTnet.Tests.Mockups
public void ThrowIfLogErrors()
{
if (!IgnoreServerLogErrors)
lock (_serverErrors)
{
lock (_serverErrors)
if (!IgnoreServerLogErrors && _serverErrors.Count > 0)
{
if (_serverErrors.Count > 0)
{
var message = $"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)}).";
Console.WriteLine(message);
throw new Exception(message);
}
var message = $"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)}).";
Console.WriteLine(message);
throw new Exception(message);
}
}
if (!IgnoreClientLogErrors)
lock (_clientErrors)
{
lock (_clientErrors)
if (!IgnoreClientLogErrors && _clientErrors.Count > 0)
{
if (_clientErrors.Count > 0)
{
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
Console.WriteLine(message);
throw new Exception(message);
}
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
Console.WriteLine(message);
throw new Exception(message);
}
}
}
public void TrackException(Exception exception)
{
if (exception == null)
{
return;
}
lock (_exceptions)
{
_exceptions.Add(exception);
}
}
void TrackUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
TrackException(e.Exception);
}
}
}
@@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Protocol;
@@ -12,7 +11,7 @@ using MQTTnet.Protocol;
namespace MQTTnet.Tests
{
[TestClass]
public sealed class MqttApplicationMessageBuilder_Tests
public class MqttApplicationMessageBuilder_Tests
{
[TestMethod]
public void CreateApplicationMessage_TopicOnly()
@@ -30,7 +29,7 @@ namespace MQTTnet.Tests
Assert.AreEqual("xyz", message.Topic);
Assert.IsFalse(message.Retain);
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "00:06:00");
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00");
}
[TestMethod]
@@ -42,7 +41,7 @@ namespace MQTTnet.Tests
Assert.AreEqual("123", message.Topic);
Assert.IsFalse(message.Retain);
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "Hello");
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello");
}
[TestMethod]
@@ -1,5 +1,3 @@
#pragma warning disable CS0618 // Type or member is obsolete
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
@@ -10,7 +8,7 @@ using System.Threading.Tasks;
namespace MQTTnet.Tests
{
[TestClass]
public sealed class MqttApplicationMessageTest
public class MqttApplicationMessageTest
{
[TestMethod]
public void PayloadSegment()
@@ -19,27 +17,32 @@ namespace MQTTnet.Tests
Assert.AreEqual(0, message.Payload.Length);
Assert.AreEqual(0, message.PayloadSegment.Count);
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2 };
Assert.AreEqual(2, message.Payload.Length);
Assert.AreEqual(2, message.PayloadSegment.Count);
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.Payload = new byte[] { 1, 2, 3 };
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 });
Assert.AreEqual(3, message.Payload.Length);
Assert.AreEqual(3, message.PayloadSegment.Count);
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 }, 1, 1);
Assert.AreEqual(1, message.Payload.Length);
Assert.AreEqual(1, message.PayloadSegment.Count);
Assert.IsFalse(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
Assert.IsTrue(object.ReferenceEquals(message.Payload, message.Payload));
Assert.IsFalse(object.ReferenceEquals(message.Payload, message.PayloadSegment.Array));
}
}
}
+16 -13
View File
@@ -303,7 +303,7 @@ namespace MQTTnet.Tests.Server
var server = await testEnvironment.StartServer();
server.InterceptingPublishAsync += e =>
{
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("extended"));
e.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended");
return CompletedTask.Instance;
};
@@ -314,7 +314,7 @@ namespace MQTTnet.Tests.Server
var isIntercepted = false;
c2.ApplicationMessageReceivedAsync += e =>
{
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()), StringComparison.Ordinal) == 0;
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0;
return CompletedTask.Instance;
};
@@ -425,7 +425,7 @@ namespace MQTTnet.Tests.Server
new MqttApplicationMessage
{
Topic = "/test/1",
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("true")),
Payload = Encoding.UTF8.GetBytes("true"),
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
})
{
@@ -598,7 +598,8 @@ namespace MQTTnet.Tests.Server
// c
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
await LongTestDelay();
await Task.Delay(500);
var flow = string.Join(string.Empty, events);
Assert.AreEqual("c", flow);
@@ -606,8 +607,10 @@ namespace MQTTnet.Tests.Server
// Connect client with same client ID. Should disconnect existing client.
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
await LongTestDelay();
await Task.Delay(500);
flow = string.Join(string.Empty, events);
Assert.AreEqual("cdc", flow);
c2.ApplicationMessageReceivedAsync += e =>
@@ -625,7 +628,8 @@ namespace MQTTnet.Tests.Server
// r
await c2.PublishStringAsync("topic");
await LongTestDelay();
await Task.Delay(500);
flow = string.Join(string.Empty, events);
Assert.AreEqual("cdcr", flow);
@@ -635,17 +639,16 @@ namespace MQTTnet.Tests.Server
await c1.TryDisconnectAsync();
Assert.AreEqual(false, c1.IsConnected);
await LongTestDelay();
await Task.Delay(500);
// d
Assert.AreEqual(true, c2.IsConnected);
await c2.DisconnectAsync();
await LongTestDelay();
await Task.Delay(500);
await server.StopAsync();
await LongTestDelay();
flow = string.Join(string.Empty, events);
Assert.AreEqual("cdcrd", flow);
}
@@ -780,7 +783,7 @@ namespace MQTTnet.Tests.Server
var client1 = await testEnvironment.ConnectClient();
client1.ApplicationMessageReceivedAsync += e =>
{
receivedBody = e.ApplicationMessage.PayloadSegment.ToArray();
receivedBody = e.ApplicationMessage.Payload;
return CompletedTask.Instance;
};
@@ -849,11 +852,11 @@ namespace MQTTnet.Tests.Server
return CompletedTask.Instance;
};
// Stopping the server should disconnect the connection with the client
// which, it turn, will fire the disconnected event at the client.
await Task.Delay(100);
await server.StopAsync();
await LongTestDelay();
await Task.Delay(100);
Assert.AreEqual(1, disconnectCalled);
}
@@ -1,426 +0,0 @@
#if !(NET452 || NET461 || NET48)
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Certificates;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server
{
// missing certificate builder api means tests won't work for older frameworks
[TestClass]
public sealed class HotSwapCerts_Tests
{
static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10);
[TestMethod]
public async Task ClientCertChangeWithoutServerUpdateFailsReconnect()
{
using (var server = new ServerTestHarness())
using (var client01 = new ClientTestHarness())
{
server.InstallNewClientCert(client01.GetCurrentClientCert());
client01.InstallNewServerCert(server.GetCurrentServerCert());
await server.StartServer();
await client01.Connect();
client01.WaitForConnectOrFail(DefaultTimeout);
client01.HotSwapClientCert();
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
client01.WaitForDisconnectOrFail(DefaultTimeout);
client01.WaitForConnectToFail(DefaultTimeout);
}
}
[TestMethod]
public async Task ClientCertChangeWithServerUpdateAcceptsReconnect()
{
using (var server = new ServerTestHarness())
using (var client01 = new ClientTestHarness())
{
server.InstallNewClientCert(client01.GetCurrentClientCert());
client01.InstallNewServerCert(server.GetCurrentServerCert());
await server.StartServer();
await client01.Connect();
client01.WaitForConnectOrFail(DefaultTimeout);
client01.HotSwapClientCert();
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
client01.WaitForDisconnectOrFail(DefaultTimeout);
server.InstallNewClientCert(client01.GetCurrentClientCert());
client01.WaitForConnectOrFail(DefaultTimeout);
}
}
[TestMethod]
public async Task ServerCertChangeWithClientCertUpdateAllowsReconnect()
{
using (var server = new ServerTestHarness())
using (var client01 = new ClientTestHarness())
{
server.InstallNewClientCert(client01.GetCurrentClientCert());
client01.InstallNewServerCert(server.GetCurrentServerCert());
await server.StartServer();
await client01.Connect();
client01.WaitForConnectOrFail(DefaultTimeout);
server.HotSwapServerCert();
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
client01.WaitForDisconnectOrFail(DefaultTimeout);
client01.InstallNewServerCert(server.GetCurrentServerCert());
client01.WaitForConnectOrFail(DefaultTimeout);
}
}
[TestMethod]
public async Task ServerCertChangeWithoutClientCertUpdateFailsReconnect()
{
using (var server = new ServerTestHarness())
using (var client01 = new ClientTestHarness())
{
server.InstallNewClientCert(client01.GetCurrentClientCert());
client01.InstallNewServerCert(server.GetCurrentServerCert());
await server.StartServer();
await client01.Connect();
client01.WaitForConnectOrFail(DefaultTimeout);
server.HotSwapServerCert();
server.ForceDisconnectAsync(client01).Wait(DefaultTimeout);
client01.WaitForDisconnectOrFail(DefaultTimeout);
client01.WaitForConnectToFail(DefaultTimeout);
}
}
static X509Certificate2 CreateSelfSignedCertificate(string oid)
{
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Loopback);
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost");
using (var rsa = RSA.Create())
{
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid(oid) }, false));
certRequest.CertificateExtensions.Add(sanBuilder.Build());
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
{
var pfxCertificate = new X509Certificate2(
certificate.Export(X509ContentType.Pfx),
(string)null,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
return pfxCertificate;
}
}
}
sealed class ClientTestHarness : IDisposable
{
readonly HotSwappableClientCertProvider _hotSwapClient = new HotSwappableClientCertProvider();
IMqttClient _client;
public string ClientId => _client.Options.ClientId;
public Task Connect()
{
return Run_Client_Connection();
}
public void Dispose()
{
_client.Dispose();
_hotSwapClient.Dispose();
}
public X509Certificate2 GetCurrentClientCert()
{
var result = _hotSwapClient.GetCertificates()[0];
return new X509Certificate2(result);
}
public void HotSwapClientCert()
{
_hotSwapClient.HotSwapCert();
}
public void InstallNewServerCert(X509Certificate2 serverCert)
{
_hotSwapClient.InstallNewServerCert(serverCert);
}
public void WaitForConnectOrFail(TimeSpan timeout)
{
Thread.Sleep(100);
if (!_client.IsConnected)
{
_client.ReconnectAsync().Wait(timeout);
}
WaitForConnect(timeout);
Assert.IsNotNull(_client, "Client was never initialized");
Assert.IsTrue(_client.IsConnected, $"Client connection failed after {timeout}");
}
public void WaitForConnectToFail(TimeSpan timeout)
{
Assert.IsFalse(_client.IsConnected, "Client should be disconnected before waiting for connect.");
WaitForConnect(timeout);
Assert.IsNotNull(_client, "Client was never initialized");
Assert.IsFalse(_client.IsConnected, "Client connection success but test wanted fail");
}
void WaitForDisconnect(TimeSpan timeout)
{
var timer = Stopwatch.StartNew();
while ((_client == null || _client.IsConnected) && timer.Elapsed < timeout)
{
Thread.Sleep(100);
}
}
public void WaitForDisconnectOrFail(TimeSpan timeout)
{
WaitForDisconnect(timeout);
Assert.IsNotNull(_client, "Client was never initialized");
Assert.IsFalse(_client.IsConnected, $"Client connection should have disconnected after {timeout}");
}
async Task Run_Client_Connection()
{
var optionsBuilder = new MqttClientOptionsBuilder()
.WithTlsOptions(
o => o.WithClientCertificatesProvider(_hotSwapClient)
.WithCertificateValidationHandler(_hotSwapClient.OnCertificateValidation)
.WithSslProtocols(SslProtocols.Tls12))
.WithTcpServer("localhost")
.WithCleanSession()
.WithProtocolVersion(MqttProtocolVersion.V500);
var mqttClientOptions = optionsBuilder.Build();
var factory = new MqttFactory();
var mqttClient = factory.CreateMqttClient();
_client = mqttClient;
await mqttClient.ConnectAsync(mqttClientOptions);
}
void WaitForConnect(TimeSpan timeout)
{
var timer = Stopwatch.StartNew();
while ((_client == null || !_client.IsConnected) && timer.Elapsed < timeout)
{
Thread.Sleep(100);
}
}
}
sealed class ServerTestHarness : IDisposable
{
readonly HotSwappableServerCertProvider _hotSwapServer = new HotSwappableServerCertProvider();
MqttServer _server;
public void Dispose()
{
if (_server != null)
{
_server.StopAsync().Wait();
_server.Dispose();
}
_hotSwapServer?.Dispose();
}
public Task ForceDisconnectAsync(ClientTestHarness client)
{
return _server.DisconnectClientAsync(client.ClientId, MqttDisconnectReasonCode.UnspecifiedError);
}
public X509Certificate2 GetCurrentServerCert()
{
return _hotSwapServer.GetCertificate();
}
public void HotSwapServerCert()
{
_hotSwapServer.HotSwapCert();
}
public void InstallNewClientCert(X509Certificate2 serverCert)
{
_hotSwapServer.InstallNewClientCert(serverCert);
}
public Task StartServer()
{
var mqttFactory = new MqttFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(_hotSwapServer)
.WithRemoteCertificateValidationCallback(_hotSwapServer.RemoteCertificateValidationCallback)
.WithEncryptedEndpoint()
.Build();
mqttServerOptions.TlsEndpointOptions.ClientCertificateRequired = true;
_server = mqttFactory.CreateMqttServer(mqttServerOptions);
return _server.StartAsync();
}
}
class HotSwappableClientCertProvider : IMqttClientCertificatesProvider, IDisposable
{
X509Certificate2Collection _certificates;
ConcurrentBag<X509Certificate2> _serverCerts = new ConcurrentBag<X509Certificate2>();
public HotSwappableClientCertProvider()
{
_certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2"));
}
public void Dispose()
{
if (_certificates != null)
{
foreach (var certs in _certificates)
{
#if !NET452
certs.Dispose();
#endif
}
}
}
public X509CertificateCollection GetCertificates()
{
return new X509Certificate2Collection(_certificates);
}
public void HotSwapCert()
{
_certificates = new X509Certificate2Collection(CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.2"));
}
public void InstallNewServerCert(X509Certificate2 serverCert)
{
_serverCerts.Add(serverCert);
}
public bool OnCertificateValidation(MqttClientCertificateValidationEventArgs certContext)
{
var serverCerts = _serverCerts.ToArray();
var providedCert = certContext.Certificate.GetRawCertData();
for (int i = 0, n = serverCerts.Length; i < n; i++)
{
var currentcert = serverCerts[i];
if (currentcert.RawData.SequenceEqual(providedCert))
{
return true;
}
}
return false;
}
}
sealed class HotSwappableServerCertProvider : ICertificateProvider, IDisposable
{
readonly ConcurrentBag<X509Certificate2> _clientCerts = new ConcurrentBag<X509Certificate2>();
X509Certificate2 _certificate;
public HotSwappableServerCertProvider()
{
_certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
}
public void Dispose()
{
#if !NET452
_certificate.Dispose();
#endif
}
public X509Certificate2 GetCertificate()
{
return _certificate;
}
public void HotSwapCert()
{
var newCert = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
var oldCert = Interlocked.Exchange(ref _certificate, newCert);
#if !NET452
oldCert.Dispose();
#endif
}
public void InstallNewClientCert(X509Certificate2 certificate)
{
_clientCerts.Add(certificate);
}
public bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
var serverCerts = _clientCerts.ToArray();
var providedCert = certificate.GetRawCertData();
for (int i = 0, n = serverCerts.Length; i < n; i++)
{
var currentCert = serverCerts[i];
if (currentCert.RawData.SequenceEqual(providedCert))
{
return true;
}
}
return false;
}
}
}
}
#endif
@@ -1,25 +0,0 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class MqttRetainedMessageManager_Tests
{
[TestMethod]
public async Task MqttRetainedMessageManager_GetUndefinedTopic()
{
var logger = new Mockups.TestLogger();
var eventContainer = new MQTTnet.Server.MqttServerEventContainer();
var retainedMessagesManager = new MQTTnet.Server.MqttRetainedMessagesManager(eventContainer, logger);
var task = retainedMessagesManager.GetMessage("undefined");
Assert.IsNotNull(task, "Task should not be null");
var result = await task;
Assert.IsNull(result, "Null result expected");
}
}
}
@@ -221,10 +221,7 @@ namespace MQTTnet.Tests.Server
var eventContainer = new MqttServerEventContainer();
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
var session = new MqttSession(new MqttConnectPacket
{
ClientId = ""
}, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
var session = new MqttSession("", false, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
}
@@ -8,7 +8,6 @@ using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server
{
+12 -30
View File
@@ -70,28 +70,6 @@ namespace MQTTnet.Tests.Server
}
}
[TestMethod]
public async Task Do_Not_Use_Expired_Session()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer(o => o.WithPersistentSessions());
var c1 = await testEnvironment.ConnectClient(o => o.WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3));
// Kill the client connection and ensure that the session will stay there but is expired after 3 seconds!
c1.Dispose();
await Task.Delay(TimeSpan.FromSeconds(6));
c1 = testEnvironment.CreateClient();
var options = testEnvironment.CreateDefaultClientOptionsBuilder().WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3).Build();
var connectResult = await c1.ConnectAsync(options);
Assert.AreEqual(false, connectResult.IsSessionPresent);
}
}
[TestMethod]
public async Task Fire_Deleted_Event()
{
@@ -143,7 +121,7 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
}
}
[TestMethod]
[DataRow(MqttProtocolVersion.V310)]
[DataRow(MqttProtocolVersion.V311)]
@@ -156,12 +134,16 @@ namespace MQTTnet.Tests.Server
await testEnvironment.StartServer();
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(10)).WithProtocolVersion(protocolVersion);
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion);
var hasReceive = false;
void OnReceive()
{
hasReceive = true;
if (!hasReceive)
{
hasReceive = true;
}
}
// Try to connect 50 clients at the same time.
@@ -170,7 +152,7 @@ namespace MQTTnet.Tests.Server
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
await LongTestDelay();
Assert.AreEqual(1, connectedClients.Count);
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
@@ -182,7 +164,7 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, hasReceive);
}
}
[DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
@@ -283,7 +265,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e =>
{
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty));
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty);
return CompletedTask.Instance;
};
@@ -373,8 +355,8 @@ namespace MQTTnet.Tests.Server
onReceive();
return CompletedTask.Instance;
};
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
}
@@ -557,7 +557,7 @@ namespace MQTTnet.Tests.Server
var eventContainer = new MqttServerEventContainer();
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
_clientSession = new MqttSession(new MqttConnectPacket{ ClientId = clientId }, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
_clientSession = new MqttSession(clientId, false, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
List<string> topicsToSubscribe;
+11 -9
View File
@@ -101,7 +101,7 @@ namespace MQTTnet.Tests.Server
new MqttApplicationMessage
{
Topic = "TestTopic1",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
Payload = new byte[] { 1, 2, 3, 4 }
});
await testEnvironment.Server.InjectApplicationMessage(
@@ -109,7 +109,7 @@ namespace MQTTnet.Tests.Server
new MqttApplicationMessage
{
Topic = "TestTopic1",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
Payload = new byte[] { 1, 2, 3, 4 }
}));
certificateProvider.CurrentCertificate = CreateCertificate(secondOid);
@@ -137,7 +137,7 @@ namespace MQTTnet.Tests.Server
new MqttApplicationMessage
{
Topic = "TestTopic2",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
Payload = new byte[] { 1, 2, 3, 4 }
});
await testEnvironment.Server.InjectApplicationMessage(
@@ -145,7 +145,7 @@ namespace MQTTnet.Tests.Server
new MqttApplicationMessage
{
Topic = "TestTopic2",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
Payload = new byte[] { 1, 2, 3, 4 }
}));
// Ensure first client still works
@@ -153,7 +153,7 @@ namespace MQTTnet.Tests.Server
new MqttApplicationMessage
{
Topic = "TestTopic1",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
Payload = new byte[] { 1, 2, 3, 4 }
});
await testEnvironment.Server.InjectApplicationMessage(
@@ -161,7 +161,7 @@ namespace MQTTnet.Tests.Server
new MqttApplicationMessage
{
Topic = "TestTopic1",
PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 })
Payload = new byte[] { 1, 2, 3, 4 }
}));
await Task.Delay(1000);
@@ -178,10 +178,12 @@ namespace MQTTnet.Tests.Server
var clientOptionsBuilder = testEnvironment.Factory.CreateClientOptionsBuilder();
clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString())
.WithTcpServer("localhost", 8883)
.WithTlsOptions(
o =>
.WithTls(
tls =>
{
o.WithSslProtocols(SslProtocols.Tls12).WithCertificateValidationHandler(certValidator);
tls.UseTls = true;
tls.SslProtocol = SslProtocols.Tls12;
tls.CertificateValidationHandler = certValidator;
});
var clientOptions = clientOptionsBuilder.Build();
@@ -25,6 +25,8 @@ namespace MQTTnet.Adapter
long BytesReceived { get; }
bool IsReadingPacket { get; }
Task ConnectAsync(CancellationToken cancellationToken);
Task DisconnectAsync(CancellationToken cancellationToken);
@@ -14,7 +14,6 @@ namespace MQTTnet.Adapter
Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
Task StartAsync(MqttServerOptions options, IMqttNetLogger logger);
Task StopAsync();
}
}
+53 -59
View File
@@ -26,7 +26,9 @@ namespace MQTTnet.Adapter
readonly IMqttChannel _channel;
readonly byte[] _fixedHeaderBuffer = new byte[2];
readonly MqttNetSourceLogger _logger;
readonly byte[] _singleByteBuffer = new byte[1];
readonly AsyncLock _syncRoot = new AsyncLock();
Statistics _statistics; // mutable struct, don't make readonly!
@@ -45,8 +47,6 @@ namespace MQTTnet.Adapter
_logger = logger.WithSource(nameof(MqttChannelAdapter));
}
public bool AllowPacketFragmentation { get; set; } = true;
public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived);
public long BytesSent => Volatile.Read(ref _statistics._bytesSent);
@@ -55,12 +55,16 @@ namespace MQTTnet.Adapter
public string Endpoint => _channel.Endpoint;
public bool IsReadingPacket { get; private set; }
public bool IsSecureConnection => _channel.IsSecureConnection;
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public MqttPacketInspector PacketInspector { get; set; }
public bool AllowPacketFragmentation { get; set; } = true;
public async Task ConnectAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -74,30 +78,12 @@ namespace MQTTnet.Adapter
* block forever. Even a cancellation token is not supported properly.
*/
var connectTask = _channel.ConnectAsync(cancellationToken);
var timeout = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => timeout.TrySetResult(null)))
{
var connectTask = Task.Run(
async () =>
{
try
{
await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false);
}
catch
{
// If the timeout is already reached the exception is no longer of interest and
// must be catched. Otherwise it will arrive at the TaskScheduler.UnobservedTaskException.
if (!timeout.Task.IsCompleted)
{
throw;
}
}
},
CancellationToken.None);
await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false);
if (timeout.Task.IsCompleted && !connectTask.IsCompleted)
{
throw new OperationCanceledException("MQTT connect canceled.", cancellationToken);
@@ -218,7 +204,7 @@ namespace MQTTnet.Adapter
PacketInspector?.BeginSendPacket(packetBuffer);
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation)
{
await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false);
@@ -387,45 +373,53 @@ namespace MQTTnet.Adapter
return ReceivedMqttPacket.Empty;
}
var fixedHeader = readFixedHeaderResult.FixedHeader;
if (fixedHeader.RemainingLength == 0)
IsReadingPacket = true;
try
{
return new ReceivedMqttPacket(fixedHeader.Flags, EmptyBuffer.ArraySegment, 2);
var fixedHeader = readFixedHeaderResult.FixedHeader;
if (fixedHeader.RemainingLength == 0)
{
return new ReceivedMqttPacket(fixedHeader.Flags, EmptyBuffer.ArraySegment, 2);
}
var bodyLength = fixedHeader.RemainingLength;
var body = new byte[bodyLength];
var bodyOffset = 0;
var chunkSize = Math.Min(ReadBufferSize, bodyLength);
do
{
var bytesLeft = body.Length - bodyOffset;
if (chunkSize > bytesLeft)
{
chunkSize = bytesLeft;
}
var readBytes = await _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return ReceivedMqttPacket.Empty;
}
if (readBytes == 0)
{
return ReceivedMqttPacket.Empty;
}
bodyOffset += readBytes;
} while (bodyOffset < bodyLength);
PacketInspector?.FillReceiveBuffer(body);
var bodySegment = new ArraySegment<byte>(body, 0, bodyLength);
return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength);
}
var bodyLength = fixedHeader.RemainingLength;
var body = new byte[bodyLength];
var bodyOffset = 0;
var chunkSize = Math.Min(ReadBufferSize, bodyLength);
do
finally
{
var bytesLeft = body.Length - bodyOffset;
if (chunkSize > bytesLeft)
{
chunkSize = bytesLeft;
}
var readBytes = await _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return ReceivedMqttPacket.Empty;
}
if (readBytes == 0)
{
return ReceivedMqttPacket.Empty;
}
bodyOffset += readBytes;
} while (bodyOffset < bodyLength);
PacketInspector?.FillReceiveBuffer(body);
var bodySegment = new ArraySegment<byte>(body, 0, bodyLength);
return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength);
IsReadingPacket = false;
}
}
static bool WrapAndThrowException(Exception exception)
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client
{
public sealed class MqttClientPartialAuthenticationResponse
{
public byte[] AuthenticationData { get; set; }
public MqttAuthenticateReasonCode ReasonCode { get; set; }
public List<MqttUserProperty> UserProperties { get; set; }
}
}
@@ -0,0 +1,111 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client.Internal;
using MQTTnet.Exceptions;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client
{
public sealed class MqttExtendedAuthenticationExchangeEventArgs : EventArgs
{
static readonly IReadOnlyCollection<MqttUserProperty> EmptyUserProperties = new List<MqttUserProperty>();
readonly IMqttAuthenticationTransportStrategy _transportStrategy;
public MqttExtendedAuthenticationExchangeEventArgs(MqttAuthPacket authPacket, IMqttAuthenticationTransportStrategy transportStrategy)
{
if (authPacket == null)
{
throw new ArgumentNullException(nameof(authPacket));
}
_transportStrategy = transportStrategy ?? throw new ArgumentNullException(nameof(transportStrategy));
ReasonCode = authPacket.ReasonCode;
ReasonString = authPacket.ReasonString;
AuthenticationMethod = authPacket.AuthenticationMethod;
AuthenticationData = authPacket.AuthenticationData;
UserProperties = authPacket.UserProperties ?? EmptyUserProperties;
}
/// <summary>
/// Gets the authentication data.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public byte[] AuthenticationData { get; }
/// <summary>
/// Gets the authentication method.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string AuthenticationMethod { get; }
/// <summary>
/// Gets the reason code.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttAuthenticateReasonCode ReasonCode { get; }
/// <summary>
/// Gets the reason string.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString { get; }
// /// <summary>
// /// Gets the response which will be sent to the server.
// /// </summary>
// public MqttExtendedAuthenticationExchangeResponse Response { get; } = new MqttExtendedAuthenticationExchangeResponse();
/// <summary>
/// Gets or sets the user properties.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
public async Task<MqttClientPartialAuthenticationResponse> ReceiveAuthenticationDataAsync(CancellationToken cancellationToken)
{
var receivePacket = await _transportStrategy.ReceivePacketAsync(AuthenticationMethod, cancellationToken).ConfigureAwait(false);
if (receivePacket == null)
{
throw new MqttCommunicationException("The client closed the connection.");
}
return new MqttClientPartialAuthenticationResponse
{
ReasonCode = receivePacket.ReasonCode,
AuthenticationData = receivePacket.AuthenticationData,
UserProperties = receivePacket.UserProperties
};
}
public Task SendAuthenticationDataAsync(
MqttAuthenticateReasonCode reasonCode,
byte[] authenticationData = null,
string reasonString = null,
List<MqttUserProperty> userProperties = null,
CancellationToken cancellationToken = default)
{
// The authentication method will never change so we must use the already known one [MQTT-4.12.0-5].
//var authPacket = MqttPacketFactories.Auth.Create(AuthenticationMethod, reasonCode);
var authPacket = new MqttAuthPacket
{
AuthenticationMethod = AuthenticationMethod,
AuthenticationData = authenticationData,
UserProperties = userProperties,
ReasonCode = reasonCode,
ReasonString = reasonString
};
return _transportStrategy.SendPacketAsync(authPacket, cancellationToken);
}
}
}
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
namespace MQTTnet.Client
{
public sealed class MqttReAuthenticationOptions
{
/// <summary>
/// Gets or sets the authentication data.
/// Authentication data is binary information used to transmit multiple iterations of cryptographic secrets of protocol
/// steps.
/// The content of the authentication data is highly dependent on the specific implementation of the authentication
/// method.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public byte[] AuthenticationData { get; set; }
/// <summary>
/// Gets or sets the user properties.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
}
}
@@ -11,110 +11,112 @@ namespace MQTTnet.Client
public sealed class MqttClientConnectResult
{
/// <summary>
/// Gets the result code.
/// MQTTv5 only.
/// </summary>
public MqttClientConnectResultCode ResultCode { get; internal set; }
/// <summary>
/// Gets a value indicating whether a session was already available or not.
/// MQTTv5 only.
/// </summary>
public bool IsSessionPresent { get; internal set; }
/// <summary>
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server.
/// MQTTv5 only.
/// </summary>
public bool WildcardSubscriptionAvailable { get; internal set; }
/// <summary>
/// Gets whether the server supports retained messages.
/// MQTTv5 only.
/// </summary>
public bool RetainAvailable { get; internal set; }
/// <summary>
/// Gets the client identifier which was chosen by the server.
/// MQTTv5 only.
/// Gets the client identifier which was chosen by the server.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string AssignedClientIdentifier { get; internal set; }
/// <summary>
/// Gets the authentication method.
/// MQTTv5 only.
/// Gets the authentication data.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public byte[] AuthenticationData { get; internal set; }
/// <summary>
/// Gets the authentication method.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string AuthenticationMethod { get; internal set; }
/// <summary>
/// Gets the authentication data.
/// MQTTv5 only.
/// Gets a value indicating whether a session was already available or not.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public byte[] AuthenticationData { get; internal set; }
public bool IsSessionPresent { get; internal set; }
public uint? MaximumPacketSize { get; internal set; }
/// <summary>
/// Gets the reason string.
/// MQTTv5 only.
/// </summary>
public string ReasonString { get; internal set; }
public ushort? ReceiveMaximum { get; internal set; }
/// <summary>
/// Gets the maximum QoS which is supported by the server.
/// MQTTv5 only.
/// Gets the maximum QoS which is supported by the server.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttQualityOfServiceLevel MaximumQoS { get; internal set; }
/// <summary>
/// Gets the response information.
/// MQTTv5 only.
/// Gets the reason string.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString { get; internal set; }
public ushort? ReceiveMaximum { get; internal set; }
/// <summary>
/// Gets the response information.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ResponseInformation { get; internal set; }
/// <summary>
/// Gets the maximum value for a topic alias. 0 means not supported.
/// MQTTv5 only.
/// Gets the result code.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public ushort TopicAliasMaximum { get; internal set; }
public MqttClientConnectResultCode ResultCode { get; internal set; }
/// <summary>
/// Gets an alternate server which should be used instead of the current one.
/// MQTTv5 only.
/// Gets whether the server supports retained messages.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ServerReference { get; internal set; }
public bool RetainAvailable { get; internal set; }
/// <summary>
/// MQTTv5 only.
/// Gets the keep alive interval which was chosen by the server instead of the
/// keep alive interval from the client CONNECT packet.
/// A value of 0 indicates that the feature is not used.
/// MQTTv5 only.
/// Gets the keep alive interval which was chosen by the server instead of the
/// keep alive interval from the client CONNECT packet.
/// A value of 0 indicates that the feature is not used.
/// </summary>
public ushort ServerKeepAlive { get; internal set; }
/// <summary>
/// Gets an alternate server which should be used instead of the current one.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ServerReference { get; internal set; }
public uint? SessionExpiryInterval { get; internal set; }
/// <summary>
/// Gets a value indicating whether the subscription identifiers are available or not.
/// MQTTv5 only.
/// </summary>
public bool SubscriptionIdentifiersAvailable { get; internal set; }
/// <summary>
/// Gets a value indicating whether the shared subscriptions are available or not.
/// MQTTv5 only.
/// Gets a value indicating whether the shared subscriptions are available or not.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public bool SharedSubscriptionAvailable { get; internal set; }
/// <summary>
/// Gets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// MQTTv5 only.
/// Gets a value indicating whether the subscription identifiers are available or not.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public bool SubscriptionIdentifiersAvailable { get; internal set; }
/// <summary>
/// Gets the maximum value for a topic alias. 0 means not supported.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public ushort TopicAliasMaximum { get; internal set; }
/// <summary>
/// Gets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
/// packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; internal set; }
/// <summary>
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public bool WildcardSubscriptionAvailable { get; internal set; }
}
}
}
@@ -1,13 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
namespace MQTTnet.Client
{
public interface IMqttExtendedAuthenticationExchangeHandler
{
Task HandleRequestAsync(MqttExtendedAuthenticationExchangeContext context);
}
}
@@ -1,62 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client
{
public class MqttExtendedAuthenticationExchangeContext
{
public MqttExtendedAuthenticationExchangeContext(MqttAuthPacket authPacket, MqttClient client)
{
if (authPacket == null) throw new ArgumentNullException(nameof(authPacket));
ReasonCode = authPacket.ReasonCode;
ReasonString = authPacket.ReasonString;
AuthenticationMethod = authPacket.AuthenticationMethod;
AuthenticationData = authPacket.AuthenticationData;
UserProperties = authPacket.UserProperties;
Client = client ?? throw new ArgumentNullException(nameof(client));
}
/// <summary>
/// Gets the reason code.
/// Hint: MQTT 5 feature only.
/// </summary>
public MqttAuthenticateReasonCode ReasonCode { get; }
/// <summary>
/// Gets the reason string.
/// Hint: MQTT 5 feature only.
/// </summary>
public string ReasonString { get; }
/// <summary>
/// Gets the authentication method.
/// Hint: MQTT 5 feature only.
/// </summary>
public string AuthenticationMethod { get; }
/// <summary>
/// Gets the authentication data.
/// Hint: MQTT 5 feature only.
/// </summary>
public byte[] AuthenticationData { get; }
/// <summary>
/// Gets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only.
/// </summary>
public List<MqttUserProperty> UserProperties { get; }
public MqttClient Client { get; }
}
}
@@ -1,42 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client
{
public class MqttExtendedAuthenticationExchangeData
{
/// <summary>
/// Gets or sets the reason code.
/// Hint: MQTT 5 feature only.
/// </summary>
public MqttAuthenticateReasonCode ReasonCode { get; set; }
/// <summary>
/// Gets or sets the reason string.
/// Hint: MQTT 5 feature only.
/// </summary>
public string ReasonString { get; set; }
/// <summary>
/// Gets or sets the authentication data.
/// Authentication data is binary information used to transmit multiple iterations of cryptographic secrets of protocol steps.
/// The content of the authentication data is highly dependent on the specific implementation of the authentication method.
/// Hint: MQTT 5 feature only.
/// </summary>
public byte[] AuthenticationData { get; set; }
/// <summary>
/// Gets or sets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only.
/// </summary>
public List<MqttUserProperty> UserProperties { get; }
}
}
+4 -2
View File
@@ -8,13 +8,15 @@ namespace MQTTnet.Client
public interface IMqttClient : IDisposable
{
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<MqttExtendedAuthenticationExchangeEventArgs, Task> ExtendedAuthenticationExchangeAsync;
event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
bool IsConnected { get; }
@@ -29,7 +31,7 @@ namespace MQTTnet.Client
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
Task ReAuthenticateAsync(MqttReAuthenticationOptions options, CancellationToken cancellationToken = default);
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
@@ -0,0 +1,225 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client.Internal
{
public sealed class MqttInitialAuthenticationStrategy : IMqttAuthenticationTransportStrategy
{
readonly IMqttChannelAdapter _channelAdapter;
public MqttInitialAuthenticationStrategy(IMqttChannelAdapter channelAdapter)
{
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
}
public Task SendPacketAsync(MqttAuthPacket authPacket, CancellationToken cancellationToken)
{
if (authPacket == null)
{
throw new ArgumentNullException(nameof(authPacket));
}
return _channelAdapter.SendPacketAsync(authPacket, cancellationToken);
}
public async Task<MqttAuthPacket> ReceivePacketAsync(string authenticationMethod, CancellationToken cancellationToken)
{
if (authenticationMethod == null)
{
throw new ArgumentNullException(nameof(authenticationMethod));
}
var receivePacket = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
if (receivePacket is MqttAuthPacket authPacket)
{
if (!string.Equals(authPacket.AuthenticationMethod, authenticationMethod, StringComparison.Ordinal))
{
throw new MqttProtocolViolationException("The authentication method is not allowed to change while authenticating.");
}
return authPacket;
}
if (receivePacket == null)
{
throw new MqttCommunicationException("The server closed the connection.");
}
throw new MqttProtocolViolationException("Expected an AUTH packet from the client.");
}
}
public interface IMqttAuthenticationTransportStrategy
{
Task SendPacketAsync(MqttAuthPacket authPacket, CancellationToken cancellationToken);
Task<MqttAuthPacket> ReceivePacketAsync(string authenticationMethod, CancellationToken cancellationToken);
}
public sealed class MqttClientAuthenticationHandler
{
readonly MqttClient _client;
readonly AsyncEvent<MqttExtendedAuthenticationExchangeEventArgs> _extendedAuthenticationExchangeEvent = new AsyncEvent<MqttExtendedAuthenticationExchangeEventArgs>();
readonly MqttNetSourceLogger _logger;
public MqttClientAuthenticationHandler(MqttClient client, IMqttNetLogger logger)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_client = client ?? throw new ArgumentNullException(nameof(client));
_logger = logger.WithSource(nameof(MqttClientAuthenticationHandler));
}
public void AddHandler(Func<MqttExtendedAuthenticationExchangeEventArgs, Task> handler)
{
_extendedAuthenticationExchangeEvent.AddHandler(handler);
}
public async Task<MqttClientConnectResult> Authenticate(IMqttChannelAdapter channelAdapter, MqttClientOptions options, CancellationToken cancellationToken)
{
if (channelAdapter == null)
{
throw new ArgumentNullException(nameof(channelAdapter));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
// From the RFC:
// The CONNACK packet is the packet sent by the Server in response to a CONNECT packet received from a Client.
// The Server MUST send a CONNACK with a 0x00 (Success) Reason Code before sending any Packet other than AUTH [MQTT-3.2.0-1].
// The Server MUST NOT send more than one CONNACK in a Network Connection [MQTT-3.2.0-2].
MqttClientConnectResult result;
try
{
// Send the CONNECT packet..
var connectPacket = MqttPacketFactories.Connect.Create(options);
await _client.Send(connectPacket, cancellationToken).ConfigureAwait(false);
// Wait for the CONNACK packet...
MqttConnAckPacket connAckPacket = null;
while (!cancellationToken.IsCancellationRequested)
{
var receivedPacket = await channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
if (receivedPacket is MqttAuthPacket initialAuthPacket)
{
// MQTT v3.1.1 cannot send an AUTH packet.
// If the Server requires additional information to complete the authentication, it can send an AUTH packet to the Client.
// This packet MUST contain a Reason Code of 0x18 (Continue authentication)
if (initialAuthPacket.ReasonCode != MqttAuthenticateReasonCode.ContinueAuthentication)
{
throw new MqttProtocolViolationException("Wrong reason code received [MQTT-4.12.0-2].");
}
//var response = await HandleExtendedAuthentication(authPacket, channelAdapter).ConfigureAwait(false);
await HandleExtendedAuthentication(initialAuthPacket, new MqttInitialAuthenticationStrategy(channelAdapter)).ConfigureAwait(false);
}
else if (receivedPacket is MqttConnAckPacket connAckPacketBuffer)
{
connAckPacket = connAckPacketBuffer;
// The CONNACK packet is the last packet when authenticating so there is no further AUTH packet allowed!
break;
}
else
{
throw new MqttProtocolViolationException($"Received {receivedPacket.GetRfcName()} while authenticating.");
}
}
cancellationToken.ThrowIfCancellationRequested();
// If the initial CONNECT packet included an Authentication Method property then all AUTH packets,
// and any successful CONNACK packet MUST include an Authentication Method Property with the same
// value as in the CONNECT packet [MQTT-4.12.0-5].
if (!string.IsNullOrEmpty(connectPacket.AuthenticationMethod))
{
if (!string.Equals(connectPacket.AuthenticationMethod, connAckPacket?.AuthenticationMethod))
{
throw new MqttProtocolViolationException("The CONNACK packet does not have the same authentication method as the CONNECT packet.");
}
}
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion);
}
catch (Exception exception)
{
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
}
// This is no feature. It is basically a backward compatibility option and should be removed in the future.
// The client should not throw any exception if the transport layer connection was successful and the server
// did send a proper ACK packet.
if (options.ThrowOnNonSuccessfulResponseFromServer)
{
_logger.Warning(
"Client will now throw an _MqttConnectingFailedException_. This is obsolete and will be removed in the future. Consider setting _ThrowOnNonSuccessfulResponseFromServer=False_ in client options.");
if (result.ResultCode != MqttClientConnectResultCode.Success)
{
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
}
}
_logger.Verbose("Authenticated MQTT connection with server established.");
return result;
}
public Task HandleExtendedAuthentication(MqttAuthPacket initialAuthPacket, IMqttAuthenticationTransportStrategy transportStrategy)
{
ValidateEventHandler();
var eventArgs = new MqttExtendedAuthenticationExchangeEventArgs(initialAuthPacket, transportStrategy);
return _extendedAuthenticationExchangeEvent.InvokeAsync(eventArgs);
}
void ValidateEventHandler()
{
if (!_extendedAuthenticationExchangeEvent.HasHandlers)
{
throw new InvalidOperationException("Cannot handle extended authentication without attached event handler.");
}
}
public void RemoveHandler(Func<MqttExtendedAuthenticationExchangeEventArgs, Task> handler)
{
_extendedAuthenticationExchangeEvent.RemoveHandler(handler);
}
public async Task ReAuthenticate(MqttReAuthenticationOptions options, CancellationToken cancellationToken)
{
ValidateEventHandler();
var authPacket = MqttPacketFactories.Auth.CreateReAuthenticationPacket(_client.Options, options);
// Sending the AUTH packet will trigger the re authentication and the event handler will
// handle the details.
await _client.Send(authPacket, cancellationToken).ConfigureAwait(false);
}
}
}
@@ -0,0 +1,126 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
namespace MQTTnet.Client.Internal
{
public sealed class MqttClientKeepAliveHandler
{
readonly MqttClient _client;
readonly MqttNetSourceLogger _logger;
CancellationTokenSource _cancellationToken;
TimeSpan _keepAliveInterval;
Task _keepAlivePacketsSenderTask;
DateTime _lastPacketSentTimestamp;
public MqttClientKeepAliveHandler(MqttClient client, IMqttNetLogger logger)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
_logger = logger.WithSource(nameof(MqttClientKeepAliveHandler));
}
public void Disable()
{
try
{
_cancellationToken?.Cancel(false);
}
finally
{
_cancellationToken?.Dispose();
_cancellationToken = null;
}
}
public void Enable(MqttClientConnectResult connectResult)
{
if (connectResult == null)
{
throw new ArgumentNullException(nameof(connectResult));
}
_keepAliveInterval = _client.Options.KeepAlivePeriod;
if (connectResult.ServerKeepAlive > 0)
{
_logger.Info("Using keep alive value {0} sent from the server", connectResult.ServerKeepAlive);
_keepAliveInterval = TimeSpan.FromSeconds(connectResult.ServerKeepAlive);
}
if (_keepAliveInterval == TimeSpan.Zero)
{
return;
}
_cancellationToken = new CancellationTokenSource();
_keepAlivePacketsSenderTask = Task.Run(() => SendKeepAliveMessagesLoop(_cancellationToken.Token), _cancellationToken.Token);
}
public void TrackSentPacket()
{
// Only use UTC to avoid performance hit by loading current time zone all the time.
_lastPacketSentTimestamp = DateTime.UtcNow;
}
async Task SendKeepAliveMessagesLoop(CancellationToken cancellationToken)
{
try
{
_logger.Verbose("Start sending keep alive packets.");
while (!cancellationToken.IsCancellationRequested)
{
// Values described here: [MQTT-3.1.2-24].
var timeWithoutPacketSent = DateTime.UtcNow - _lastPacketSentTimestamp;
if (timeWithoutPacketSent > _keepAliveInterval)
{
using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
timeout.CancelAfter(_client.Options.Timeout);
await _client.PingAsync(timeout.Token).ConfigureAwait(false);
}
}
// Wait a fixed time in all cases. Calculation of the remaining time is complicated
// due to some edge cases and was buggy in the past. Now we wait several ms because the
// min keep alive value is one second so that the server will wait 1.5 seconds for a PING packet.
await Task.Delay(250, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
if (_client._cleanDisconnectInitiated)
{
return;
}
if (exception is OperationCanceledException)
{
return;
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while sending/receiving keep alive packets");
}
else
{
_logger.Error(exception, "Error exception while sending/receiving keep alive packets");
}
await _client.DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped sending keep alive packets");
}
}
}
}
+135 -236
View File
@@ -21,17 +21,19 @@ namespace MQTTnet.Client
public sealed class MqttClient : Disposable, IMqttClient
{
readonly IMqttClientAdapterFactory _adapterFactory;
readonly MqttClientAuthenticationHandler _authenticationHandler;
readonly object _disconnectLock = new object();
readonly MqttClientEvents _events = new MqttClientEvents();
readonly MqttClientKeepAliveHandler _keepAliveHandler;
readonly MqttNetSourceLogger _logger;
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
readonly IMqttNetLogger _rootLogger;
IMqttChannelAdapter _adapter;
IMqttChannelAdapter _channelAdapter;
bool _cleanDisconnectInitiated;
internal bool _cleanDisconnectInitiated;
CancellationTokenSource _clientAlive;
volatile int _connectionStatus;
// The value for this field can be set from two different enums.
@@ -39,11 +41,6 @@ namespace MQTTnet.Client
int _disconnectReason;
string _disconnectReasonString;
List<MqttUserProperty> _disconnectUserProperties;
Task _keepAlivePacketsSenderTask;
DateTime _lastPacketSentTimestamp;
CancellationTokenSource _mqttClientAlive;
MqttPacketDispatcher _packetDispatcher;
Task _packetReceiverTask;
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
@@ -54,6 +51,9 @@ namespace MQTTnet.Client
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
_rootLogger = logger ?? throw new ArgumentNullException(nameof(logger));
_logger = logger.WithSource(nameof(MqttClient));
_authenticationHandler = new MqttClientAuthenticationHandler(this, logger);
_keepAliveHandler = new MqttClientKeepAliveHandler(this, logger);
}
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
@@ -80,6 +80,12 @@ namespace MQTTnet.Client
remove => _events.DisconnectedEvent.RemoveHandler(value);
}
public event Func<MqttExtendedAuthenticationExchangeEventArgs, Task> ExtendedAuthenticationExchangeAsync
{
add => _authenticationHandler.AddHandler(value);
remove => _authenticationHandler.RemoveHandler(value);
}
public event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync
{
add => _events.InspectPacketEvent.AddHandler(value);
@@ -116,16 +122,14 @@ namespace MQTTnet.Client
_packetIdentifierProvider.Reset();
_packetDispatcher = new MqttPacketDispatcher();
_mqttClientAlive = new CancellationTokenSource();
var mqttClientAliveToken = _mqttClientAlive.Token;
_clientAlive = new CancellationTokenSource();
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_events.InspectPacketEvent, _rootLogger), _rootLogger);
_adapter = adapter ?? throw new InvalidOperationException("The adapter factory did not provide an adapter.");
_channelAdapter = adapter;
if (cancellationToken.CanBeCanceled)
{
connectResult = await ConnectInternal(adapter, cancellationToken).ConfigureAwait(false);
connectResult = await ConnectInternal(cancellationToken).ConfigureAwait(false);
}
else
{
@@ -133,31 +137,10 @@ namespace MQTTnet.Client
// CancellationToken.None or similar.
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
connectResult = await ConnectInternal(adapter, timeout.Token).ConfigureAwait(false);
connectResult = await ConnectInternal(timeout.Token).ConfigureAwait(false);
}
}
if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
{
_logger.Warning("Connecting failed: {0}", connectResult.ResultCode);
await DisconnectInternal(null, null, connectResult).ConfigureAwait(false);
return connectResult;
}
_lastPacketSentTimestamp = DateTime.UtcNow;
var keepAliveInterval = Options.KeepAlivePeriod;
if (connectResult.ServerKeepAlive > 0)
{
_logger.Info($"Using keep alive value ({connectResult.ServerKeepAlive}) sent from the server");
keepAliveInterval = TimeSpan.FromSeconds(connectResult.ServerKeepAlive);
}
if (keepAliveInterval != TimeSpan.Zero)
{
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessages(mqttClientAliveToken), mqttClientAliveToken);
}
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
_logger.Info("Connected");
@@ -211,7 +194,7 @@ namespace MQTTnet.Client
if (Options.ValidateFeatures)
{
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
}
// Sending the DISCONNECT may fail due to connection issues. The resulting exception
@@ -238,11 +221,6 @@ namespace MQTTnet.Client
public async Task PingAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
ThrowIfNotConnected();
if (cancellationToken.CanBeCanceled)
{
await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
@@ -267,7 +245,7 @@ namespace MQTTnet.Client
if (Options.ValidateFeatures)
{
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion);
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
}
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
@@ -293,27 +271,25 @@ namespace MQTTnet.Client
}
}
public Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default)
public Task ReAuthenticateAsync(MqttReAuthenticationOptions options, CancellationToken cancellationToken = default)
{
if (data == null)
if (options == null)
{
throw new ArgumentNullException(nameof(data));
throw new ArgumentNullException(nameof(options));
}
ThrowIfDisposed();
ThrowIfNotConnected();
var authPacket = new MqttAuthPacket
if (cancellationToken.CanBeCanceled)
{
// This must always be equal to the value from the CONNECT packet. So we use it here to ensure that.
AuthenticationMethod = Options.AuthenticationMethod,
AuthenticationData = data.AuthenticationData,
ReasonString = data.ReasonString,
ReasonCode = data.ReasonCode,
UserProperties = data.UserProperties
};
return _authenticationHandler.ReAuthenticate(options, cancellationToken);
}
return Send(authPacket, cancellationToken);
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
return _authenticationHandler.ReAuthenticate(options, timeout.Token);
}
}
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default)
@@ -328,14 +304,14 @@ namespace MQTTnet.Client
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter.Topic);
}
ThrowIfDisposed();
ThrowIfNotConnected();
if (Options.ValidateFeatures)
{
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
}
ThrowIfDisposed();
ThrowIfNotConnected();
var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
@@ -372,7 +348,7 @@ namespace MQTTnet.Client
if (Options.ValidateFeatures)
{
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
}
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
@@ -394,6 +370,27 @@ namespace MQTTnet.Client
return MqttClientResultFactory.UnsubscribeResult.Create(unsubscribePacket, unsubAckPacket);
}
internal Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult)
{
var clientWasConnected = IsConnected;
if (!DisconnectIsPendingOrFinished())
{
return DisconnectCore(sender, exception, connectResult, clientWasConnected);
}
return CompletedTask.Instance;
}
internal Task Send(MqttPacket packet, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
_keepAliveHandler.TrackSentPacket();
return _channelAdapter.SendPacketAsync(packet, cancellationToken);
}
protected override void Dispose(bool disposing)
{
if (disposing)
@@ -434,73 +431,22 @@ namespace MQTTnet.Client
return CompletedTask.Instance;
}
async Task<MqttClientConnectResult> Authenticate(IMqttChannelAdapter channelAdapter, MqttClientOptions options, CancellationToken cancellationToken)
{
MqttClientConnectResult result;
try
{
var connectPacket = MqttPacketFactories.Connect.Create(options);
await Send(connectPacket, cancellationToken).ConfigureAwait(false);
var receivedPacket = await Receive(cancellationToken).ConfigureAwait(false);
if (receivedPacket is MqttConnAckPacket connAckPacket)
{
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion);
}
else if (receivedPacket is MqttAuthPacket)
{
throw new NotSupportedException("Extended authentication handler is not yet supported");
}
else if (receivedPacket == null)
{
throw new MqttCommunicationException("Connection closed.");
}
else
{
throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket}).");
}
}
catch (Exception exception)
{
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
}
// This is no feature. It is basically a backward compatibility option and should be removed in the future.
// The client should not throw any exception if the transport layer connection was successful and the server
// did send a proper ACK packet with a non success response.
if (options.ThrowOnNonSuccessfulConnectResponse)
{
if (result.ResultCode != MqttClientConnectResultCode.Success)
{
_logger.Warning(
"Client will now throw an _MqttConnectingFailedException_. This is obsolete and will be removed in the future. Consider setting _ThrowOnNonSuccessfulResponseFromServer=False_ in client options.");
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
}
}
_logger.Verbose("Authenticated MQTT connection with server established.");
return result;
}
void Cleanup()
{
try
{
_mqttClientAlive?.Cancel(false);
_clientAlive?.Cancel(false);
}
finally
{
_mqttClientAlive?.Dispose();
_mqttClientAlive = null;
_clientAlive?.Dispose();
_clientAlive = null;
_publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = null;
_adapter?.Dispose();
_adapter = null;
_channelAdapter?.Dispose();
_channelAdapter = null;
_packetDispatcher?.Dispose();
_packetDispatcher = null;
@@ -512,24 +458,29 @@ namespace MQTTnet.Client
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand);
}
async Task<MqttClientConnectResult> ConnectInternal(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
async Task<MqttClientConnectResult> ConnectInternal(CancellationToken cancellationToken)
{
var backgroundCancellationToken = _mqttClientAlive.Token;
var clientAliveToken = _clientAlive.Token;
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(clientAliveToken, cancellationToken))
{
_logger.Verbose("Trying to connect with server '{0}'", Options.ChannelOptions);
await channelAdapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
await _channelAdapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
_logger.Verbose("Connection with server established");
_publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
var connectResult = await _authenticationHandler.Authenticate(_channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false);
var connectResult = await Authenticate(channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false);
if (connectResult.ResultCode == MqttClientConnectResultCode.Success)
{
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(backgroundCancellationToken), backgroundCancellationToken);
_publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPacketsLoop(clientAliveToken), clientAliveToken);
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(clientAliveToken), clientAliveToken);
_keepAliveHandler.Enable(connectResult);
}
return connectResult;
@@ -542,13 +493,13 @@ namespace MQTTnet.Client
try
{
if (_adapter != null)
if (_channelAdapter != null)
{
_logger.Verbose("Disconnecting [Timeout={0}]", Options.Timeout);
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
await _adapter.DisconnectAsync(timeout.Token).ConfigureAwait(false);
await _channelAdapter.DisconnectAsync(timeout.Token).ConfigureAwait(false);
}
}
@@ -561,13 +512,14 @@ namespace MQTTnet.Client
try
{
_packetDispatcher?.Dispose(new MqttClientDisconnectedException(exception));
_packetDispatcher.Dispose(new MqttClientDisconnectedException(exception));
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger);
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
_keepAliveHandler.Disable();
await Task.WhenAll(receiverTask, publishPacketReceiverTask).ConfigureAwait(false);
}
catch (Exception innerException)
{
@@ -666,17 +618,6 @@ namespace MQTTnet.Client
return CompletedTask.Instance;
}
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket)
{
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
if (extendedAuthenticationExchangeHandler != null)
{
return extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this));
}
return CompletedTask.Instance;
}
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
{
_disconnectReason = (int)disconnectPacket.ReasonCode;
@@ -689,7 +630,7 @@ namespace MQTTnet.Client
return DisconnectInternal(_packetReceiverTask, null, null);
}
async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken)
async Task ProcessReceivedPublishPacketsLoop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
@@ -709,15 +650,12 @@ namespace MQTTnet.Client
await eventArgs.AcknowledgeAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (ObjectDisposedException)
{
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
_logger.Error(exception, "Error while handling application message");
_logger.Error(exception, "Error while handling application message.");
}
}
}
@@ -772,7 +710,7 @@ namespace MQTTnet.Client
async Task<MqttPacket> Receive(CancellationToken cancellationToken)
{
var packetTask = _adapter.ReceivePacketAsync(cancellationToken);
var packetTask = _channelAdapter.ReceivePacketAsync(cancellationToken);
MqttPacket packet;
if (packetTask.IsCompleted)
@@ -791,7 +729,7 @@ namespace MQTTnet.Client
{
try
{
_logger.Verbose("Start receiving packets");
_logger.Verbose("Start receiving packets.");
while (!cancellationToken.IsCancellationRequested)
{
@@ -829,33 +767,53 @@ namespace MQTTnet.Client
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while receiving packets");
_logger.Warning(exception, "Communication error while receiving packets.");
}
else
{
_logger.Error(exception, "Error while receiving packets");
_logger.Error(exception, "Error while receiving packets.");
}
// The packet dispatcher is set to null when the client is being disposed so it may already being gone!
_packetDispatcher?.FailAll(exception);
_packetDispatcher.FailAll(exception);
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped receiving packets");
_logger.Verbose("Stopped receiving packets.");
}
}
async Task<TResponsePacket> Request<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
internal async Task<TPacket> Receive<TPacket>(int packetIdentifier, CancellationToken cancellationToken) where TPacket : MqttPacket
{
using (var packetAwaitable = _packetDispatcher.AddAwaitable<TPacket>(packetIdentifier))
{
return await packetAwaitable.WaitOneAsync(cancellationToken);
}
}
internal async Task<TResponsePacket> Request<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
{
cancellationToken.ThrowIfCancellationRequested();
ushort packetIdentifier = 0;
// Some packets are a direct response to another packet but have no packet identifier for correlation!
int packetIdentifier;
if (requestPacket is MqttPacketWithIdentifier packetWithIdentifier)
{
packetIdentifier = packetWithIdentifier.PacketIdentifier;
}
else if (requestPacket is MqttPingReqPacket)
{
packetIdentifier = MqttPacketDispatcher.PingRespPacketFakeIdentifier;
}
else if (requestPacket is MqttAuthPacket)
{
packetIdentifier = MqttPacketDispatcher.AuthPacketFakeIdentifier;
}
else
{
throw new MqttProtocolViolationException($"Expecting a response for packet {requestPacket.GetRfcName()} is not supported.");
}
using (var packetAwaitable = _packetDispatcher.AddAwaitable<TResponsePacket>(packetIdentifier))
{
@@ -865,7 +823,7 @@ namespace MQTTnet.Client
}
catch (Exception exception)
{
_logger.Warning(exception, "Error when sending {0} request packet", requestPacket.GetType().Name);
_logger.Warning(exception, "Error when sending request packet ({0}).", requestPacket.GetType().Name);
packetAwaitable.Fail(exception);
}
@@ -877,7 +835,7 @@ namespace MQTTnet.Client
{
if (exception is MqttCommunicationTimedOutException)
{
_logger.Warning("Timeout while waiting for {0} response packet", typeof(TResponsePacket).Name);
_logger.Warning("Timeout while waiting for response packet ({0}).", typeof(TResponsePacket).Name);
}
throw;
@@ -885,15 +843,6 @@ namespace MQTTnet.Client
}
}
Task Send(MqttPacket packet, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
_lastPacketSentTimestamp = DateTime.UtcNow;
return _adapter.SendPacketAsync(packet, cancellationToken);
}
void ThrowIfConnected(string message)
{
if (IsConnected)
@@ -939,11 +888,11 @@ namespace MQTTnet.Client
{
try
{
_mqttClientAlive?.Cancel(false);
_clientAlive?.Cancel(false);
}
catch (Exception exception)
{
_logger.Warning(exception, "Error while initiating disconnect");
_logger.Warning(exception, "Error while initiating disconnect.");
}
}
}
@@ -968,23 +917,31 @@ namespace MQTTnet.Client
{
await ProcessReceivedDisconnectPacket(disconnectPacket).ConfigureAwait(false);
}
else if (packet is MqttAuthPacket authPacket)
{
await ProcessReceivedAuthPacket(authPacket).ConfigureAwait(false);
}
// else if (packet is MqttAuthPacket authPacket)
// {
// await _authenticationHandler.HandleExtendedAuthentication(authPacket, new MqttInitialAuthenticationStrategy(_channelAdapter)).ConfigureAwait(false);
// }
else if (packet is MqttPingRespPacket)
{
_packetDispatcher.TryDispatch(packet);
}
else if (packet is MqttPingReqPacket)
{
throw new MqttProtocolViolationException("The PINGREQ Packet is sent from a client to the server only.");
throw new MqttProtocolViolationException("The PINGREQ packet can only be sent from client to server.");
}
else if (packet is MqttConnAckPacket)
{
throw new MqttProtocolViolationException("The CONNACK packet can only be send while connecting.");
}
else if (packet is MqttConnectPacket)
{
throw new MqttProtocolViolationException("The CONNECT packet can only be sent from client to server.");
}
else
{
if (!_packetDispatcher.TryDispatch(packet))
{
throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
throw new MqttProtocolViolationException($"Received unexpected {packet} packet.");
}
}
}
@@ -1000,75 +957,17 @@ namespace MQTTnet.Client
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while receiving packets");
_logger.Warning(exception, "Communication error while receiving packets.");
}
else
{
_logger.Error(exception, "Error while processing received {0} packet", packet.GetType().Name);
_logger.Error(exception, $"Error while processing received packet ({packet.GetType().Name}).");
}
// The packet dispatcher may already be gone due to disconnect etc!
_packetDispatcher?.FailAll(exception);
_packetDispatcher.FailAll(exception);
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
}
}
async Task TrySendKeepAliveMessages(CancellationToken cancellationToken)
{
try
{
_logger.Verbose("Start sending keep alive packets");
var keepAlivePeriod = Options.KeepAlivePeriod;
while (!cancellationToken.IsCancellationRequested)
{
// Values described here: [MQTT-3.1.2-24].
var timeWithoutPacketSent = DateTime.UtcNow - _lastPacketSentTimestamp;
if (timeWithoutPacketSent > keepAlivePeriod)
{
using (var timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
timeoutCancellationTokenSource.CancelAfter(Options.Timeout);
await PingAsync(timeoutCancellationTokenSource.Token).ConfigureAwait(false);
}
}
// Wait a fixed time in all cases. Calculation of the remaining time is complicated
// due to some edge cases and was buggy in the past. Now we wait several ms because the
// min keep alive value is one second so that the server will wait 1.5 seconds for a PING
// packet.
await Task.Delay(250, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
if (_cleanDisconnectInitiated)
{
return;
}
if (exception is OperationCanceledException)
{
return;
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while sending/receiving keep alive packets");
}
else
{
_logger.Error(exception, "Error exception while sending/receiving keep alive packets");
}
await DisconnectInternal(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped sending keep alive packets");
}
}
}
}
}
+7 -10
View File
@@ -14,6 +14,8 @@ namespace MQTTnet.Client
{
public static class MqttClientExtensions
{
static readonly MqttReAuthenticationOptions EmptyReAuthenticationOptions = new MqttReAuthenticationOptions();
public static Task DisconnectAsync(
this IMqttClient client,
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
@@ -77,6 +79,11 @@ namespace MQTTnet.Client
return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken);
}
public static Task ReAuthenticateAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{
return client.ReAuthenticateAsync(EmptyReAuthenticationOptions, cancellationToken);
}
public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{
if (client.Options == null)
@@ -88,16 +95,6 @@ namespace MQTTnet.Client
return client.ConnectAsync(client.Options, cancellationToken);
}
public static Task SendExtendedAuthenticationExchangeDataAsync(this IMqttClient client, MqttExtendedAuthenticationExchangeData data)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}
return client.SendExtendedAuthenticationExchangeDataAsync(data, CancellationToken.None);
}
public static Task<MqttClientSubscribeResult> SubscribeAsync(this IMqttClient mqttClient, MqttTopicFilter topicFilter, CancellationToken cancellationToken = default)
{
if (mqttClient == null)
@@ -1,36 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
namespace MQTTnet.Client
{
public sealed class DefaultMqttCertificatesProvider : IMqttClientCertificatesProvider
{
readonly X509Certificate2Collection _certificates;
public DefaultMqttCertificatesProvider(X509Certificate2Collection certificates)
{
_certificates = certificates;
}
public DefaultMqttCertificatesProvider(IEnumerable<X509Certificate> certificates)
{
if (certificates != null)
{
_certificates = new X509Certificate2Collection();
foreach (var certificate in certificates)
{
_certificates.Add(certificate);
}
}
}
public X509CertificateCollection GetCertificates()
{
return _certificates;
}
}
}
@@ -1,11 +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.
namespace MQTTnet.Client
{
public interface IMqttClientCertificatesProvider
{
System.Security.Cryptography.X509Certificates.X509CertificateCollection GetCertificates();
}
}
@@ -14,14 +14,8 @@ namespace MQTTnet.Client
public X509Chain Chain { get; set; }
public IMqttClientChannelOptions ClientOptions { get; set; }
#if NET452 || NET461 || NET48
/// <summary>
/// Can be a host string name or an object derived from WebRequest.
/// </summary>
public object Sender { get; set; }
#endif
public SslPolicyErrors SslPolicyErrors { get; set; }
public IMqttClientChannelOptions ClientOptions { get; set; }
}
}
}
@@ -53,9 +53,7 @@ namespace MQTTnet.Client
public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
public IMqttClientCredentialsProvider Credentials { get; set; }
public IMqttExtendedAuthenticationExchangeHandler ExtendedAuthenticationExchangeHandler { get; set; }
/// <summary>
/// Gets or sets the keep alive period.
/// The connection is normally left open by the client so that is can send and receive data at any time.
@@ -103,7 +101,7 @@ namespace MQTTnet.Client
/// <summary>
/// Gets or sets whether an exception should be thrown when the server has sent a non success ACK packet.
/// </summary>
public bool ThrowOnNonSuccessfulConnectResponse { get; set; } = true;
public bool ThrowOnNonSuccessfulResponseFromServer { get; set; } = true;
/// <summary>
/// Gets or sets the timeout which will be applied at socket level and internal operations.
@@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#pragma warning disable CS0612 // Type or member is obsolete
using System;
using System.Collections.Generic;
using System.Linq;
@@ -17,14 +15,10 @@ namespace MQTTnet.Client
public sealed class MqttClientOptionsBuilder
{
readonly MqttClientOptions _options = new MqttClientOptions();
[Obsolete] MqttClientWebSocketProxyOptions _proxyOptions;
MqttClientWebSocketProxyOptions _proxyOptions;
MqttClientTcpOptions _tcpOptions;
MqttClientTlsOptions _tlsOptions;
[Obsolete] MqttClientOptionsBuilderTlsParameters _tlsParameters;
MqttClientOptionsBuilderTlsParameters _tlsParameters;
MqttClientWebSocketOptions _webSocketOptions;
public MqttClientOptions Build()
@@ -34,17 +28,11 @@ namespace MQTTnet.Client
throw new InvalidOperationException("A channel must be set.");
}
// The user can specify the TCP options with already configured TLS options
// or start with TLS settings not knowing which transport will be used (depending
// on the order of called methods from the builder).
// The builder prefers the explicitly set TLS options!
var tlsOptions = _tlsOptions ?? _tcpOptions?.TlsOptions;
if (_tlsParameters != null)
{
if (_tlsParameters?.UseTls == true)
{
tlsOptions = new MqttClientTlsOptions
var tlsOptions = new MqttClientTlsOptions
{
UseTls = true,
SslProtocol = _tlsParameters.SslProtocol,
@@ -52,21 +40,26 @@ namespace MQTTnet.Client
CertificateValidationHandler = _tlsParameters.CertificateValidationHandler,
IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors,
IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors,
ClientCertificatesProvider = _tlsParameters.CertificatesProvider,
#if WINDOWS_UWP
Certificates = _tlsParameters.Certificates?.Select(c => c.ToArray()).ToList(),
#else
Certificates = _tlsParameters.Certificates?.ToList(),
#endif
#if NETCOREAPP3_1_OR_GREATER
ApplicationProtocols = _tlsParameters.ApplicationProtocols,
#endif
};
}
}
if (_tcpOptions != null)
{
_tcpOptions.TlsOptions = tlsOptions;
}
else if (_webSocketOptions != null)
{
_webSocketOptions.TlsOptions = tlsOptions;
if (_tcpOptions != null)
{
_tcpOptions.TlsOptions = tlsOptions;
}
else if (_webSocketOptions != null)
{
_webSocketOptions.TlsOptions = tlsOptions;
}
}
}
if (_proxyOptions != null)
@@ -86,7 +79,7 @@ namespace MQTTnet.Client
return _options;
}
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data = null)
{
_options.AuthenticationMethod = method;
_options.AuthenticationData = data;
@@ -117,7 +110,6 @@ namespace MQTTnet.Client
return this;
}
[Obsolete("Use WithTcpServer(... configure) or WithWebSocketServer(... configure) instead.")]
public MqttClientOptionsBuilder WithConnectionUri(Uri uri)
{
if (uri == null)
@@ -134,16 +126,12 @@ namespace MQTTnet.Client
break;
case "mqtts":
WithTcpServer(uri.Host, port)
.WithTlsOptions(
o =>
{
});
WithTcpServer(uri.Host, port).WithTls();
break;
case "ws":
case "wss":
WithWebSocketServer(o => o.WithUri(uri.ToString()));
WithWebSocketServer(uri.ToString());
break;
default:
@@ -161,7 +149,6 @@ namespace MQTTnet.Client
return this;
}
[Obsolete("Use WithTcpServer(... configure) or WithWebSocketServer(... configure) instead.")]
public MqttClientOptionsBuilder WithConnectionUri(string uri)
{
return WithConnectionUri(new Uri(uri, UriKind.Absolute));
@@ -190,12 +177,6 @@ namespace MQTTnet.Client
return this;
}
public MqttClientOptionsBuilder WithExtendedAuthenticationExchangeHandler(IMqttExtendedAuthenticationExchangeHandler handler)
{
_options.ExtendedAuthenticationExchangeHandler = handler;
return this;
}
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value)
{
_options.KeepAlivePeriod = value;
@@ -225,16 +206,6 @@ namespace MQTTnet.Client
return this;
}
/// <summary>
/// The client will not throw an exception when the MQTT server responses with a non success ACK packet.
/// This will become the default behavior in future versions of the library.
/// </summary>
public MqttClientOptionsBuilder WithoutThrowOnNonSuccessfulConnectResponse()
{
_options.ThrowOnNonSuccessfulConnectResponse = false;
return this;
}
public MqttClientOptionsBuilder WithProtocolVersion(MqttProtocolVersion value)
{
if (value == MqttProtocolVersion.Unknown)
@@ -246,7 +217,6 @@ namespace MQTTnet.Client
return this;
}
[Obsolete("Use WithWebSocketServer(... configure) instead.")]
public MqttClientOptionsBuilder WithProxy(
string address,
string username = null,
@@ -268,7 +238,6 @@ namespace MQTTnet.Client
return this;
}
[Obsolete("Use WithWebSocketServer(... configure) instead.")]
public MqttClientOptionsBuilder WithProxy(Action<MqttClientWebSocketProxyOptions> optionsBuilder)
{
if (optionsBuilder == null)
@@ -339,20 +308,17 @@ namespace MQTTnet.Client
return this;
}
[Obsolete("Use WithTlsOptions(... configure) instead.")]
public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters)
{
_tlsParameters = parameters;
return this;
}
[Obsolete("Use WithTlsOptions(... configure) instead.")]
public MqttClientOptionsBuilder WithTls()
{
return WithTls(new MqttClientOptionsBuilderTlsParameters { UseTls = true });
}
[Obsolete("Use WithTlsOptions(... configure) instead.")]
public MqttClientOptionsBuilder WithTls(Action<MqttClientOptionsBuilderTlsParameters> optionsBuilder)
{
if (optionsBuilder == null)
@@ -369,26 +335,6 @@ namespace MQTTnet.Client
return this;
}
public MqttClientOptionsBuilder WithTlsOptions(MqttClientTlsOptions tlsOptions)
{
_tlsOptions = tlsOptions;
return this;
}
public MqttClientOptionsBuilder WithTlsOptions(Action<MqttClientTlsOptionsBuilder> configure)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
var builder = new MqttClientTlsOptionsBuilder();
configure.Invoke(builder);
_tlsOptions = builder.Build();
return this;
}
public MqttClientOptionsBuilder WithTopicAliasMaximum(ushort topicAliasMaximum)
{
_options.TopicAliasMaximum = topicAliasMaximum;
@@ -420,7 +366,6 @@ namespace MQTTnet.Client
return this;
}
[Obsolete("Use WithWebSocketServer(... configure) instead.")]
public MqttClientOptionsBuilder WithWebSocketServer(string uri, MqttClientOptionsBuilderWebSocketParameters parameters = null)
{
_webSocketOptions = new MqttClientWebSocketOptions
@@ -433,21 +378,6 @@ namespace MQTTnet.Client
return this;
}
public MqttClientOptionsBuilder WithWebSocketServer(Action<MqttClientWebSocketOptionsBuilder> configure)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
var webSocketOptionsBuilder = new MqttClientWebSocketOptionsBuilder();
configure.Invoke(webSocketOptionsBuilder);
_webSocketOptions = webSocketOptionsBuilder.Build();
return this;
}
[Obsolete("Use WithWebSocketServer(... configure) instead.")]
public MqttClientOptionsBuilder WithWebSocketServer(Action<MqttClientWebSocketOptions> optionsBuilder)
{
if (optionsBuilder == null)
@@ -479,12 +409,6 @@ namespace MQTTnet.Client
return this;
}
public MqttClientOptionsBuilder WithWillMessageExpiryInterval(uint willMessageExpiryInterval)
{
_options.WillMessageExpiryInterval = willMessageExpiryInterval;
return this;
}
public MqttClientOptionsBuilder WithWillPayload(byte[] willPayload)
{
_options.WillPayload = willPayload;
@@ -5,14 +5,12 @@
using System;
using System.Collections.Generic;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
namespace MQTTnet.Client
{
[Obsolete("Use methods from MqttClientOptionsBuilder instead.")]
public sealed class MqttClientOptionsBuilderTlsParameters
{
IEnumerable<System.Security.Cryptography.X509Certificates.X509Certificate> _obsoleteCertificates;
public bool UseTls { get; set; }
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; }
@@ -26,24 +24,7 @@ namespace MQTTnet.Client
#if WINDOWS_UWP
public IEnumerable<IEnumerable<byte>> Certificates { get; set; }
#else
[Obsolete("Use CertificatesProvider instead.")]
public IEnumerable<System.Security.Cryptography.X509Certificates.X509Certificate> Certificates
{
get => _obsoleteCertificates;
set
{
_obsoleteCertificates = value;
if (value == null)
{
CertificatesProvider = null;
}
else
{
CertificatesProvider = new DefaultMqttCertificatesProvider(value);
}
}
}
public IEnumerable<X509Certificate> Certificates { get; set; }
#endif
#if NETCOREAPP3_1_OR_GREATER
@@ -55,7 +36,5 @@ namespace MQTTnet.Client
public bool IgnoreCertificateChainErrors { get; set; }
public bool IgnoreCertificateRevocationErrors { get; set; }
public IMqttClientCertificatesProvider CertificatesProvider { get; set; }
}
}
@@ -2,13 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Net;
namespace MQTTnet.Client
{
[Obsolete("Use dedicated methods in MqttClientOptionsBuilder.")]
public class MqttClientOptionsBuilderWebSocketParameters
{
public IDictionary<string, string> RequestHeaders { get; set; }
@@ -17,7 +17,7 @@ namespace MQTTnet.Client
return options.Port.Value;
}
return !(options.TlsOptions?.UseTls ?? false) ? 1883 : 8883;
return !options.TlsOptions.UseTls ? 1883 : 8883;
}
}
}
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
@@ -24,39 +23,22 @@ namespace MQTTnet.Client
public X509RevocationMode RevocationMode { get; set; } = X509RevocationMode.Online;
/// <summary>
/// Gets or sets the provider for certificates.
/// This provider gets called whenever the client wants to connect
/// with the server and requires certificates for authentication.
/// The implementation may return different certificates each time.
/// </summary>
public IMqttClientCertificatesProvider ClientCertificatesProvider { get; set; }
#if NETCOREAPP3_1_OR_GREATER
public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
public CipherSuitesPolicy CipherSuitesPolicy { get; set; }
public EncryptionPolicy EncryptionPolicy { get; set; } = EncryptionPolicy.RequireEncryption;
public bool AllowRenegotiation { get; set; } = true;
#if WINDOWS_UWP
public List<byte[]> Certificates { get; set; }
#else
public List<X509Certificate> Certificates { get; set; }
#endif
/// <summary>
/// Gets or sets the target host.
/// If the value is null or empty the same host as the TCP socket host will be used.
/// </summary>
public string TargetHost { get; set; }
#if NETCOREAPP3_1_OR_GREATER
public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; }
public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; }
#endif
#if NET48 || NETCOREAPP3_1_OR_GREATER
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
#else
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
#endif
#if NET7_0_OR_GREATER
public X509Certificate2Collection TrustChain { get; set; }
#endif
}
}
@@ -1,145 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
#if NETCOREAPP3_1_OR_GREATER
using System.Net.Security;
#endif
namespace MQTTnet.Client
{
public sealed class MqttClientTlsOptionsBuilder
{
readonly MqttClientTlsOptions _tlsOptions = new MqttClientTlsOptions
{
// If someone used this builder the change is very very high that TLS
// should be actually used.
UseTls = true
};
public MqttClientTlsOptions Build()
{
return _tlsOptions;
}
public MqttClientTlsOptionsBuilder UseTls(bool useTls = true)
{
_tlsOptions.UseTls = useTls;
return this;
}
public MqttClientTlsOptionsBuilder WithAllowUntrustedCertificates(bool allowUntrustedCertificates = true)
{
_tlsOptions.AllowUntrustedCertificates = allowUntrustedCertificates;
return this;
}
public MqttClientTlsOptionsBuilder WithCertificateValidationHandler(Func<MqttClientCertificateValidationEventArgs, bool> certificateValidationHandler)
{
if (certificateValidationHandler == null)
{
throw new ArgumentNullException(nameof(certificateValidationHandler));
}
_tlsOptions.CertificateValidationHandler = certificateValidationHandler;
return this;
}
public MqttClientTlsOptionsBuilder WithClientCertificates(IEnumerable<X509Certificate2> certificates)
{
if (certificates == null)
{
throw new ArgumentNullException(nameof(certificates));
}
_tlsOptions.ClientCertificatesProvider = new DefaultMqttCertificatesProvider(certificates);
return this;
}
public MqttClientTlsOptionsBuilder WithClientCertificates(X509Certificate2Collection certificates)
{
if (certificates == null)
{
throw new ArgumentNullException(nameof(certificates));
}
_tlsOptions.ClientCertificatesProvider = new DefaultMqttCertificatesProvider(certificates);
return this;
}
public MqttClientTlsOptionsBuilder WithClientCertificatesProvider(IMqttClientCertificatesProvider clientCertificatesProvider)
{
_tlsOptions.ClientCertificatesProvider = clientCertificatesProvider;
return this;
}
public MqttClientTlsOptionsBuilder WithIgnoreCertificateChainErrors(bool ignoreCertificateChainErrors = true)
{
_tlsOptions.IgnoreCertificateChainErrors = ignoreCertificateChainErrors;
return this;
}
public MqttClientTlsOptionsBuilder WithIgnoreCertificateRevocationErrors(bool ignoreCertificateRevocationErrors = true)
{
_tlsOptions.IgnoreCertificateRevocationErrors = ignoreCertificateRevocationErrors;
return this;
}
public MqttClientTlsOptionsBuilder WithRevocationMode(X509RevocationMode revocationMode)
{
_tlsOptions.RevocationMode = revocationMode;
return this;
}
public MqttClientTlsOptionsBuilder WithSslProtocols(SslProtocols sslProtocols)
{
_tlsOptions.SslProtocol = sslProtocols;
return this;
}
public MqttClientTlsOptionsBuilder WithTargetHost(string targetHost)
{
_tlsOptions.TargetHost = targetHost;
return this;
}
#if NETCOREAPP3_1_OR_GREATER
public MqttClientTlsOptionsBuilder WithAllowRenegotiation(bool allowRenegotiation = true)
{
_tlsOptions.AllowRenegotiation = allowRenegotiation;
return this;
}
public MqttClientTlsOptionsBuilder WithApplicationProtocols(List<SslApplicationProtocol> applicationProtocols)
{
_tlsOptions.ApplicationProtocols = applicationProtocols;
return this;
}
public MqttClientTlsOptionsBuilder WithCipherSuitesPolicy(CipherSuitesPolicy cipherSuitePolicy)
{
_tlsOptions.CipherSuitesPolicy = cipherSuitePolicy;
return this;
}
public MqttClientTlsOptionsBuilder WithCipherSuitesPolicy(EncryptionPolicy encryptionPolicy)
{
_tlsOptions.EncryptionPolicy = encryptionPolicy;
return this;
}
#endif
#if NET7_0_OR_GREATER
public MqttClientTlsOptionsBuilder WithTrustChain(X509Certificate2Collection chain)
{
_tlsOptions.TrustChain = chain;
return this;
}
#endif
}
}
@@ -2,19 +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.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.WebSockets;
namespace MQTTnet.Client
{
public sealed class MqttClientWebSocketOptions : IMqttClientChannelOptions
{
public CookieContainer CookieContainer { get; set; }
public ICredentials Credentials { get; set; }
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
public IDictionary<string, string> RequestHeaders { get; set; }
@@ -22,34 +18,12 @@ namespace MQTTnet.Client
public ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" };
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
public string Uri { get; set; }
public override string ToString()
{
return Uri;
}
#if !NETSTANDARD1_3
#if !WINDOWS_UWP
/// <summary>
/// Gets or sets the keep alive interval for the Web Socket connection.
/// This is not related to the keep alive interval for the MQTT protocol.
/// </summary>
public TimeSpan KeepAliveInterval { get; set; } = WebSocket.DefaultKeepAliveInterval;
/// <summary>
/// Gets or sets whether the default (system) credentials should be used when connecting via Web Socket connection.
/// This is not related to the credentials which are used for the MQTT protocol.
/// </summary>
public bool UseDefaultCredentials { get; set; }
#else
/// <summary>
/// Gets or sets the keep alive interval for the Web Socket connection.
/// This is not related to the keep alive interval for the MQTT protocol.
/// </summary>
public TimeSpan KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(30);
#endif
#endif
}
}
@@ -1,85 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Net;
namespace MQTTnet.Client
{
public sealed class MqttClientWebSocketOptionsBuilder
{
readonly MqttClientWebSocketOptions _webSocketOptions = new MqttClientWebSocketOptions();
public MqttClientWebSocketOptions Build()
{
return _webSocketOptions;
}
public MqttClientWebSocketOptionsBuilder WithCookieContainer(CookieContainer cookieContainer)
{
_webSocketOptions.CookieContainer = cookieContainer;
return this;
}
public MqttClientWebSocketOptionsBuilder WithCookieContainer(ICredentials credentials)
{
_webSocketOptions.Credentials = credentials;
return this;
}
public MqttClientWebSocketOptionsBuilder WithProxyOptions(MqttClientWebSocketProxyOptions proxyOptions)
{
_webSocketOptions.ProxyOptions = proxyOptions;
return this;
}
public MqttClientWebSocketOptionsBuilder WithProxyOptions(Action<MqttClientWebSocketProxyOptionsBuilder> configure)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
var proxyOptionsBuilder = new MqttClientWebSocketProxyOptionsBuilder();
configure.Invoke(proxyOptionsBuilder);
_webSocketOptions.ProxyOptions = proxyOptionsBuilder.Build();
return this;
}
public MqttClientWebSocketOptionsBuilder WithRequestHeaders(IDictionary<string, string> requestHeaders)
{
_webSocketOptions.RequestHeaders = requestHeaders;
return this;
}
public MqttClientWebSocketOptionsBuilder WithSubProtocols(ICollection<string> subProtocols)
{
_webSocketOptions.SubProtocols = subProtocols;
return this;
}
public MqttClientWebSocketOptionsBuilder WithUri(string uri)
{
_webSocketOptions.Uri = uri;
return this;
}
#if !NETSTANDARD1_3
public MqttClientWebSocketOptionsBuilder WithKeepAliveInterval(TimeSpan keepAliveInterval)
{
_webSocketOptions.KeepAliveInterval = keepAliveInterval;
return this;
}
#endif
#if !WINDOWS_UWP && !NETSTANDARD1_3
public MqttClientWebSocketOptionsBuilder WithUseDefaultCredentials(bool useDefaultCredentials = true)
{
_webSocketOptions.UseDefaultCredentials = useDefaultCredentials;
return this;
}
#endif
}
}
@@ -16,8 +16,6 @@ namespace MQTTnet.Client
public bool BypassOnLocal { get; set; }
public bool UseDefaultCredentials { get; set; }
public string[] BypassList { get; set; }
}
}
@@ -1,67 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Linq;
namespace MQTTnet.Client
{
public sealed class MqttClientWebSocketProxyOptionsBuilder
{
readonly MqttClientWebSocketProxyOptions _proxyOptions = new MqttClientWebSocketProxyOptions();
public MqttClientWebSocketProxyOptionsBuilder WithAddress(string address)
{
_proxyOptions.Address = address;
return this;
}
public MqttClientWebSocketProxyOptionsBuilder WithUsername(string username)
{
_proxyOptions.Username = username;
return this;
}
public MqttClientWebSocketProxyOptionsBuilder WithPassword(string password)
{
_proxyOptions.Password = password;
return this;
}
public MqttClientWebSocketProxyOptionsBuilder WithDomain(string domain)
{
_proxyOptions.Domain = domain;
return this;
}
public MqttClientWebSocketProxyOptionsBuilder WithBypassOnLocal(bool bypassOnLocal = true)
{
_proxyOptions.BypassOnLocal = bypassOnLocal;
return this;
}
public MqttClientWebSocketProxyOptionsBuilder WithBypassList(string[] bypassList)
{
_proxyOptions.BypassList = bypassList;
return this;
}
public MqttClientWebSocketProxyOptionsBuilder WithBypassList(IEnumerable<string> bypassList)
{
_proxyOptions.BypassList = bypassList?.ToArray();
return this;
}
public MqttClientWebSocketProxyOptionsBuilder WithUseDefaultCredentials(bool useDefaultCredentials = true)
{
_proxyOptions.UseDefaultCredentials = useDefaultCredentials;
return this;
}
public MqttClientWebSocketProxyOptions Build()
{
return _proxyOptions;
}
}
}
@@ -16,8 +16,6 @@ namespace MQTTnet.Diagnostics
return "net461";
#elif NET472
return "net472";
#elif NET48
return "net48";
#elif NETSTANDARD1_3
return "netstandard1.3";
#elif NETSTANDARD2_0
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Client;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Formatter
{
public sealed class MqttAuthPacketFactory
{
public MqttAuthPacket CreateReAuthenticationPacket(MqttClientOptions clientOptions, MqttReAuthenticationOptions reAuthenticationOptions)
{
if (clientOptions == null)
{
throw new ArgumentNullException(nameof(clientOptions));
}
if (reAuthenticationOptions == null)
{
throw new ArgumentNullException(nameof(reAuthenticationOptions));
}
return new MqttAuthPacket
{
// The authentication method cannot change and must be always the same as long as the client is connected.
AuthenticationMethod = clientOptions.AuthenticationMethod,
ReasonCode = MqttAuthenticateReasonCode.ReAuthenticate,
AuthenticationData = reAuthenticationOptions.AuthenticationData,
UserProperties = reAuthenticationOptions.UserProperties,
ReasonString = null
};
}
}
}
@@ -2,12 +2,54 @@
// 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.Exceptions;
using MQTTnet.Protocol;
namespace MQTTnet.Formatter
{
public static class MqttConnectReasonCodeConverter
{
public static MqttConnectReasonCode ToConnectReasonCode(MqttConnectReturnCode returnCode)
{
switch (returnCode)
{
case MqttConnectReturnCode.ConnectionAccepted:
{
return MqttConnectReasonCode.Success;
}
case MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion:
{
return MqttConnectReasonCode.UnsupportedProtocolVersion;
}
case MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword:
{
return MqttConnectReasonCode.BadUserNameOrPassword;
}
case MqttConnectReturnCode.ConnectionRefusedIdentifierRejected:
{
return MqttConnectReasonCode.ClientIdentifierNotValid;
}
case MqttConnectReturnCode.ConnectionRefusedServerUnavailable:
{
return MqttConnectReasonCode.ServerUnavailable;
}
case MqttConnectReturnCode.ConnectionRefusedNotAuthorized:
{
return MqttConnectReasonCode.NotAuthorized;
}
default:
{
throw new MqttProtocolViolationException("Unable to convert connect reason code (MQTTv5) to return code (MQTTv3).");
}
}
}
public static MqttConnectReturnCode ToConnectReturnCode(MqttConnectReasonCode reasonCode)
{
switch (reasonCode)
@@ -17,13 +59,13 @@ namespace MQTTnet.Formatter
return MqttConnectReturnCode.ConnectionAccepted;
}
case MqttConnectReasonCode.BadAuthenticationMethod:
case MqttConnectReasonCode.Banned:
case MqttConnectReasonCode.NotAuthorized:
{
return MqttConnectReturnCode.ConnectionRefusedNotAuthorized;
}
case MqttConnectReasonCode.BadAuthenticationMethod:
case MqttConnectReasonCode.BadUserNameOrPassword:
{
return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
@@ -33,12 +75,12 @@ namespace MQTTnet.Formatter
{
return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected;
}
case MqttConnectReasonCode.UnsupportedProtocolVersion:
{
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
}
case MqttConnectReasonCode.UseAnotherServer:
case MqttConnectReasonCode.ServerUnavailable:
case MqttConnectReasonCode.ServerBusy:
@@ -48,7 +90,10 @@ namespace MQTTnet.Formatter
}
default:
{
// This is the most best matching value.
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
}
}
}
}
@@ -5,107 +5,45 @@
using MQTTnet.Client;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Server.Disconnecting;
namespace MQTTnet.Formatter
{
public sealed class MqttDisconnectPacketFactory
{
static readonly MqttDisconnectPacket DefaultNormalDisconnection = new MqttDisconnectPacket
readonly MqttDisconnectPacket _normalDisconnection = new MqttDisconnectPacket
{
ReasonCode = MqttDisconnectReasonCode.NormalDisconnection,
UserProperties = null,
ReasonString = null,
ServerReference = null,
SessionExpiryInterval = 0
};
static readonly MqttDisconnectPacket DefaultServerShuttingDown = new MqttDisconnectPacket
{
ReasonCode = MqttDisconnectReasonCode.ServerShuttingDown,
UserProperties = null,
ReasonString = null,
ServerReference = null,
SessionExpiryInterval = 0
};
static readonly MqttDisconnectPacket DefaultUnspecifiedError = new MqttDisconnectPacket
{
ReasonCode = MqttDisconnectReasonCode.UnspecifiedError,
UserProperties = null,
ReasonString = null,
ServerReference = null,
SessionExpiryInterval = 0
ReasonCode = MqttDisconnectReasonCode.NormalDisconnection
};
public MqttDisconnectPacket Create(MqttDisconnectReasonCode reasonCode)
{
if (reasonCode == MqttDisconnectReasonCode.NormalDisconnection)
{
return DefaultNormalDisconnection;
}
if (reasonCode == MqttDisconnectReasonCode.ServerShuttingDown)
{
return DefaultServerShuttingDown;
}
if (reasonCode == MqttDisconnectReasonCode.UnspecifiedError)
{
return DefaultUnspecifiedError;
return _normalDisconnection;
}
return new MqttDisconnectPacket
{
ReasonCode = reasonCode,
UserProperties = null,
ReasonString = null,
ServerReference = null,
SessionExpiryInterval = 0
};
}
public MqttDisconnectPacket Create(MqttServerStopOptions serverStopOptions)
{
if (serverStopOptions == null)
{
return DefaultServerShuttingDown;
}
return Create(serverStopOptions.DefaultClientDisconnectOptions);
}
public MqttDisconnectPacket Create(MqttServerClientDisconnectOptions clientDisconnectOptions)
{
if (clientDisconnectOptions == null)
{
return DefaultNormalDisconnection;
}
return new MqttDisconnectPacket
{
ReasonCode = clientDisconnectOptions.ReasonCode,
UserProperties = clientDisconnectOptions.UserProperties,
ReasonString = clientDisconnectOptions.ReasonString,
ServerReference = clientDisconnectOptions.ServerReference,
SessionExpiryInterval = 0 // TODO: Not yet supported!
ReasonCode = reasonCode
};
}
public MqttDisconnectPacket Create(MqttClientDisconnectOptions clientDisconnectOptions)
{
var packet = new MqttDisconnectPacket();
if (clientDisconnectOptions == null)
{
return DefaultNormalDisconnection;
packet.ReasonCode = MqttDisconnectReasonCode.NormalDisconnection;
}
else
{
packet.ReasonCode = (MqttDisconnectReasonCode)clientDisconnectOptions.Reason;
packet.UserProperties = clientDisconnectOptions.UserProperties;
packet.SessionExpiryInterval = clientDisconnectOptions.SessionExpiryInterval;
}
return new MqttDisconnectPacket
{
ReasonCode = (MqttDisconnectReasonCode)clientDisconnectOptions.Reason,
UserProperties = clientDisconnectOptions.UserProperties,
SessionExpiryInterval = clientDisconnectOptions.SessionExpiryInterval
};
return packet;
}
}
}
@@ -6,6 +6,8 @@ namespace MQTTnet.Formatter
{
public static class MqttPacketFactories
{
public static MqttAuthPacketFactory Auth { get; } = new MqttAuthPacketFactory();
public static MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
public static MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
@@ -130,9 +130,6 @@ namespace MQTTnet.Formatter.V5
MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce
};
// Also set the return code of MQTT 3.1.1 for backward compatibility and debugging purposes.
packet.ReturnCode = MqttConnectReasonCodeConverter.ToConnectReturnCode(packet.ReasonCode);
var propertiesReader = new MqttV5PropertiesReader(_bufferReader);
while (propertiesReader.MoveNext())
{
@@ -6,6 +6,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
@@ -16,7 +18,6 @@ using Windows.Security.Cryptography.Certificates;
using MQTTnet.Channel;
using MQTTnet.Client;
using MQTTnet.Server;
using System.Runtime.InteropServices.WindowsRuntime;
namespace MQTTnet.Implementations
{
@@ -125,19 +126,17 @@ namespace MQTTnet.Implementations
private static Certificate LoadCertificate(IMqttClientChannelOptions options)
{
var certificates = options.TlsOptions?.ClientCertificatesProvider?.GetCertificates();
if (certificates == null || certificates.Count == 0)
if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any())
{
return null;
}
if (certificates.Count > 1)
if (options.TlsOptions.Certificates.Count > 1)
{
throw new NotSupportedException("Only one client certificate is supported when using 'uap10.0'.");
}
return new Certificate(certificates[0].Export(X509ContentType.Cert).AsBuffer());
return new Certificate(options.TlsOptions.Certificates.First().AsBuffer());
}
private IEnumerable<ChainValidationResult> ResolveIgnorableServerCertificateErrors()
@@ -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;
using MQTTnet.Internal;
namespace MQTTnet.Implementations
@@ -73,7 +73,7 @@ namespace MQTTnet.Implementations
{
socket.Bind(_tcpOptions.LocalEndpoint);
}
socket.ReceiveBufferSize = _tcpOptions.BufferSize;
socket.SendBufferSize = _tcpOptions.BufferSize;
socket.SendTimeout = (int)_clientOptions.Timeout.TotalMilliseconds;
@@ -100,12 +100,6 @@ namespace MQTTnet.Implementations
if (_tcpOptions.TlsOptions?.UseTls == true)
{
var targetHost = _tcpOptions.TlsOptions.TargetHost;
if (string.IsNullOrEmpty(targetHost))
{
targetHost = _tcpOptions.Server;
}
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
try
{
@@ -115,35 +109,16 @@ namespace MQTTnet.Implementations
ApplicationProtocols = _tcpOptions.TlsOptions.ApplicationProtocols,
ClientCertificates = LoadCertificates(),
EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol,
CertificateRevocationCheckMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors
? X509RevocationMode.NoCheck
: _tcpOptions.TlsOptions.RevocationMode,
TargetHost = targetHost,
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy,
EncryptionPolicy = _tcpOptions.TlsOptions.EncryptionPolicy,
AllowRenegotiation = _tcpOptions.TlsOptions.AllowRenegotiation
CertificateRevocationCheckMode =
_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode,
TargetHost = _tcpOptions.Server,
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy
};
#if NET7_0_OR_GREATER
if (_tcpOptions.TlsOptions.TrustChain?.Count > 0)
{
sslOptions.CertificateChainPolicy = new X509ChainPolicy
{
TrustMode = X509ChainTrustMode.CustomRootTrust,
VerificationFlags = X509VerificationFlags.IgnoreEndRevocationUnknown,
RevocationMode = _tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors
? X509RevocationMode.NoCheck
: _tcpOptions.TlsOptions.RevocationMode
};
sslOptions.CertificateChainPolicy.CustomTrustStore.AddRange(_tcpOptions.TlsOptions.TrustChain);
}
#endif
await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
#else
await sslStream.AuthenticateAsClientAsync(
targetHost,
_tcpOptions.Server,
LoadCertificates(),
_tcpOptions.TlsOptions.SslProtocol,
!_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors)
@@ -315,7 +290,18 @@ namespace MQTTnet.Implementations
X509CertificateCollection LoadCertificates()
{
return _tcpOptions.TlsOptions.ClientCertificatesProvider?.GetCertificates();
var certificates = new X509CertificateCollection();
if (_tcpOptions.TlsOptions.Certificates == null)
{
return certificates;
}
foreach (var certificate in _tcpOptions.TlsOptions.Certificates)
{
certificates.Add(certificate);
}
return certificates;
}
}
}
@@ -231,7 +231,6 @@ namespace MQTTnet.Implementations
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger))
{
clientAdapter.AllowPacketFragmentation = _options.AllowPacketFragmentation;
await clientHandler(clientAdapter).ConfigureAwait(false);
}
}
@@ -256,7 +255,6 @@ namespace MQTTnet.Implementations
{
try
{
// ReSharper disable once MethodHasAsyncOverload
stream?.Dispose();
clientSocket?.Dispose();
}
@@ -46,13 +46,13 @@ namespace MQTTnet.Implementations
var uri = _options.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
{
if (_options.TlsOptions?.UseTls == true)
if (_options.TlsOptions?.UseTls == false)
{
uri = "wss://" + uri;
uri = "ws://" + uri;
}
else
{
uri = "ws://" + uri;
uri = "wss://" + uri;
}
}
@@ -155,26 +155,14 @@ namespace MQTTnet.Implementations
throw new NotSupportedException("Proxies are not supported when using 'netstandard 1.3'.");
#else
var proxyUri = new Uri(_options.ProxyOptions.Address);
WebProxy webProxy;
if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password))
{
var credentials = new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain);
webProxy = new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList, credentials);
}
else
{
webProxy = new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
}
if (_options.ProxyOptions.UseDefaultCredentials)
{
// Only update the property if required because setting it to false will alter
// the used credentials internally!
webProxy.UseDefaultCredentials = true;
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList, credentials);
}
return webProxy;
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
#endif
}
@@ -206,35 +194,19 @@ namespace MQTTnet.Implementations
clientWebSocket.Options.Cookies = _options.CookieContainer;
}
if (_options.TlsOptions?.UseTls == true)
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
{
var certificates = _options.TlsOptions?.ClientCertificatesProvider?.GetCertificates();
if (certificates?.Count > 0)
clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
foreach (var certificate in _options.TlsOptions.Certificates)
{
clientWebSocket.Options.ClientCertificates = certificates;
#if WINDOWS_UWP
clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
#else
clientWebSocket.Options.ClientCertificates.Add(certificate);
#endif
}
}
#if !NETSTANDARD1_3
#if !WINDOWS_UWP
// Only set the value if it is actually true. This property is not supported on all platforms
// and will throw a _PlatformNotSupported_ (i.e. WASM) exception when being used regardless of the actual value.
if (_options.UseDefaultCredentials)
{
clientWebSocket.Options.UseDefaultCredentials = _options.UseDefaultCredentials;
}
if (_options.KeepAliveInterval != WebSocket.DefaultKeepAliveInterval)
{
clientWebSocket.Options.KeepAliveInterval = _options.KeepAliveInterval;
}
#endif
#endif
if (_options.Credentials != null)
{
clientWebSocket.Options.Credentials = _options.Credentials;
}
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
if (certificateValidationHandler != null)
{
@@ -244,20 +216,10 @@ namespace MQTTnet.Implementations
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard2.0'.");
#elif WINDOWS_UWP
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'uap10.0'.");
#elif NET452 || NET461 || NET48
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) =>
{
var context = new MqttClientCertificateValidationEventArgs
{
Sender = sender,
Certificate = certificate,
Chain = chain,
SslPolicyErrors = sslPolicyErrors,
ClientOptions = _options
};
return certificateValidationHandler(context);
};
#elif NET452
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net452'.");
#elif NET461
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net461'.");
#else
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
@@ -276,4 +238,4 @@ namespace MQTTnet.Implementations
}
}
}
}
}
+2 -7
View File
@@ -104,22 +104,17 @@ namespace MQTTnet.Internal
_signal.Dispose();
}
public MqttPacketBusItem DropFirstItem(MqttPacketBusPartition partition)
public void DropFirstItem(MqttPacketBusPartition partition)
{
lock (_syncRoot)
{
var partitionInstance = _partitions[(int)partition];
if (partitionInstance.Count > 0)
if (partitionInstance.Any())
{
var firstItem = partitionInstance.First.Value;
partitionInstance.RemoveFirst();
return firstItem;
}
}
return null;
}
public void EnqueueItem(MqttPacketBusItem item, MqttPacketBusPartition partition)
@@ -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.Threading;
using System.Threading.Tasks;
using MQTTnet.Exceptions;
namespace MQTTnet.Internal
{
public static class MqttTaskTimeout
{
public static async Task WaitAsync(Func<CancellationToken, Task> action, TimeSpan timeout, CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException(nameof(action));
using (var timeoutCts = new CancellationTokenSource(timeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
{
try
{
await action(linkedCts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException exception)
{
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
if (timeoutReached)
{
throw new MqttCommunicationTimedOutException(exception);
}
throw;
}
}
}
}
}
@@ -19,6 +19,7 @@ namespace MQTTnet.LowLevelClient
readonly IMqttClientAdapterFactory _clientAdapterFactory;
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
readonly MqttNetSourceLogger _logger;
readonly IMqttNetLogger _rootLogger;
IMqttChannelAdapter _adapter;
+1 -1
View File
@@ -11,7 +11,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<LangVersion>7.3</LangVersion>
<AssemblyName>MQTTnet</AssemblyName>
+1 -1
View File
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cauthentication/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cconnecting/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdiagnostics/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdisconnecting/@EntryIndexedValue">True</s:Boolean>
@@ -17,5 +18,4 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Cmanagement/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Coptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Cstatus/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Cstopping/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=server_005Cstorage/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -4,7 +4,6 @@
using System;
using System.Text;
using MQTTnet.Internal;
namespace MQTTnet
{
@@ -17,16 +16,6 @@ namespace MQTTnet
throw new ArgumentNullException(nameof(applicationMessage));
}
if(applicationMessage.PayloadSegment == EmptyBuffer.ArraySegment)
{
return null;
}
if (applicationMessage.PayloadSegment.Array == null)
{
return null;
}
var payloadSegment = applicationMessage.PayloadSegment;
return Encoding.UTF8.GetString(payloadSegment.Array, payloadSegment.Offset, payloadSegment.Count);
}
-11
View File
@@ -11,7 +11,6 @@ using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.LowLevelClient;
using MQTTnet.Server;
using MQTTnet.Server.Disconnecting;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet
@@ -176,16 +175,6 @@ namespace MQTTnet
return new MqttServer(options, serverAdapters, DefaultLogger);
}
public MqttServerClientDisconnectOptionsBuilder CreateMqttServerClientDisconnectOptionsBuilder()
{
return new MqttServerClientDisconnectOptionsBuilder();
}
public MqttServerStopOptionsBuilder CreateMqttServerStopOptionsBuilder()
{
return new MqttServerStopOptionsBuilder();
}
public MqttServerOptionsBuilder CreateServerOptionsBuilder()
{
return new MqttServerOptionsBuilder();
@@ -9,7 +9,7 @@ namespace MQTTnet.PacketDispatcher
{
public interface IMqttPacketAwaitable : IDisposable
{
MqttPacketAwaitableFilter Filter { get; }
int PacketIdentifier { get; }
void Complete(MqttPacket packet);
@@ -16,18 +16,13 @@ namespace MQTTnet.PacketDispatcher
readonly AsyncTaskCompletionSource<MqttPacket> _promise = new AsyncTaskCompletionSource<MqttPacket>();
readonly MqttPacketDispatcher _owningPacketDispatcher;
public MqttPacketAwaitable(ushort packetIdentifier, MqttPacketDispatcher owningPacketDispatcher)
public MqttPacketAwaitable(int packetPacketIdentifier, MqttPacketDispatcher owningPacketDispatcher)
{
Filter = new MqttPacketAwaitableFilter
{
Type = typeof(TPacket),
Identifier = packetIdentifier
};
PacketIdentifier = packetPacketIdentifier;
_owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher));
}
public MqttPacketAwaitableFilter Filter { get; }
public int PacketIdentifier { get; }
public async Task<TPacket> WaitOneAsync(CancellationToken cancellationToken)
{
@@ -1,15 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.PacketDispatcher
{
public sealed class MqttPacketAwaitableFilter
{
public Type Type { get; set; }
public ushort Identifier { get; set; }
}
}
@@ -10,11 +10,15 @@ namespace MQTTnet.PacketDispatcher
{
public sealed class MqttPacketDispatcher : IDisposable
{
public const int PingRespPacketFakeIdentifier = 2147483647;
public const int AuthPacketFakeIdentifier = 2147483646;
public const int SuccessAuthPacketFakeIdentifier = 2147483647;
readonly List<IMqttPacketAwaitable> _waiters = new List<IMqttPacketAwaitable>();
bool _isDisposed;
public MqttPacketAwaitable<TResponsePacket> AddAwaitable<TResponsePacket>(ushort packetIdentifier) where TResponsePacket : MqttPacket
public MqttPacketAwaitable<TResponsePacket> AddAwaitable<TResponsePacket>(int packetIdentifier) where TResponsePacket : MqttPacket
{
var awaitable = new MqttPacketAwaitable<TResponsePacket>(packetIdentifier, this);
@@ -99,15 +103,21 @@ namespace MQTTnet.PacketDispatcher
throw new ArgumentNullException(nameof(packet));
}
ushort identifier = 0;
var identifier = 0;
if (packet is MqttPacketWithIdentifier packetWithIdentifier)
{
identifier = packetWithIdentifier.PacketIdentifier;
}
var packetType = packet.GetType();
else if (packet is MqttPingRespPacket)
{
identifier = PingRespPacketFakeIdentifier;
}
else
{
throw new InvalidOperationException($"Cannot dispatch {packet.GetRfcName()} packet.");
}
var waiters = new List<IMqttPacketAwaitable>();
lock (_waiters)
{
ThrowIfDisposed();
@@ -115,11 +125,8 @@ namespace MQTTnet.PacketDispatcher
for (var i = _waiters.Count - 1; i >= 0; i--)
{
var entry = _waiters[i];
// Note: The PingRespPacket will also arrive here and has NO identifier but there
// is code which waits for it. So the code must be able to deal with filters which
// are referring to the type only (identifier is 0)!
if (entry.Filter.Type != packetType || entry.Filter.Identifier != identifier)
if (entry.PacketIdentifier != identifier)
{
continue;
}
+6 -3
View File
@@ -28,7 +28,7 @@ namespace MQTTnet.Packets
public MqttQualityOfServiceLevel MaximumQoS { get; set; }
/// <summary>
/// Added in MQTTv5.
/// The return code is only used in MQTTv 5.0.0 and higher.
/// </summary>
public MqttConnectReasonCode ReasonCode { get; set; }
@@ -39,13 +39,16 @@ namespace MQTTnet.Packets
public string ResponseInformation { get; set; }
public bool RetainAvailable { get; set; }
/// <summary>
/// The return code is only used in MQTTv 3.1.1 and lower.
/// </summary>
public MqttConnectReturnCode ReturnCode { get; set; }
public ushort ServerKeepAlive { get; set; }
public string ServerReference { get; set; }
public uint SessionExpiryInterval { get; set; }
public bool SharedSubscriptionAvailable { get; set; }
@@ -36,7 +36,7 @@ namespace MQTTnet.Packets
public override string ToString()
{
return $"Disconnect: [ReasonCode={ReasonCode}] [ReasonString={ReasonString}] [ServerReference={ServerReference}] [SessionExpiryInterval={SessionExpiryInterval}]";
return $"Disconnect: [ReasonCode={ReasonCode}]";
}
}
}
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
namespace MQTTnet.Packets
{
public sealed class MqttUserPropertiesBuilder
{
readonly List<MqttUserProperty> _properties = new List<MqttUserProperty>();
public List<MqttUserProperty> Build()
{
return _properties;
}
public MqttUserPropertiesBuilder With(string name, string value = "")
{
_properties.Add(new MqttUserProperty(name, value));
return this;
}
}
}
@@ -1,33 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Disconnecting
{
public sealed class MqttServerClientDisconnectOptions
{
public MqttDisconnectReasonCode ReasonCode { get; set; } = MqttDisconnectReasonCode.NormalDisconnection;
/// <summary>
/// The reason string is sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString { get; set; }
/// <summary>
/// The server reference is sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ServerReference { get; set; }
/// <summary>
/// These user properties are sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
}
}
@@ -1,55 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Disconnecting
{
public sealed class MqttServerClientDisconnectOptionsBuilder
{
readonly MqttServerClientDisconnectOptions _options = new MqttServerClientDisconnectOptions();
public MqttServerClientDisconnectOptions Build()
{
return _options;
}
public MqttServerClientDisconnectOptionsBuilder WithReasonCode(MqttDisconnectReasonCode value)
{
_options.ReasonCode = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithReasonString(string value)
{
_options.ReasonString = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithServerReference(string value)
{
_options.ServerReference = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> value)
{
_options.UserProperties = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
{
if (_options.UserProperties == null)
{
_options.UserProperties = new List<MqttUserProperty>();
}
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
}
}
@@ -1,27 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Server
{
public sealed class ApplicationMessageEnqueuedEventArgs : EventArgs
{
public ApplicationMessageEnqueuedEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, bool isDropped)
{
SenderClientId = senderClientId ?? throw new ArgumentNullException( nameof(senderClientId));
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
IsDropped = isDropped;
}
public string SenderClientId { get; }
public string ReceiverClientId { get; }
public bool IsDropped { get; }
public MqttApplicationMessage ApplicationMessage { get; }
}
}
@@ -4,33 +4,26 @@
using System;
using System.Collections;
using System.Collections.Generic;
using MQTTnet.Formatter;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public sealed class ClientConnectedEventArgs : EventArgs
{
readonly MqttConnectPacket _connectPacket;
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems)
public ClientConnectedEventArgs(string clientId, string userName, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems)
{
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
ProtocolVersion = protocolVersion;
Endpoint = endpoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
/// <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 => _connectPacket.ClientId;
public string ClientId { get; }
/// <summary>
/// Gets the endpoint of the connected client.
@@ -50,12 +43,6 @@ namespace MQTTnet.Server
/// <summary>
/// Gets the user name of the connected client.
/// </summary>
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the user properties sent by the client.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _connectPacket?.UserProperties;
public string UserName { get; }
}
}
@@ -2,11 +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.
using System;
namespace MQTTnet.Server
{
public enum EnqueueDataPacketResult
public sealed class ClientReAuthenticatingEventArgs : EventArgs
{
Enqueued,
Dropped
}
}

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