mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 17:23:04 +08:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df0b268a08 | ||
|
|
addf812d58 | ||
|
|
533b8e22c2 | ||
|
|
3aaf101b3b | ||
|
|
9d9076ba77 | ||
|
|
af1c151e8e | ||
|
|
202e299f65 | ||
|
|
129348c07b |
@@ -1,4 +1,4 @@
|
|||||||
* [Client] Fixed _PlatformNotSupportedException_ when using Blazor (#1755, thanks to @Nickztar).
|
* [Core] Add validation of maximum string lengths (#1718).
|
||||||
* [Server] Fixed _NullReferenceException_ in retained messages management (#1762, thanks to @logicaloud).
|
* [Client] Added overloads for setting packet payload and will payload (#1720).
|
||||||
* [Server] Exposed new option which allows disabling packet fragmentation (#1753).
|
* [Client] The proper connect result is now exposed in the _Disconnected_ event when authentication fails (#1139).
|
||||||
* [Server] Expired sessions will no longer be used when a client connects (#1756).
|
* [Server] Improved performance by changing internal locking strategy for subscriptions (#1716, thanks to @zeheng).
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using MQTTnet;
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
@@ -177,4 +176,4 @@ public static class Client_Subscribe_Samples
|
|||||||
response.DumpToConsole();
|
response.DumpToConsole();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,6 @@
|
|||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using MQTTnet.Packets;
|
|
||||||
using MQTTnet.Protocol;
|
|
||||||
|
|
||||||
namespace MQTTnet.Samples.Server;
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
@@ -34,10 +32,7 @@ public static class Server_Retained_Messages_Samples
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
|
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
|
||||||
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
|
|
||||||
|
|
||||||
eventArgs.LoadedRetainedMessages = retainedMessages;
|
|
||||||
Console.WriteLine("Retained messages loaded.");
|
Console.WriteLine("Retained messages loaded.");
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
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
|
// 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.
|
// of retained messages must be provided in the loaded event.
|
||||||
|
|
||||||
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
|
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
|
||||||
|
|
||||||
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
|
|
||||||
await File.WriteAllBytesAsync(storePath, buffer);
|
await File.WriteAllBytesAsync(storePath, buffer);
|
||||||
Console.WriteLine("Retained messages saved.");
|
Console.WriteLine("Retained messages saved.");
|
||||||
}
|
}
|
||||||
@@ -86,59 +79,4 @@ public static class Server_Retained_Messages_Samples
|
|||||||
Console.ReadLine();
|
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||||
|
|
||||||
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
|
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||||
|
|
||||||
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
|
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
|
||||||
|
|||||||
@@ -69,11 +69,6 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
throw new ArgumentNullException(nameof(methodName));
|
throw new ArgumentNullException(nameof(methodName));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_mqttClient.IsConnected)
|
|
||||||
{
|
|
||||||
throw new MqttClientDisconnectedException(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
|
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
|
||||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||||
|
|
||||||
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
|
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
|
||||||
|
|||||||
@@ -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,16 +3,13 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Extensions.Rpc;
|
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
|
||||||
@@ -21,38 +18,6 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class MqttClient_Connection_Tests : BaseTestClass
|
public sealed class MqttClient_Connection_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
[TestMethod]
|
|
||||||
public async Task Connect_Disconnect_Connect()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
|
||||||
var client = testEnvironment.CreateClient();
|
|
||||||
|
|
||||||
await client.ConnectAsync(clientOptions);
|
|
||||||
await client.DisconnectAsync();
|
|
||||||
await client.ConnectAsync(clientOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[ExpectedException(typeof(InvalidOperationException))]
|
|
||||||
public async Task Connect_Multiple_Times_Should_Fail()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
|
||||||
var client = testEnvironment.CreateClient();
|
|
||||||
|
|
||||||
await client.ConnectAsync(clientOptions);
|
|
||||||
await client.ConnectAsync(clientOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[ExpectedException(typeof(MqttCommunicationException))]
|
[ExpectedException(typeof(MqttCommunicationException))]
|
||||||
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
|
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
|
||||||
@@ -163,7 +128,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
await client.DisconnectAsync(disconnectOptions);
|
await client.DisconnectAsync(disconnectOptions);
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.IsNotNull(eventArgs);
|
Assert.IsNotNull(eventArgs);
|
||||||
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
|
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
|
||||||
}
|
}
|
||||||
@@ -191,7 +156,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
await client.DisconnectAsync(disconnectOptions);
|
await client.DisconnectAsync(disconnectOptions);
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.IsNotNull(eventArgs);
|
Assert.IsNotNull(eventArgs);
|
||||||
Assert.IsNotNull(eventArgs.UserProperties);
|
Assert.IsNotNull(eventArgs.UserProperties);
|
||||||
Assert.AreEqual(1, eventArgs.UserProperties.Count);
|
Assert.AreEqual(1, eventArgs.UserProperties.Count);
|
||||||
@@ -199,47 +164,5 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
|
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Return_Non_Success()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
|
||||||
{
|
|
||||||
var server = await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
server.ValidatingConnectionAsync += args =>
|
|
||||||
{
|
|
||||||
args.ResponseUserProperties = new List<MqttUserProperty>
|
|
||||||
{
|
|
||||||
new MqttUserProperty("Property", "Value")
|
|
||||||
};
|
|
||||||
|
|
||||||
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
|
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
var client = testEnvironment.CreateClient();
|
|
||||||
|
|
||||||
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().WithoutThrowOnNonSuccessfulConnectResponse().Build());
|
|
||||||
|
|
||||||
Assert.IsNotNull(response);
|
|
||||||
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
|
|
||||||
Assert.AreEqual(response.UserProperties[0].Name, "Property");
|
|
||||||
Assert.AreEqual(response.UserProperties[0].Value, "Value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
|
||||||
public async Task Rpc_Client_Not_Connected()
|
|
||||||
{
|
|
||||||
var factory = new MqttFactory();
|
|
||||||
using (var client = factory.CreateMqttClient())
|
|
||||||
{
|
|
||||||
var rpcClient = factory.CreateMqttRpcClient(client);
|
|
||||||
await rpcClient.ExecuteAsync("aaa", new byte[] { 1, 2, 3 }, MqttQualityOfServiceLevel.AtLeastOnce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,59 +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.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Protocol;
|
|
||||||
using MQTTnet.Server;
|
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Clients.MqttClient
|
|
||||||
{
|
|
||||||
[TestClass]
|
|
||||||
public sealed class MqttClient_Publish_Tests : BaseTestClass
|
|
||||||
{
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Acknowledge_After_Disconnect_Should_Fail()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
var server = await testEnvironment.StartServer();
|
|
||||||
|
|
||||||
var client = await testEnvironment.ConnectClient();
|
|
||||||
await client.SubscribeAsync("TEST", MqttQualityOfServiceLevel.AtLeastOnce);
|
|
||||||
|
|
||||||
var flag = false;
|
|
||||||
|
|
||||||
client.ApplicationMessageReceivedAsync += async args =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
args.AutoAcknowledge = false;
|
|
||||||
|
|
||||||
await client.DisconnectAsync();
|
|
||||||
await args.AcknowledgeAsync();
|
|
||||||
|
|
||||||
flag = true;
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await server.InjectApplicationMessageAsync(
|
|
||||||
new MqttApplicationMessage
|
|
||||||
{
|
|
||||||
Topic = "TEST"
|
|
||||||
});
|
|
||||||
|
|
||||||
SpinWait.SpinUntil(() => flag, TimeSpan.FromSeconds(10));
|
|
||||||
|
|
||||||
Assert.IsTrue(flag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -69,8 +69,38 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
|||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Connect_Disconnect_Connect()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
||||||
|
var client = testEnvironment.CreateClient();
|
||||||
|
|
||||||
|
await client.ConnectAsync(clientOptions);
|
||||||
|
await client.DisconnectAsync();
|
||||||
|
await client.ConnectAsync(clientOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(InvalidOperationException))]
|
||||||
|
public async Task Connect_Multiple_Times_Should_Fail()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
||||||
|
var client = testEnvironment.CreateClient();
|
||||||
|
|
||||||
|
await client.ConnectAsync(clientOptions);
|
||||||
|
await client.ConnectAsync(clientOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Disconnect_Event_Contains_Exception()
|
public async Task Disconnect_Event_Contains_Exception()
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ using MQTTnet.Client;
|
|||||||
namespace MQTTnet.Tests.Clients
|
namespace MQTTnet.Tests.Clients
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class MqttClientOptionsBuilder_Tests
|
public class MqttClientOptionsBuilder_Tests
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void WithConnectionUri_Credential_Test()
|
public void WithConnectionUri_Credential_Test()
|
||||||
|
|||||||
@@ -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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,7 +78,7 @@ namespace MQTTnet.Tests.Formatter
|
|||||||
Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1
|
Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Serialize_Full_MqttConnAckPacket_V310()
|
public void Serialize_Full_MqttConnAckPacket_V310()
|
||||||
{
|
{
|
||||||
var connAckPacket = new MqttConnAckPacket
|
var connAckPacket = new MqttConnAckPacket
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -25,9 +24,10 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
public sealed class TestEnvironment : IDisposable
|
public sealed class TestEnvironment : IDisposable
|
||||||
{
|
{
|
||||||
readonly List<string> _clientErrors = new List<string>();
|
readonly List<string> _clientErrors = new List<string>();
|
||||||
readonly ConcurrentBag<IMqttClient> _clients = new ConcurrentBag<IMqttClient>();
|
readonly List<IMqttClient> _clients = new List<IMqttClient>();
|
||||||
|
|
||||||
readonly List<Exception> _exceptions = new List<Exception>();
|
readonly List<Exception> _exceptions = new List<Exception>();
|
||||||
readonly ConcurrentBag<ILowLevelMqttClient> _lowLevelClients = new ConcurrentBag<ILowLevelMqttClient>();
|
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
|
||||||
readonly MqttProtocolVersion _protocolVersion;
|
readonly MqttProtocolVersion _protocolVersion;
|
||||||
readonly List<string> _serverErrors = new List<string>();
|
readonly List<string> _serverErrors = new List<string>();
|
||||||
|
|
||||||
@@ -65,12 +65,9 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
if (e.LogMessage.Level == MqttNetLogLevel.Error)
|
if (e.LogMessage.Level == MqttNetLogLevel.Error)
|
||||||
{
|
{
|
||||||
if (!IgnoreClientLogErrors)
|
lock (_clientErrors)
|
||||||
{
|
{
|
||||||
lock (_clientErrors)
|
_clientErrors.Add(e.LogMessage.ToString());
|
||||||
{
|
|
||||||
_clientErrors.Add(e.LogMessage.ToString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -215,27 +212,30 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
public IMqttClient CreateClient()
|
public IMqttClient CreateClient()
|
||||||
{
|
{
|
||||||
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance;
|
lock (_clients)
|
||||||
|
|
||||||
var client = Factory.CreateMqttClient(logger);
|
|
||||||
client.ConnectingAsync += e =>
|
|
||||||
{
|
{
|
||||||
if (TestContext != null)
|
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : new MqttNetNullLogger();
|
||||||
{
|
|
||||||
var clientOptions = e.ClientOptions;
|
|
||||||
var existingClientId = clientOptions.ClientId;
|
|
||||||
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
|
|
||||||
{
|
|
||||||
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
var client = Factory.CreateMqttClient(logger);
|
||||||
};
|
_clients.Add(client);
|
||||||
|
|
||||||
_clients.Add(client);
|
client.ConnectingAsync += e =>
|
||||||
|
{
|
||||||
return client;
|
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()
|
public MqttClientOptions CreateDefaultClientOptions()
|
||||||
@@ -250,10 +250,13 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
public ILowLevelMqttClient CreateLowLevelClient()
|
public ILowLevelMqttClient CreateLowLevelClient()
|
||||||
{
|
{
|
||||||
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
|
lock (_clients)
|
||||||
_lowLevelClients.Add(client);
|
{
|
||||||
|
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
|
||||||
|
_lowLevelClients.Add(client);
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttServer CreateServer(MqttServerOptions options)
|
public MqttServer CreateServer(MqttServerOptions options)
|
||||||
@@ -274,8 +277,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
// Null is used when the client id is assigned from the server!
|
// Null is used when the client id is assigned from the server!
|
||||||
if (!string.IsNullOrEmpty(e.ClientId) && !e.ClientId.StartsWith(TestContext.TestName))
|
if (!string.IsNullOrEmpty(e.ClientId) && !e.ClientId.StartsWith(TestContext.TestName))
|
||||||
{
|
{
|
||||||
TrackException(new InvalidOperationException($"Invalid client ID used ({e.ClientId}). It must start with UnitTest name."));
|
Assert.Fail($"Client ID does not start with test name ({TestContext.TestName}).");
|
||||||
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,23 +291,12 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
{
|
{
|
||||||
foreach (var mqttClient in _clients)
|
foreach (var mqttClient in _clients)
|
||||||
{
|
{
|
||||||
try
|
mqttClient?.Dispose();
|
||||||
{
|
|
||||||
//mqttClient.DisconnectAsync().GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// This can happen when the test already disconnected the client.
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
mqttClient?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var lowLevelMqttClient in _lowLevelClients)
|
foreach (var lowLevelMqttClient in _lowLevelClients)
|
||||||
{
|
{
|
||||||
lowLevelMqttClient.Dispose();
|
lowLevelMqttClient?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -380,16 +371,13 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
|
throw new Exception(message);
|
||||||
Console.WriteLine(message);
|
|
||||||
throw new Exception(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -598,7 +598,8 @@ namespace MQTTnet.Tests.Server
|
|||||||
// c
|
// c
|
||||||
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
||||||
|
|
||||||
await LongTestDelay();
|
await Task.Delay(500);
|
||||||
|
|
||||||
var flow = string.Join(string.Empty, events);
|
var flow = string.Join(string.Empty, events);
|
||||||
Assert.AreEqual("c", flow);
|
Assert.AreEqual("c", flow);
|
||||||
|
|
||||||
@@ -606,8 +607,10 @@ namespace MQTTnet.Tests.Server
|
|||||||
// Connect client with same client ID. Should disconnect existing client.
|
// Connect client with same client ID. Should disconnect existing client.
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
||||||
|
|
||||||
await LongTestDelay();
|
await Task.Delay(500);
|
||||||
|
|
||||||
flow = string.Join(string.Empty, events);
|
flow = string.Join(string.Empty, events);
|
||||||
|
|
||||||
Assert.AreEqual("cdc", flow);
|
Assert.AreEqual("cdc", flow);
|
||||||
|
|
||||||
c2.ApplicationMessageReceivedAsync += e =>
|
c2.ApplicationMessageReceivedAsync += e =>
|
||||||
@@ -625,7 +628,8 @@ namespace MQTTnet.Tests.Server
|
|||||||
// r
|
// r
|
||||||
await c2.PublishStringAsync("topic");
|
await c2.PublishStringAsync("topic");
|
||||||
|
|
||||||
await LongTestDelay();
|
await Task.Delay(500);
|
||||||
|
|
||||||
flow = string.Join(string.Empty, events);
|
flow = string.Join(string.Empty, events);
|
||||||
Assert.AreEqual("cdcr", flow);
|
Assert.AreEqual("cdcr", flow);
|
||||||
|
|
||||||
@@ -635,17 +639,16 @@ namespace MQTTnet.Tests.Server
|
|||||||
await c1.TryDisconnectAsync();
|
await c1.TryDisconnectAsync();
|
||||||
Assert.AreEqual(false, c1.IsConnected);
|
Assert.AreEqual(false, c1.IsConnected);
|
||||||
|
|
||||||
await LongTestDelay();
|
await Task.Delay(500);
|
||||||
|
|
||||||
// d
|
// d
|
||||||
Assert.AreEqual(true, c2.IsConnected);
|
Assert.AreEqual(true, c2.IsConnected);
|
||||||
await c2.DisconnectAsync();
|
await c2.DisconnectAsync();
|
||||||
|
|
||||||
await LongTestDelay();
|
await Task.Delay(500);
|
||||||
|
|
||||||
await server.StopAsync();
|
await server.StopAsync();
|
||||||
|
|
||||||
await LongTestDelay();
|
|
||||||
flow = string.Join(string.Empty, events);
|
flow = string.Join(string.Empty, events);
|
||||||
Assert.AreEqual("cdcrd", flow);
|
Assert.AreEqual("cdcrd", flow);
|
||||||
}
|
}
|
||||||
@@ -849,11 +852,11 @@ namespace MQTTnet.Tests.Server
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Stopping the server should disconnect the connection with the client
|
await Task.Delay(100);
|
||||||
// which, it turn, will fire the disconnected event at the client.
|
|
||||||
await server.StopAsync();
|
await server.StopAsync();
|
||||||
|
|
||||||
await LongTestDelay();
|
await Task.Delay(100);
|
||||||
|
|
||||||
Assert.AreEqual(1, disconnectCalled);
|
Assert.AreEqual(1, disconnectCalled);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 eventContainer = new MqttServerEventContainer();
|
||||||
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
|
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
|
||||||
|
|
||||||
var session = new MqttSession(new MqttConnectPacket
|
var session = new MqttSession("", false, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
|
||||||
{
|
|
||||||
ClientId = ""
|
|
||||||
}, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
|
|
||||||
|
|
||||||
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
|
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]
|
[TestMethod]
|
||||||
public async Task Fire_Deleted_Event()
|
public async Task Fire_Deleted_Event()
|
||||||
{
|
{
|
||||||
@@ -143,7 +121,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow(MqttProtocolVersion.V310)]
|
[DataRow(MqttProtocolVersion.V310)]
|
||||||
[DataRow(MqttProtocolVersion.V311)]
|
[DataRow(MqttProtocolVersion.V311)]
|
||||||
@@ -156,12 +134,16 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
await testEnvironment.StartServer();
|
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;
|
var hasReceive = false;
|
||||||
|
|
||||||
void OnReceive()
|
void OnReceive()
|
||||||
{
|
{
|
||||||
hasReceive = true;
|
if (!hasReceive)
|
||||||
|
{
|
||||||
|
hasReceive = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to connect 50 clients at the same time.
|
// 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();
|
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(1, connectedClients.Count);
|
Assert.AreEqual(1, connectedClients.Count);
|
||||||
|
|
||||||
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
|
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
|
||||||
@@ -182,7 +164,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual(true, hasReceive);
|
Assert.AreEqual(true, hasReceive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataTestMethod]
|
[DataTestMethod]
|
||||||
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
||||||
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
||||||
@@ -373,8 +355,8 @@ namespace MQTTnet.Tests.Server
|
|||||||
onReceive();
|
onReceive();
|
||||||
return CompletedTask.Instance;
|
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);
|
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
var eventContainer = new MqttServerEventContainer();
|
var eventContainer = new MqttServerEventContainer();
|
||||||
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
|
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
|
||||||
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, 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;
|
List<string> topicsToSubscribe;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class MqttClientPartialAuthenticationResponse
|
||||||
|
{
|
||||||
|
public byte[] AuthenticationData { get; set; }
|
||||||
|
|
||||||
|
public MqttAuthenticateReasonCode ReasonCode { get; set; }
|
||||||
|
|
||||||
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Client.Internal;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class MqttExtendedAuthenticationExchangeEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
static readonly IReadOnlyCollection<MqttUserProperty> EmptyUserProperties = new List<MqttUserProperty>();
|
||||||
|
|
||||||
|
readonly IMqttAuthenticationTransportStrategy _transportStrategy;
|
||||||
|
|
||||||
|
public MqttExtendedAuthenticationExchangeEventArgs(MqttAuthPacket authPacket, IMqttAuthenticationTransportStrategy transportStrategy)
|
||||||
|
{
|
||||||
|
if (authPacket == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(authPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
_transportStrategy = transportStrategy ?? throw new ArgumentNullException(nameof(transportStrategy));
|
||||||
|
|
||||||
|
ReasonCode = authPacket.ReasonCode;
|
||||||
|
ReasonString = authPacket.ReasonString;
|
||||||
|
AuthenticationMethod = authPacket.AuthenticationMethod;
|
||||||
|
AuthenticationData = authPacket.AuthenticationData;
|
||||||
|
UserProperties = authPacket.UserProperties ?? EmptyUserProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the authentication data.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public byte[] AuthenticationData { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the authentication method.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public string AuthenticationMethod { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the reason code.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public MqttAuthenticateReasonCode ReasonCode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the reason string.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public string ReasonString { get; }
|
||||||
|
|
||||||
|
// /// <summary>
|
||||||
|
// /// Gets the response which will be sent to the server.
|
||||||
|
// /// </summary>
|
||||||
|
// public MqttExtendedAuthenticationExchangeResponse Response { get; } = new MqttExtendedAuthenticationExchangeResponse();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user properties.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
|
||||||
|
|
||||||
|
public async Task<MqttClientPartialAuthenticationResponse> ReceiveAuthenticationDataAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var receivePacket = await _transportStrategy.ReceivePacketAsync(AuthenticationMethod, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (receivePacket == null)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("The client closed the connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MqttClientPartialAuthenticationResponse
|
||||||
|
{
|
||||||
|
ReasonCode = receivePacket.ReasonCode,
|
||||||
|
AuthenticationData = receivePacket.AuthenticationData,
|
||||||
|
UserProperties = receivePacket.UserProperties
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SendAuthenticationDataAsync(
|
||||||
|
MqttAuthenticateReasonCode reasonCode,
|
||||||
|
byte[] authenticationData = null,
|
||||||
|
string reasonString = null,
|
||||||
|
List<MqttUserProperty> userProperties = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// The authentication method will never change so we must use the already known one [MQTT-4.12.0-5].
|
||||||
|
//var authPacket = MqttPacketFactories.Auth.Create(AuthenticationMethod, reasonCode);
|
||||||
|
|
||||||
|
var authPacket = new MqttAuthPacket
|
||||||
|
{
|
||||||
|
AuthenticationMethod = AuthenticationMethod,
|
||||||
|
AuthenticationData = authenticationData,
|
||||||
|
UserProperties = userProperties,
|
||||||
|
ReasonCode = reasonCode,
|
||||||
|
ReasonString = reasonString
|
||||||
|
};
|
||||||
|
|
||||||
|
return _transportStrategy.SendPacketAsync(authPacket, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class MqttReAuthenticationOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the authentication data.
|
||||||
|
/// Authentication data is binary information used to transmit multiple iterations of cryptographic secrets of protocol
|
||||||
|
/// steps.
|
||||||
|
/// The content of the authentication data is highly dependent on the specific implementation of the authentication
|
||||||
|
/// method.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public byte[] AuthenticationData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user properties.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,110 +11,112 @@ namespace MQTTnet.Client
|
|||||||
public sealed class MqttClientConnectResult
|
public sealed class MqttClientConnectResult
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the result code.
|
/// Gets the client identifier which was chosen by the server.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
|
||||||
public MqttClientConnectResultCode ResultCode { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether a session was already available or not.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsSessionPresent { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
|
||||||
public bool WildcardSubscriptionAvailable { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether the server supports retained messages.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
|
||||||
public bool RetainAvailable { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the client identifier which was chosen by the server.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AssignedClientIdentifier { get; internal set; }
|
public string AssignedClientIdentifier { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the authentication method.
|
/// Gets the authentication data.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public byte[] AuthenticationData { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the authentication method.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AuthenticationMethod { get; internal set; }
|
public string AuthenticationMethod { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the authentication data.
|
/// Gets a value indicating whether a session was already available or not.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte[] AuthenticationData { get; internal set; }
|
public bool IsSessionPresent { get; internal set; }
|
||||||
|
|
||||||
public uint? MaximumPacketSize { get; internal set; }
|
public uint? MaximumPacketSize { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the reason string.
|
/// Gets the maximum QoS which is supported by the server.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
|
||||||
public string ReasonString { get; internal set; }
|
|
||||||
|
|
||||||
public ushort? ReceiveMaximum { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the maximum QoS which is supported by the server.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttQualityOfServiceLevel MaximumQoS { get; internal set; }
|
public MqttQualityOfServiceLevel MaximumQoS { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the response information.
|
/// Gets the reason string.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public string ReasonString { get; internal set; }
|
||||||
|
|
||||||
|
public ushort? ReceiveMaximum { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the response information.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ResponseInformation { get; internal set; }
|
public string ResponseInformation { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the maximum value for a topic alias. 0 means not supported.
|
/// Gets the result code.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort TopicAliasMaximum { get; internal set; }
|
public MqttClientConnectResultCode ResultCode { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an alternate server which should be used instead of the current one.
|
/// Gets whether the server supports retained messages.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ServerReference { get; internal set; }
|
public bool RetainAvailable { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MQTTv5 only.
|
/// MQTTv5 only.
|
||||||
/// Gets the keep alive interval which was chosen by the server instead of the
|
/// Gets the keep alive interval which was chosen by the server instead of the
|
||||||
/// keep alive interval from the client CONNECT packet.
|
/// keep alive interval from the client CONNECT packet.
|
||||||
/// A value of 0 indicates that the feature is not used.
|
/// A value of 0 indicates that the feature is not used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort ServerKeepAlive { get; internal set; }
|
public ushort ServerKeepAlive { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an alternate server which should be used instead of the current one.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public string ServerReference { get; internal set; }
|
||||||
|
|
||||||
public uint? SessionExpiryInterval { get; internal set; }
|
public uint? SessionExpiryInterval { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the subscription identifiers are available or not.
|
/// Gets a value indicating whether the shared subscriptions are available or not.
|
||||||
/// MQTTv5 only.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
|
||||||
public bool SubscriptionIdentifiersAvailable { get; internal set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the shared subscriptions are available or not.
|
|
||||||
/// MQTTv5 only.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SharedSubscriptionAvailable { get; internal set; }
|
public bool SharedSubscriptionAvailable { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the user properties.
|
/// Gets a value indicating whether the subscription identifiers are available or not.
|
||||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
/// </summary>
|
||||||
/// The feature is very similar to the HTTP header concept.
|
public bool SubscriptionIdentifiersAvailable { get; internal set; }
|
||||||
/// MQTTv5 only.
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the maximum value for a topic alias. 0 means not supported.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public ushort TopicAliasMaximum { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the user properties.
|
||||||
|
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||||
|
/// packet.
|
||||||
|
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
|
||||||
|
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||||
|
/// The feature is very similar to the HTTP header concept.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<MqttUserProperty> UserProperties { get; internal set; }
|
public List<MqttUserProperty> UserProperties { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server.
|
||||||
|
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
public bool WildcardSubscriptionAvailable { get; internal set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
|
||||||
{
|
|
||||||
public interface IMqttExtendedAuthenticationExchangeHandler
|
|
||||||
{
|
|
||||||
Task HandleRequestAsync(MqttExtendedAuthenticationExchangeContext context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-62
@@ -1,62 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
using MQTTnet.Protocol;
|
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
|
||||||
{
|
|
||||||
public class MqttExtendedAuthenticationExchangeContext
|
|
||||||
{
|
|
||||||
public MqttExtendedAuthenticationExchangeContext(MqttAuthPacket authPacket, MqttClient client)
|
|
||||||
{
|
|
||||||
if (authPacket == null) throw new ArgumentNullException(nameof(authPacket));
|
|
||||||
|
|
||||||
ReasonCode = authPacket.ReasonCode;
|
|
||||||
ReasonString = authPacket.ReasonString;
|
|
||||||
AuthenticationMethod = authPacket.AuthenticationMethod;
|
|
||||||
AuthenticationData = authPacket.AuthenticationData;
|
|
||||||
UserProperties = authPacket.UserProperties;
|
|
||||||
|
|
||||||
Client = client ?? throw new ArgumentNullException(nameof(client));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the reason code.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public MqttAuthenticateReasonCode ReasonCode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the reason string.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public string ReasonString { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the authentication method.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public string AuthenticationMethod { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the authentication data.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public byte[] AuthenticationData { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the user properties.
|
|
||||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
|
|
||||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
|
||||||
/// The feature is very similar to the HTTP header concept.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public List<MqttUserProperty> UserProperties { get; }
|
|
||||||
|
|
||||||
public MqttClient Client { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-42
@@ -1,42 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
using MQTTnet.Protocol;
|
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
|
||||||
{
|
|
||||||
public class MqttExtendedAuthenticationExchangeData
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the reason code.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public MqttAuthenticateReasonCode ReasonCode { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the reason string.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public string ReasonString { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the authentication data.
|
|
||||||
/// Authentication data is binary information used to transmit multiple iterations of cryptographic secrets of protocol steps.
|
|
||||||
/// The content of the authentication data is highly dependent on the specific implementation of the authentication method.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public byte[] AuthenticationData { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the user properties.
|
|
||||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
|
|
||||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
|
||||||
/// The feature is very similar to the HTTP header concept.
|
|
||||||
/// Hint: MQTT 5 feature only.
|
|
||||||
/// </summary>
|
|
||||||
public List<MqttUserProperty> UserProperties { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,13 +8,15 @@ namespace MQTTnet.Client
|
|||||||
public interface IMqttClient : IDisposable
|
public interface IMqttClient : IDisposable
|
||||||
{
|
{
|
||||||
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
|
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
|
||||||
|
|
||||||
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
|
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
|
||||||
|
|
||||||
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
|
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
|
||||||
|
|
||||||
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
||||||
|
|
||||||
|
event Func<MqttExtendedAuthenticationExchangeEventArgs, Task> ExtendedAuthenticationExchangeAsync;
|
||||||
|
|
||||||
event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
|
event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
|
||||||
|
|
||||||
bool IsConnected { get; }
|
bool IsConnected { get; }
|
||||||
@@ -29,7 +31,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
|
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
|
Task ReAuthenticateAsync(MqttReAuthenticationOptions options, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
|
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client.Internal
|
||||||
|
{
|
||||||
|
public sealed class MqttInitialAuthenticationStrategy : IMqttAuthenticationTransportStrategy
|
||||||
|
{
|
||||||
|
readonly IMqttChannelAdapter _channelAdapter;
|
||||||
|
|
||||||
|
public MqttInitialAuthenticationStrategy(IMqttChannelAdapter channelAdapter)
|
||||||
|
{
|
||||||
|
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task SendPacketAsync(MqttAuthPacket authPacket, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (authPacket == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(authPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _channelAdapter.SendPacketAsync(authPacket, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MqttAuthPacket> ReceivePacketAsync(string authenticationMethod, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (authenticationMethod == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(authenticationMethod));
|
||||||
|
}
|
||||||
|
|
||||||
|
var receivePacket = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
if (receivePacket is MqttAuthPacket authPacket)
|
||||||
|
{
|
||||||
|
if (!string.Equals(authPacket.AuthenticationMethod, authenticationMethod, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("The authentication method is not allowed to change while authenticating.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return authPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivePacket == null)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("The server closed the connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MqttProtocolViolationException("Expected an AUTH packet from the client.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IMqttAuthenticationTransportStrategy
|
||||||
|
{
|
||||||
|
Task SendPacketAsync(MqttAuthPacket authPacket, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<MqttAuthPacket> ReceivePacketAsync(string authenticationMethod, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class MqttClientAuthenticationHandler
|
||||||
|
{
|
||||||
|
readonly MqttClient _client;
|
||||||
|
|
||||||
|
readonly AsyncEvent<MqttExtendedAuthenticationExchangeEventArgs> _extendedAuthenticationExchangeEvent = new AsyncEvent<MqttExtendedAuthenticationExchangeEventArgs>();
|
||||||
|
readonly MqttNetSourceLogger _logger;
|
||||||
|
|
||||||
|
public MqttClientAuthenticationHandler(MqttClient client, IMqttNetLogger logger)
|
||||||
|
{
|
||||||
|
if (logger == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
_client = client ?? throw new ArgumentNullException(nameof(client));
|
||||||
|
_logger = logger.WithSource(nameof(MqttClientAuthenticationHandler));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddHandler(Func<MqttExtendedAuthenticationExchangeEventArgs, Task> handler)
|
||||||
|
{
|
||||||
|
_extendedAuthenticationExchangeEvent.AddHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MqttClientConnectResult> Authenticate(IMqttChannelAdapter channelAdapter, MqttClientOptions options, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (channelAdapter == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(channelAdapter));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
// From the RFC:
|
||||||
|
// The CONNACK packet is the packet sent by the Server in response to a CONNECT packet received from a Client.
|
||||||
|
// The Server MUST send a CONNACK with a 0x00 (Success) Reason Code before sending any Packet other than AUTH [MQTT-3.2.0-1].
|
||||||
|
// The Server MUST NOT send more than one CONNACK in a Network Connection [MQTT-3.2.0-2].
|
||||||
|
|
||||||
|
MqttClientConnectResult result;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Send the CONNECT packet..
|
||||||
|
var connectPacket = MqttPacketFactories.Connect.Create(options);
|
||||||
|
await _client.Send(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Wait for the CONNACK packet...
|
||||||
|
MqttConnAckPacket connAckPacket = null;
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var receivedPacket = await channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (receivedPacket is MqttAuthPacket initialAuthPacket)
|
||||||
|
{
|
||||||
|
// MQTT v3.1.1 cannot send an AUTH packet.
|
||||||
|
// If the Server requires additional information to complete the authentication, it can send an AUTH packet to the Client.
|
||||||
|
// This packet MUST contain a Reason Code of 0x18 (Continue authentication)
|
||||||
|
if (initialAuthPacket.ReasonCode != MqttAuthenticateReasonCode.ContinueAuthentication)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("Wrong reason code received [MQTT-4.12.0-2].");
|
||||||
|
}
|
||||||
|
|
||||||
|
//var response = await HandleExtendedAuthentication(authPacket, channelAdapter).ConfigureAwait(false);
|
||||||
|
await HandleExtendedAuthentication(initialAuthPacket, new MqttInitialAuthenticationStrategy(channelAdapter)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else if (receivedPacket is MqttConnAckPacket connAckPacketBuffer)
|
||||||
|
{
|
||||||
|
connAckPacket = connAckPacketBuffer;
|
||||||
|
|
||||||
|
// The CONNACK packet is the last packet when authenticating so there is no further AUTH packet allowed!
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException($"Received {receivedPacket.GetRfcName()} while authenticating.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
// If the initial CONNECT packet included an Authentication Method property then all AUTH packets,
|
||||||
|
// and any successful CONNACK packet MUST include an Authentication Method Property with the same
|
||||||
|
// value as in the CONNECT packet [MQTT-4.12.0-5].
|
||||||
|
if (!string.IsNullOrEmpty(connectPacket.AuthenticationMethod))
|
||||||
|
{
|
||||||
|
if (!string.Equals(connectPacket.AuthenticationMethod, connAckPacket?.AuthenticationMethod))
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("The CONNACK packet does not have the same authentication method as the CONNECT packet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is no feature. It is basically a backward compatibility option and should be removed in the future.
|
||||||
|
// The client should not throw any exception if the transport layer connection was successful and the server
|
||||||
|
// did send a proper ACK packet.
|
||||||
|
if (options.ThrowOnNonSuccessfulResponseFromServer)
|
||||||
|
{
|
||||||
|
_logger.Warning(
|
||||||
|
"Client will now throw an _MqttConnectingFailedException_. This is obsolete and will be removed in the future. Consider setting _ThrowOnNonSuccessfulResponseFromServer=False_ in client options.");
|
||||||
|
|
||||||
|
if (result.ResultCode != MqttClientConnectResultCode.Success)
|
||||||
|
{
|
||||||
|
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Verbose("Authenticated MQTT connection with server established.");
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task HandleExtendedAuthentication(MqttAuthPacket initialAuthPacket, IMqttAuthenticationTransportStrategy transportStrategy)
|
||||||
|
{
|
||||||
|
ValidateEventHandler();
|
||||||
|
|
||||||
|
var eventArgs = new MqttExtendedAuthenticationExchangeEventArgs(initialAuthPacket, transportStrategy);
|
||||||
|
return _extendedAuthenticationExchangeEvent.InvokeAsync(eventArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ValidateEventHandler()
|
||||||
|
{
|
||||||
|
if (!_extendedAuthenticationExchangeEvent.HasHandlers)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cannot handle extended authentication without attached event handler.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveHandler(Func<MqttExtendedAuthenticationExchangeEventArgs, Task> handler)
|
||||||
|
{
|
||||||
|
_extendedAuthenticationExchangeEvent.RemoveHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ReAuthenticate(MqttReAuthenticationOptions options, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ValidateEventHandler();
|
||||||
|
|
||||||
|
var authPacket = MqttPacketFactories.Auth.CreateReAuthenticationPacket(_client.Options, options);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Sending the AUTH packet will trigger the re authentication and the event handler will
|
||||||
|
// handle the details.
|
||||||
|
await _client.Send(authPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+119
-210
@@ -21,17 +21,19 @@ namespace MQTTnet.Client
|
|||||||
public sealed class MqttClient : Disposable, IMqttClient
|
public sealed class MqttClient : Disposable, IMqttClient
|
||||||
{
|
{
|
||||||
readonly IMqttClientAdapterFactory _adapterFactory;
|
readonly IMqttClientAdapterFactory _adapterFactory;
|
||||||
|
readonly MqttClientAuthenticationHandler _authenticationHandler;
|
||||||
readonly object _disconnectLock = new object();
|
readonly object _disconnectLock = new object();
|
||||||
readonly MqttClientEvents _events = new MqttClientEvents();
|
readonly MqttClientEvents _events = new MqttClientEvents();
|
||||||
|
readonly MqttClientKeepAliveHandler _keepAliveHandler;
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
|
|
||||||
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
readonly IMqttNetLogger _rootLogger;
|
readonly IMqttNetLogger _rootLogger;
|
||||||
|
|
||||||
IMqttChannelAdapter _adapter;
|
IMqttChannelAdapter _channelAdapter;
|
||||||
|
|
||||||
bool _cleanDisconnectInitiated;
|
internal bool _cleanDisconnectInitiated;
|
||||||
|
|
||||||
|
CancellationTokenSource _clientAlive;
|
||||||
volatile int _connectionStatus;
|
volatile int _connectionStatus;
|
||||||
|
|
||||||
// The value for this field can be set from two different enums.
|
// The value for this field can be set from two different enums.
|
||||||
@@ -39,11 +41,6 @@ namespace MQTTnet.Client
|
|||||||
int _disconnectReason;
|
int _disconnectReason;
|
||||||
string _disconnectReasonString;
|
string _disconnectReasonString;
|
||||||
List<MqttUserProperty> _disconnectUserProperties;
|
List<MqttUserProperty> _disconnectUserProperties;
|
||||||
|
|
||||||
Task _keepAlivePacketsSenderTask;
|
|
||||||
DateTime _lastPacketSentTimestamp;
|
|
||||||
|
|
||||||
CancellationTokenSource _mqttClientAlive;
|
|
||||||
MqttPacketDispatcher _packetDispatcher;
|
MqttPacketDispatcher _packetDispatcher;
|
||||||
Task _packetReceiverTask;
|
Task _packetReceiverTask;
|
||||||
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
|
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
|
||||||
@@ -54,6 +51,9 @@ namespace MQTTnet.Client
|
|||||||
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
|
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
|
||||||
_rootLogger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_rootLogger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_logger = logger.WithSource(nameof(MqttClient));
|
_logger = logger.WithSource(nameof(MqttClient));
|
||||||
|
|
||||||
|
_authenticationHandler = new MqttClientAuthenticationHandler(this, logger);
|
||||||
|
_keepAliveHandler = new MqttClientKeepAliveHandler(this, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
||||||
@@ -80,6 +80,12 @@ namespace MQTTnet.Client
|
|||||||
remove => _events.DisconnectedEvent.RemoveHandler(value);
|
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
|
public event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync
|
||||||
{
|
{
|
||||||
add => _events.InspectPacketEvent.AddHandler(value);
|
add => _events.InspectPacketEvent.AddHandler(value);
|
||||||
@@ -116,16 +122,14 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
_packetIdentifierProvider.Reset();
|
_packetIdentifierProvider.Reset();
|
||||||
_packetDispatcher = new MqttPacketDispatcher();
|
_packetDispatcher = new MqttPacketDispatcher();
|
||||||
|
_clientAlive = new CancellationTokenSource();
|
||||||
_mqttClientAlive = new CancellationTokenSource();
|
|
||||||
var mqttClientAliveToken = _mqttClientAlive.Token;
|
|
||||||
|
|
||||||
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_events.InspectPacketEvent, _rootLogger), _rootLogger);
|
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)
|
if (cancellationToken.CanBeCanceled)
|
||||||
{
|
{
|
||||||
connectResult = await ConnectInternal(adapter, cancellationToken).ConfigureAwait(false);
|
connectResult = await ConnectInternal(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -133,30 +137,10 @@ namespace MQTTnet.Client
|
|||||||
// CancellationToken.None or similar.
|
// CancellationToken.None or similar.
|
||||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
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);
|
|
||||||
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);
|
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
|
||||||
|
|
||||||
_logger.Info("Connected");
|
_logger.Info("Connected");
|
||||||
@@ -210,7 +194,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (Options.ValidateFeatures)
|
if (Options.ValidateFeatures)
|
||||||
{
|
{
|
||||||
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sending the DISCONNECT may fail due to connection issues. The resulting exception
|
// Sending the DISCONNECT may fail due to connection issues. The resulting exception
|
||||||
@@ -261,7 +245,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (Options.ValidateFeatures)
|
if (Options.ValidateFeatures)
|
||||||
{
|
{
|
||||||
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
|
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
|
||||||
@@ -287,26 +271,25 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default)
|
public Task ReAuthenticateAsync(MqttReAuthenticationOptions options, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (data == null)
|
if (options == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(data));
|
throw new ArgumentNullException(nameof(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
ThrowIfNotConnected();
|
ThrowIfNotConnected();
|
||||||
|
|
||||||
var authPacket = new MqttAuthPacket
|
if (cancellationToken.CanBeCanceled)
|
||||||
{
|
{
|
||||||
// This must always be equal to the value from the CONNECT packet. So we use it here to ensure that.
|
return _authenticationHandler.ReAuthenticate(options, cancellationToken);
|
||||||
AuthenticationMethod = Options.AuthenticationMethod,
|
}
|
||||||
AuthenticationData = data.AuthenticationData,
|
|
||||||
ReasonString = data.ReasonString,
|
|
||||||
UserProperties = data.UserProperties
|
|
||||||
};
|
|
||||||
|
|
||||||
return 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)
|
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default)
|
||||||
@@ -323,7 +306,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (Options.ValidateFeatures)
|
if (Options.ValidateFeatures)
|
||||||
{
|
{
|
||||||
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
@@ -365,7 +348,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (Options.ValidateFeatures)
|
if (Options.ValidateFeatures)
|
||||||
{
|
{
|
||||||
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
|
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
|
||||||
@@ -387,6 +370,27 @@ namespace MQTTnet.Client
|
|||||||
return MqttClientResultFactory.UnsubscribeResult.Create(unsubscribePacket, unsubAckPacket);
|
return MqttClientResultFactory.UnsubscribeResult.Create(unsubscribePacket, unsubAckPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult)
|
||||||
|
{
|
||||||
|
var clientWasConnected = IsConnected;
|
||||||
|
|
||||||
|
if (!DisconnectIsPendingOrFinished())
|
||||||
|
{
|
||||||
|
return DisconnectCore(sender, exception, connectResult, clientWasConnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Task Send(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
_keepAliveHandler.TrackSentPacket();
|
||||||
|
|
||||||
|
return _channelAdapter.SendPacketAsync(packet, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
@@ -427,74 +431,22 @@ namespace MQTTnet.Client
|
|||||||
return CompletedTask.Instance;
|
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, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
|
||||||
}
|
|
||||||
else if (receivedPacket is MqttAuthPacket)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("Extended authentication handler is not yet supported");
|
|
||||||
}
|
|
||||||
else if (receivedPacket == null)
|
|
||||||
{
|
|
||||||
throw new MqttCommunicationException("Connection closed.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket}).");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Cleanup()
|
void Cleanup()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_mqttClientAlive?.Cancel(false);
|
_clientAlive?.Cancel(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_mqttClientAlive?.Dispose();
|
_clientAlive?.Dispose();
|
||||||
_mqttClientAlive = null;
|
_clientAlive = null;
|
||||||
|
|
||||||
_publishPacketReceiverQueue?.Dispose();
|
_publishPacketReceiverQueue?.Dispose();
|
||||||
_publishPacketReceiverQueue = null;
|
_publishPacketReceiverQueue = null;
|
||||||
|
|
||||||
_adapter?.Dispose();
|
_channelAdapter?.Dispose();
|
||||||
_adapter = null;
|
_channelAdapter = null;
|
||||||
|
|
||||||
_packetDispatcher?.Dispose();
|
_packetDispatcher?.Dispose();
|
||||||
_packetDispatcher = null;
|
_packetDispatcher = null;
|
||||||
@@ -506,24 +458,29 @@ namespace MQTTnet.Client
|
|||||||
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand);
|
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);
|
_logger.Verbose("Trying to connect with server '{0}'", Options.ChannelOptions);
|
||||||
await _adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
|
|
||||||
|
await _channelAdapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.Verbose("Connection with server established");
|
_logger.Verbose("Connection with server established");
|
||||||
|
|
||||||
_publishPacketReceiverQueue?.Dispose();
|
var connectResult = await _authenticationHandler.Authenticate(_channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||||
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
|
|
||||||
|
|
||||||
var connectResult = await Authenticate(channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
|
||||||
if (connectResult.ResultCode == MqttClientConnectResultCode.Success)
|
if (connectResult.ResultCode == MqttClientConnectResultCode.Success)
|
||||||
{
|
{
|
||||||
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
|
_publishPacketReceiverQueue?.Dispose();
|
||||||
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(backgroundCancellationToken), backgroundCancellationToken);
|
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
|
||||||
|
|
||||||
|
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPacketsLoop(clientAliveToken), clientAliveToken);
|
||||||
|
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(clientAliveToken), clientAliveToken);
|
||||||
|
|
||||||
|
_keepAliveHandler.Enable(connectResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
return connectResult;
|
return connectResult;
|
||||||
@@ -536,13 +493,13 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_adapter != null)
|
if (_channelAdapter != null)
|
||||||
{
|
{
|
||||||
_logger.Verbose("Disconnecting [Timeout={0}]", Options.Timeout);
|
_logger.Verbose("Disconnecting [Timeout={0}]", Options.Timeout);
|
||||||
|
|
||||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||||
{
|
{
|
||||||
await _adapter.DisconnectAsync(timeout.Token).ConfigureAwait(false);
|
await _channelAdapter.DisconnectAsync(timeout.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,13 +512,14 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_packetDispatcher?.Dispose(new MqttClientDisconnectedException(exception));
|
_packetDispatcher.Dispose(new MqttClientDisconnectedException(exception));
|
||||||
|
|
||||||
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
|
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
|
||||||
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
|
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
|
||||||
var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger);
|
|
||||||
|
|
||||||
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
|
_keepAliveHandler.Disable();
|
||||||
|
|
||||||
|
await Task.WhenAll(receiverTask, publishPacketReceiverTask).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception innerException)
|
catch (Exception innerException)
|
||||||
{
|
{
|
||||||
@@ -660,17 +618,6 @@ namespace MQTTnet.Client
|
|||||||
return CompletedTask.Instance;
|
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)
|
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
|
||||||
{
|
{
|
||||||
_disconnectReason = (int)disconnectPacket.ReasonCode;
|
_disconnectReason = (int)disconnectPacket.ReasonCode;
|
||||||
@@ -683,7 +630,7 @@ namespace MQTTnet.Client
|
|||||||
return DisconnectInternal(_packetReceiverTask, null, null);
|
return DisconnectInternal(_packetReceiverTask, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken)
|
async Task ProcessReceivedPublishPacketsLoop(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -763,7 +710,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
async Task<MqttPacket> Receive(CancellationToken cancellationToken)
|
async Task<MqttPacket> Receive(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var packetTask = _adapter.ReceivePacketAsync(cancellationToken);
|
var packetTask = _channelAdapter.ReceivePacketAsync(cancellationToken);
|
||||||
|
|
||||||
MqttPacket packet;
|
MqttPacket packet;
|
||||||
if (packetTask.IsCompleted)
|
if (packetTask.IsCompleted)
|
||||||
@@ -837,15 +784,36 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
ushort packetIdentifier = 0;
|
// Some packets are a direct response to another packet but have no packet identifier for correlation!
|
||||||
|
int packetIdentifier;
|
||||||
if (requestPacket is MqttPacketWithIdentifier packetWithIdentifier)
|
if (requestPacket is MqttPacketWithIdentifier packetWithIdentifier)
|
||||||
{
|
{
|
||||||
packetIdentifier = packetWithIdentifier.PacketIdentifier;
|
packetIdentifier = packetWithIdentifier.PacketIdentifier;
|
||||||
}
|
}
|
||||||
|
else if (requestPacket is MqttPingReqPacket)
|
||||||
|
{
|
||||||
|
packetIdentifier = MqttPacketDispatcher.PingRespPacketFakeIdentifier;
|
||||||
|
}
|
||||||
|
else if (requestPacket is MqttAuthPacket)
|
||||||
|
{
|
||||||
|
packetIdentifier = MqttPacketDispatcher.AuthPacketFakeIdentifier;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException($"Expecting a response for packet {requestPacket.GetRfcName()} is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
using (var packetAwaitable = _packetDispatcher.AddAwaitable<TResponsePacket>(packetIdentifier))
|
using (var packetAwaitable = _packetDispatcher.AddAwaitable<TResponsePacket>(packetIdentifier))
|
||||||
{
|
{
|
||||||
@@ -875,16 +843,6 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Task Send(MqttPacket packet, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
_lastPacketSentTimestamp = DateTime.UtcNow;
|
|
||||||
|
|
||||||
//ThrowIfNotConnected();
|
|
||||||
return _adapter.SendPacketAsync(packet, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThrowIfConnected(string message)
|
void ThrowIfConnected(string message)
|
||||||
{
|
{
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
@@ -930,7 +888,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_mqttClientAlive?.Cancel(false);
|
_clientAlive?.Cancel(false);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
@@ -959,23 +917,31 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
await ProcessReceivedDisconnectPacket(disconnectPacket).ConfigureAwait(false);
|
await ProcessReceivedDisconnectPacket(disconnectPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttAuthPacket authPacket)
|
// else if (packet is MqttAuthPacket authPacket)
|
||||||
{
|
// {
|
||||||
await ProcessReceivedAuthPacket(authPacket).ConfigureAwait(false);
|
// await _authenticationHandler.HandleExtendedAuthentication(authPacket, new MqttInitialAuthenticationStrategy(_channelAdapter)).ConfigureAwait(false);
|
||||||
}
|
// }
|
||||||
else if (packet is MqttPingRespPacket)
|
else if (packet is MqttPingRespPacket)
|
||||||
{
|
{
|
||||||
_packetDispatcher.TryDispatch(packet);
|
_packetDispatcher.TryDispatch(packet);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPingReqPacket)
|
else if (packet is MqttPingReqPacket)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("The PINGREQ Packet is sent from a Client to the Server only.");
|
throw new MqttProtocolViolationException("The PINGREQ packet can only be sent from client to server.");
|
||||||
|
}
|
||||||
|
else if (packet is MqttConnAckPacket)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("The CONNACK packet can only be send while connecting.");
|
||||||
|
}
|
||||||
|
else if (packet is MqttConnectPacket)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("The CONNECT packet can only be sent from client to server.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!_packetDispatcher.TryDispatch(packet))
|
if (!_packetDispatcher.TryDispatch(packet))
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
|
throw new MqttProtocolViolationException($"Received unexpected {packet} packet.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1003,62 +969,5 @@ namespace MQTTnet.Client
|
|||||||
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
|
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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,8 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public static class MqttClientExtensions
|
public static class MqttClientExtensions
|
||||||
{
|
{
|
||||||
|
static readonly MqttReAuthenticationOptions EmptyReAuthenticationOptions = new MqttReAuthenticationOptions();
|
||||||
|
|
||||||
public static Task DisconnectAsync(
|
public static Task DisconnectAsync(
|
||||||
this IMqttClient client,
|
this IMqttClient client,
|
||||||
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
|
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
|
||||||
@@ -77,6 +79,11 @@ namespace MQTTnet.Client
|
|||||||
return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken);
|
return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task ReAuthenticateAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return client.ReAuthenticateAsync(EmptyReAuthenticationOptions, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (client.Options == null)
|
if (client.Options == null)
|
||||||
@@ -88,16 +95,6 @@ namespace MQTTnet.Client
|
|||||||
return client.ConnectAsync(client.Options, cancellationToken);
|
return client.ConnectAsync(client.Options, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task SendExtendedAuthenticationExchangeDataAsync(this IMqttClient client, MqttExtendedAuthenticationExchangeData data)
|
|
||||||
{
|
|
||||||
if (client == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(client));
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.SendExtendedAuthenticationExchangeDataAsync(data, CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Task<MqttClientSubscribeResult> SubscribeAsync(this IMqttClient mqttClient, MqttTopicFilter topicFilter, CancellationToken cancellationToken = default)
|
public static Task<MqttClientSubscribeResult> SubscribeAsync(this IMqttClient mqttClient, MqttTopicFilter topicFilter, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (mqttClient == null)
|
if (mqttClient == null)
|
||||||
|
|||||||
@@ -53,9 +53,7 @@ namespace MQTTnet.Client
|
|||||||
public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
|
public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
public IMqttClientCredentialsProvider Credentials { get; set; }
|
public IMqttClientCredentialsProvider Credentials { get; set; }
|
||||||
|
|
||||||
public IMqttExtendedAuthenticationExchangeHandler ExtendedAuthenticationExchangeHandler { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the keep alive period.
|
/// Gets or sets the keep alive period.
|
||||||
/// The connection is normally left open by the client so that is can send and receive data at any time.
|
/// The connection is normally left open by the client so that is can send and receive data at any time.
|
||||||
@@ -103,7 +101,7 @@ namespace MQTTnet.Client
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether an exception should be thrown when the server has sent a non success ACK packet.
|
/// Gets or sets whether an exception should be thrown when the server has sent a non success ACK packet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ThrowOnNonSuccessfulConnectResponse { get; set; } = true;
|
public bool ThrowOnNonSuccessfulResponseFromServer { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the timeout which will be applied at socket level and internal operations.
|
/// Gets or sets the timeout which will be applied at socket level and internal operations.
|
||||||
|
|||||||
@@ -78,18 +78,8 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
return _options;
|
return _options;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data = null)
|
||||||
/// The client will not throw an exception when the MQTT server responses with a non success ACK packet.
|
|
||||||
/// This will become the default behavior in future versions of the library.
|
|
||||||
/// </summary>
|
|
||||||
public MqttClientOptionsBuilder WithoutThrowOnNonSuccessfulConnectResponse()
|
|
||||||
{
|
|
||||||
_options.ThrowOnNonSuccessfulConnectResponse = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
|
|
||||||
{
|
{
|
||||||
_options.AuthenticationMethod = method;
|
_options.AuthenticationMethod = method;
|
||||||
_options.AuthenticationData = data;
|
_options.AuthenticationData = data;
|
||||||
@@ -187,12 +177,6 @@ namespace MQTTnet.Client
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithExtendedAuthenticationExchangeHandler(IMqttExtendedAuthenticationExchangeHandler handler)
|
|
||||||
{
|
|
||||||
_options.ExtendedAuthenticationExchangeHandler = handler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value)
|
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value)
|
||||||
{
|
{
|
||||||
_options.KeepAlivePeriod = value;
|
_options.KeepAlivePeriod = value;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Security;
|
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
@@ -31,21 +30,11 @@ namespace MQTTnet.Client
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if NETCOREAPP3_1_OR_GREATER
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
|
public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; }
|
||||||
|
|
||||||
public CipherSuitesPolicy CipherSuitesPolicy { get; set; }
|
public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; }
|
||||||
|
|
||||||
public EncryptionPolicy EncryptionPolicy { get; set; } = EncryptionPolicy.RequireEncryption;
|
|
||||||
|
|
||||||
public bool AllowRenegotiation { get; set; } = true;
|
|
||||||
#endif
|
#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 NET48 || NETCOREAPP3_1_OR_GREATER
|
#if NET48 || NETCOREAPP3_1_OR_GREATER
|
||||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
|
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -2,19 +2,15 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.WebSockets;
|
|
||||||
|
|
||||||
namespace MQTTnet.Client
|
namespace MQTTnet.Client
|
||||||
{
|
{
|
||||||
public sealed class MqttClientWebSocketOptions : IMqttClientChannelOptions
|
public sealed class MqttClientWebSocketOptions : IMqttClientChannelOptions
|
||||||
{
|
{
|
||||||
public CookieContainer CookieContainer { get; set; }
|
public CookieContainer CookieContainer { get; set; }
|
||||||
|
|
||||||
public ICredentials Credentials { get; set; }
|
|
||||||
|
|
||||||
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
|
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
|
||||||
|
|
||||||
public IDictionary<string, string> RequestHeaders { 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 ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" };
|
||||||
|
|
||||||
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
|
||||||
|
|
||||||
public string Uri { get; set; }
|
public string Uri { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Uri;
|
return Uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !NETSTANDARD1_3
|
|
||||||
#if !WINDOWS_UWP
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the keep alive interval for the Web Socket connection.
|
|
||||||
/// This is not related to the keep alive interval for the MQTT protocol.
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan KeepAliveInterval { get; set; } = WebSocket.DefaultKeepAliveInterval;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets whether the default (system) credentials should be used when connecting via Web Socket connection.
|
|
||||||
/// This is not related to the credentials which are used for the MQTT protocol.
|
|
||||||
/// </summary>
|
|
||||||
public bool UseDefaultCredentials { get; set; }
|
|
||||||
#else
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the keep alive interval for the Web Socket connection.
|
|
||||||
/// This is not related to the keep alive interval for the MQTT protocol.
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(30);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,8 +16,6 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
public bool BypassOnLocal { get; set; }
|
public bool BypassOnLocal { get; set; }
|
||||||
|
|
||||||
public bool UseDefaultCredentials { get; set; }
|
|
||||||
|
|
||||||
public string[] BypassList { get; set; }
|
public string[] BypassList { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
internal MqttPublishPacket PublishPacket { get; set; }
|
internal MqttPublishPacket PublishPacket { get; set; }
|
||||||
|
|
||||||
public Task AcknowledgeAsync(CancellationToken cancellationToken = default)
|
public Task AcknowledgeAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_acknowledgeHandler == null)
|
if (_acknowledgeHandler == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ namespace MQTTnet.Diagnostics
|
|||||||
return "net461";
|
return "net461";
|
||||||
#elif NET472
|
#elif NET472
|
||||||
return "net472";
|
return "net472";
|
||||||
#elif NET48
|
|
||||||
return "net48";
|
|
||||||
#elif NETSTANDARD1_3
|
#elif NETSTANDARD1_3
|
||||||
return "netstandard1.3";
|
return "netstandard1.3";
|
||||||
#elif NETSTANDARD2_0
|
#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.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Formatter
|
namespace MQTTnet.Formatter
|
||||||
{
|
{
|
||||||
public static class MqttConnectReasonCodeConverter
|
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)
|
public static MqttConnectReturnCode ToConnectReturnCode(MqttConnectReasonCode reasonCode)
|
||||||
{
|
{
|
||||||
switch (reasonCode)
|
switch (reasonCode)
|
||||||
@@ -17,13 +59,13 @@ namespace MQTTnet.Formatter
|
|||||||
return MqttConnectReturnCode.ConnectionAccepted;
|
return MqttConnectReturnCode.ConnectionAccepted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case MqttConnectReasonCode.BadAuthenticationMethod:
|
||||||
case MqttConnectReasonCode.Banned:
|
case MqttConnectReasonCode.Banned:
|
||||||
case MqttConnectReasonCode.NotAuthorized:
|
case MqttConnectReasonCode.NotAuthorized:
|
||||||
{
|
{
|
||||||
return MqttConnectReturnCode.ConnectionRefusedNotAuthorized;
|
return MqttConnectReturnCode.ConnectionRefusedNotAuthorized;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MqttConnectReasonCode.BadAuthenticationMethod:
|
|
||||||
case MqttConnectReasonCode.BadUserNameOrPassword:
|
case MqttConnectReasonCode.BadUserNameOrPassword:
|
||||||
{
|
{
|
||||||
return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
|
return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
|
||||||
@@ -33,12 +75,12 @@ namespace MQTTnet.Formatter
|
|||||||
{
|
{
|
||||||
return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected;
|
return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MqttConnectReasonCode.UnsupportedProtocolVersion:
|
case MqttConnectReasonCode.UnsupportedProtocolVersion:
|
||||||
{
|
{
|
||||||
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
|
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MqttConnectReasonCode.UseAnotherServer:
|
case MqttConnectReasonCode.UseAnotherServer:
|
||||||
case MqttConnectReasonCode.ServerUnavailable:
|
case MqttConnectReasonCode.ServerUnavailable:
|
||||||
case MqttConnectReasonCode.ServerBusy:
|
case MqttConnectReasonCode.ServerBusy:
|
||||||
@@ -48,7 +90,10 @@ namespace MQTTnet.Formatter
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
{
|
||||||
|
// This is the most best matching value.
|
||||||
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
|
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ namespace MQTTnet.Formatter
|
|||||||
{
|
{
|
||||||
public static class MqttPacketFactories
|
public static class MqttPacketFactories
|
||||||
{
|
{
|
||||||
|
public static MqttAuthPacketFactory Auth { get; } = new MqttAuthPacketFactory();
|
||||||
|
|
||||||
public static MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
|
public static MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
|
||||||
|
|
||||||
public static MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
|
public static MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
|
||||||
|
|||||||
@@ -130,9 +130,6 @@ namespace MQTTnet.Formatter.V5
|
|||||||
MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce
|
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);
|
var propertiesReader = new MqttV5PropertiesReader(_bufferReader);
|
||||||
while (propertiesReader.MoveNext())
|
while (propertiesReader.MoveNext())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -100,12 +100,6 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
if (_tcpOptions.TlsOptions?.UseTls == true)
|
if (_tcpOptions.TlsOptions?.UseTls == true)
|
||||||
{
|
{
|
||||||
var targetHost = _tcpOptions.TlsOptions.TargetHost;
|
|
||||||
if (string.IsNullOrEmpty(targetHost))
|
|
||||||
{
|
|
||||||
targetHost = _tcpOptions.Server;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
|
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -117,16 +111,14 @@ namespace MQTTnet.Implementations
|
|||||||
EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol,
|
EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol,
|
||||||
CertificateRevocationCheckMode =
|
CertificateRevocationCheckMode =
|
||||||
_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode,
|
_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode,
|
||||||
TargetHost = targetHost,
|
TargetHost = _tcpOptions.Server,
|
||||||
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy,
|
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy
|
||||||
EncryptionPolicy = _tcpOptions.TlsOptions.EncryptionPolicy,
|
|
||||||
AllowRenegotiation = _tcpOptions.TlsOptions.AllowRenegotiation
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
|
await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
|
||||||
#else
|
#else
|
||||||
await sslStream.AuthenticateAsClientAsync(
|
await sslStream.AuthenticateAsClientAsync(
|
||||||
targetHost,
|
_tcpOptions.Server,
|
||||||
LoadCertificates(),
|
LoadCertificates(),
|
||||||
_tcpOptions.TlsOptions.SslProtocol,
|
_tcpOptions.TlsOptions.SslProtocol,
|
||||||
!_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors)
|
!_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors)
|
||||||
@@ -298,12 +290,12 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
X509CertificateCollection LoadCertificates()
|
X509CertificateCollection LoadCertificates()
|
||||||
{
|
{
|
||||||
|
var certificates = new X509CertificateCollection();
|
||||||
if (_tcpOptions.TlsOptions.Certificates == null)
|
if (_tcpOptions.TlsOptions.Certificates == null)
|
||||||
{
|
{
|
||||||
return null;
|
return certificates;
|
||||||
}
|
}
|
||||||
|
|
||||||
var certificates = new X509CertificateCollection();
|
|
||||||
foreach (var certificate in _tcpOptions.TlsOptions.Certificates)
|
foreach (var certificate in _tcpOptions.TlsOptions.Certificates)
|
||||||
{
|
{
|
||||||
certificates.Add(certificate);
|
certificates.Add(certificate);
|
||||||
|
|||||||
@@ -231,7 +231,6 @@ namespace MQTTnet.Implementations
|
|||||||
|
|
||||||
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger))
|
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger))
|
||||||
{
|
{
|
||||||
clientAdapter.AllowPacketFragmentation = _options.AllowPacketFragmentation;
|
|
||||||
await clientHandler(clientAdapter).ConfigureAwait(false);
|
await clientHandler(clientAdapter).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +255,6 @@ namespace MQTTnet.Implementations
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// ReSharper disable once MethodHasAsyncOverload
|
|
||||||
stream?.Dispose();
|
stream?.Dispose();
|
||||||
clientSocket?.Dispose();
|
clientSocket?.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,26 +155,14 @@ namespace MQTTnet.Implementations
|
|||||||
throw new NotSupportedException("Proxies are not supported when using 'netstandard 1.3'.");
|
throw new NotSupportedException("Proxies are not supported when using 'netstandard 1.3'.");
|
||||||
#else
|
#else
|
||||||
var proxyUri = new Uri(_options.ProxyOptions.Address);
|
var proxyUri = new Uri(_options.ProxyOptions.Address);
|
||||||
WebProxy webProxy;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password))
|
if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password))
|
||||||
{
|
{
|
||||||
var credentials = new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain);
|
var credentials = new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain);
|
||||||
webProxy = new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList, credentials);
|
return 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 webProxy;
|
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,26 +207,6 @@ namespace MQTTnet.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#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;
|
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
|
||||||
if (certificateValidationHandler != null)
|
if (certificateValidationHandler != null)
|
||||||
{
|
{
|
||||||
@@ -252,8 +220,6 @@ namespace MQTTnet.Implementations
|
|||||||
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net452'.");
|
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net452'.");
|
||||||
#elif NET461
|
#elif NET461
|
||||||
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net461'.");
|
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net461'.");
|
||||||
#elif NET48
|
|
||||||
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net48'.");
|
|
||||||
#else
|
#else
|
||||||
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||||
<LangVersion>7.3</LangVersion>
|
<LangVersion>7.3</LangVersion>
|
||||||
<AssemblyName>MQTTnet</AssemblyName>
|
<AssemblyName>MQTTnet</AssemblyName>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cauthentication/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cconnecting/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cconnecting/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdiagnostics/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdiagnostics/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdisconnecting/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdisconnecting/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MQTTnet.Internal;
|
|
||||||
|
|
||||||
namespace MQTTnet
|
namespace MQTTnet
|
||||||
{
|
{
|
||||||
@@ -17,16 +16,6 @@ namespace MQTTnet
|
|||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(applicationMessage.PayloadSegment == EmptyBuffer.ArraySegment)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationMessage.PayloadSegment.Array == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var payloadSegment = applicationMessage.PayloadSegment;
|
var payloadSegment = applicationMessage.PayloadSegment;
|
||||||
return Encoding.UTF8.GetString(payloadSegment.Array, payloadSegment.Offset, payloadSegment.Count);
|
return Encoding.UTF8.GetString(payloadSegment.Array, payloadSegment.Offset, payloadSegment.Count);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace MQTTnet.PacketDispatcher
|
|||||||
{
|
{
|
||||||
public interface IMqttPacketAwaitable : IDisposable
|
public interface IMqttPacketAwaitable : IDisposable
|
||||||
{
|
{
|
||||||
MqttPacketAwaitableFilter Filter { get; }
|
int PacketIdentifier { get; }
|
||||||
|
|
||||||
void Complete(MqttPacket packet);
|
void Complete(MqttPacket packet);
|
||||||
|
|
||||||
|
|||||||
@@ -16,18 +16,13 @@ namespace MQTTnet.PacketDispatcher
|
|||||||
readonly AsyncTaskCompletionSource<MqttPacket> _promise = new AsyncTaskCompletionSource<MqttPacket>();
|
readonly AsyncTaskCompletionSource<MqttPacket> _promise = new AsyncTaskCompletionSource<MqttPacket>();
|
||||||
readonly MqttPacketDispatcher _owningPacketDispatcher;
|
readonly MqttPacketDispatcher _owningPacketDispatcher;
|
||||||
|
|
||||||
public MqttPacketAwaitable(ushort packetIdentifier, MqttPacketDispatcher owningPacketDispatcher)
|
public MqttPacketAwaitable(int packetPacketIdentifier, MqttPacketDispatcher owningPacketDispatcher)
|
||||||
{
|
{
|
||||||
Filter = new MqttPacketAwaitableFilter
|
PacketIdentifier = packetPacketIdentifier;
|
||||||
{
|
|
||||||
Type = typeof(TPacket),
|
|
||||||
Identifier = packetIdentifier
|
|
||||||
};
|
|
||||||
|
|
||||||
_owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher));
|
_owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttPacketAwaitableFilter Filter { get; }
|
public int PacketIdentifier { get; }
|
||||||
|
|
||||||
public async Task<TPacket> WaitOneAsync(CancellationToken cancellationToken)
|
public async Task<TPacket> WaitOneAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,11 +10,15 @@ namespace MQTTnet.PacketDispatcher
|
|||||||
{
|
{
|
||||||
public sealed class MqttPacketDispatcher : IDisposable
|
public sealed class MqttPacketDispatcher : IDisposable
|
||||||
{
|
{
|
||||||
|
public const int PingRespPacketFakeIdentifier = 2147483647;
|
||||||
|
public const int AuthPacketFakeIdentifier = 2147483646;
|
||||||
|
public const int SuccessAuthPacketFakeIdentifier = 2147483647;
|
||||||
|
|
||||||
readonly List<IMqttPacketAwaitable> _waiters = new List<IMqttPacketAwaitable>();
|
readonly List<IMqttPacketAwaitable> _waiters = new List<IMqttPacketAwaitable>();
|
||||||
|
|
||||||
bool _isDisposed;
|
bool _isDisposed;
|
||||||
|
|
||||||
public MqttPacketAwaitable<TResponsePacket> AddAwaitable<TResponsePacket>(ushort packetIdentifier) where TResponsePacket : MqttPacket
|
public MqttPacketAwaitable<TResponsePacket> AddAwaitable<TResponsePacket>(int packetIdentifier) where TResponsePacket : MqttPacket
|
||||||
{
|
{
|
||||||
var awaitable = new MqttPacketAwaitable<TResponsePacket>(packetIdentifier, this);
|
var awaitable = new MqttPacketAwaitable<TResponsePacket>(packetIdentifier, this);
|
||||||
|
|
||||||
@@ -99,15 +103,21 @@ namespace MQTTnet.PacketDispatcher
|
|||||||
throw new ArgumentNullException(nameof(packet));
|
throw new ArgumentNullException(nameof(packet));
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort identifier = 0;
|
var identifier = 0;
|
||||||
if (packet is MqttPacketWithIdentifier packetWithIdentifier)
|
if (packet is MqttPacketWithIdentifier packetWithIdentifier)
|
||||||
{
|
{
|
||||||
identifier = packetWithIdentifier.PacketIdentifier;
|
identifier = packetWithIdentifier.PacketIdentifier;
|
||||||
}
|
}
|
||||||
|
else if (packet is MqttPingRespPacket)
|
||||||
var packetType = packet.GetType();
|
{
|
||||||
|
identifier = PingRespPacketFakeIdentifier;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Cannot dispatch {packet.GetRfcName()} packet.");
|
||||||
|
}
|
||||||
|
|
||||||
var waiters = new List<IMqttPacketAwaitable>();
|
var waiters = new List<IMqttPacketAwaitable>();
|
||||||
|
|
||||||
lock (_waiters)
|
lock (_waiters)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
@@ -115,11 +125,8 @@ namespace MQTTnet.PacketDispatcher
|
|||||||
for (var i = _waiters.Count - 1; i >= 0; i--)
|
for (var i = _waiters.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var entry = _waiters[i];
|
var entry = _waiters[i];
|
||||||
|
|
||||||
// Note: The PingRespPacket will also arrive here and has NO identifier but there
|
if (entry.PacketIdentifier != identifier)
|
||||||
// is code which waits for it. So the code must be able to deal with filters which
|
|
||||||
// are referring to the type only (identifier is 0)!
|
|
||||||
if (entry.Filter.Type != packetType || entry.Filter.Identifier != identifier)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace MQTTnet.Packets
|
|||||||
public MqttQualityOfServiceLevel MaximumQoS { get; set; }
|
public MqttQualityOfServiceLevel MaximumQoS { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Added in MQTTv5.
|
/// The return code is only used in MQTTv 5.0.0 and higher.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttConnectReasonCode ReasonCode { get; set; }
|
public MqttConnectReasonCode ReasonCode { get; set; }
|
||||||
|
|
||||||
@@ -39,13 +39,16 @@ namespace MQTTnet.Packets
|
|||||||
public string ResponseInformation { get; set; }
|
public string ResponseInformation { get; set; }
|
||||||
|
|
||||||
public bool RetainAvailable { get; set; }
|
public bool RetainAvailable { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The return code is only used in MQTTv 3.1.1 and lower.
|
||||||
|
/// </summary>
|
||||||
public MqttConnectReturnCode ReturnCode { get; set; }
|
public MqttConnectReturnCode ReturnCode { get; set; }
|
||||||
|
|
||||||
public ushort ServerKeepAlive { get; set; }
|
public ushort ServerKeepAlive { get; set; }
|
||||||
|
|
||||||
public string ServerReference { get; set; }
|
public string ServerReference { get; set; }
|
||||||
|
|
||||||
public uint SessionExpiryInterval { get; set; }
|
public uint SessionExpiryInterval { get; set; }
|
||||||
|
|
||||||
public bool SharedSubscriptionAvailable { get; set; }
|
public bool SharedSubscriptionAvailable { get; set; }
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace MQTTnet.Packets
|
|||||||
|
|
||||||
public override string ToString()
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,33 +4,26 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Packets;
|
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class ClientConnectedEventArgs : EventArgs
|
public sealed class ClientConnectedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
readonly MqttConnectPacket _connectPacket;
|
public ClientConnectedEventArgs(string clientId, string userName, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems)
|
||||||
|
|
||||||
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems)
|
|
||||||
{
|
{
|
||||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
|
UserName = userName;
|
||||||
ProtocolVersion = protocolVersion;
|
ProtocolVersion = protocolVersion;
|
||||||
Endpoint = endpoint;
|
Endpoint = endpoint;
|
||||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
|
|
||||||
|
|
||||||
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the client identifier of the connected client.
|
/// Gets the client identifier of the connected client.
|
||||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientId => _connectPacket.ClientId;
|
public string ClientId { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the endpoint of the connected client.
|
/// Gets the endpoint of the connected client.
|
||||||
@@ -50,12 +43,6 @@ namespace MQTTnet.Server
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the user name of the connected client.
|
/// Gets the user name of the connected client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string UserName => _connectPacket.Username;
|
public string UserName { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the user properties sent by the client.
|
|
||||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
|
||||||
/// </summary>
|
|
||||||
public List<MqttUserProperty> UserProperties => _connectPacket?.UserProperties;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-4
@@ -4,12 +4,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace MQTTnet.PacketDispatcher
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class MqttPacketAwaitableFilter
|
public sealed class ClientReAuthenticatingEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public Type Type { get; set; }
|
|
||||||
|
|
||||||
public ushort Identifier { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Server
|
||||||
|
{
|
||||||
|
public sealed class PartialAuthenticationResponse
|
||||||
|
{
|
||||||
|
public byte[] AuthenticationData { get; set; }
|
||||||
|
|
||||||
|
public MqttAuthenticateReasonCode ReasonCode { get; set; }
|
||||||
|
|
||||||
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,10 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
@@ -19,11 +22,12 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
readonly MqttConnectPacket _connectPacket;
|
readonly MqttConnectPacket _connectPacket;
|
||||||
|
|
||||||
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter, IDictionary sessionItems)
|
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter, IDictionary sessionItems, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||||
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
||||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||||
|
CancellationToken = cancellationToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -44,6 +48,11 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a cancellation token which is being canceled as soon as the transport connection is closed.
|
||||||
|
/// </summary>
|
||||||
|
public CancellationToken CancellationToken { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the channel adapter. This can be a _MqttConnectionContext_ (used in ASP.NET), a _MqttChannelAdapter_ (used for
|
/// Gets the channel adapter. This can be a _MqttConnectionContext_ (used in ASP.NET), a _MqttChannelAdapter_ (used for
|
||||||
/// TCP or WebSockets) or a custom implementation.
|
/// TCP or WebSockets) or a custom implementation.
|
||||||
@@ -189,5 +198,51 @@ namespace MQTTnet.Server
|
|||||||
/// A value of 0 indicates that the value is not used.
|
/// A value of 0 indicates that the value is not used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public uint WillDelayInterval => _connectPacket.WillDelayInterval;
|
public uint WillDelayInterval => _connectPacket.WillDelayInterval;
|
||||||
|
|
||||||
|
public async Task<PartialAuthenticationResponse> ReceiveAuthenticationDataAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var receivePacket = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
if (receivePacket is MqttAuthPacket authPacket)
|
||||||
|
{
|
||||||
|
if (!string.Equals(authPacket.AuthenticationMethod, AuthenticationMethod, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("The authentication method is not allowed to change while authenticating.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PartialAuthenticationResponse
|
||||||
|
{
|
||||||
|
ReasonCode = authPacket.ReasonCode,
|
||||||
|
AuthenticationData = authPacket.AuthenticationData,
|
||||||
|
UserProperties = authPacket.UserProperties
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivePacket == null)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("The client closed the connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MqttProtocolViolationException("Expected an AUTH packet from the client.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SendAuthenticationDataAsync(
|
||||||
|
MqttAuthenticateReasonCode reasonCode,
|
||||||
|
byte[] authenticationData = null,
|
||||||
|
string reasonString = null,
|
||||||
|
List<MqttUserProperty> userProperties = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
// The authentication method will never change so we must use the already known one [MQTT-4.12.0-5].
|
||||||
|
var authPacket = new MqttAuthPacket
|
||||||
|
{
|
||||||
|
AuthenticationMethod = AuthenticationMethod,
|
||||||
|
AuthenticationData = authenticationData,
|
||||||
|
UserProperties = userProperties,
|
||||||
|
ReasonCode = reasonCode,
|
||||||
|
ReasonString = reasonString
|
||||||
|
};
|
||||||
|
|
||||||
|
return ChannelAdapter.SendPacketAsync(authPacket, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,16 +95,15 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
var cancellationToken = _cancellationToken.Token;
|
var cancellationToken = _cancellationToken.Token;
|
||||||
IsRunning = true;
|
IsRunning = true;
|
||||||
|
|
||||||
_ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false);
|
_ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
|
await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
|
|
||||||
Session.DisconnectedTimestamp = DateTime.UtcNow;
|
|
||||||
|
|
||||||
_cancellationToken?.TryCancel();
|
_cancellationToken?.TryCancel();
|
||||||
_cancellationToken?.Dispose();
|
_cancellationToken?.Dispose();
|
||||||
@@ -178,6 +177,22 @@ namespace MQTTnet.Server
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async Task HandleIncomingAuthPacket(MqttAuthPacket authPacket)
|
||||||
|
{
|
||||||
|
if (authPacket.ReasonCode != MqttAuthenticateReasonCode.ReAuthenticate)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException("Clients are only allowed to send AUTH packets after connecting for re authentication.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to handle the re authentication in another task because we rely on the package retrieval.
|
||||||
|
_ = Task.Run(
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
var eventArgs = new ClientReAuthenticatingEventArgs();
|
||||||
|
await _eventContainer.ClientReAuthenticatingEvent.InvokeAsync(eventArgs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void HandleIncomingPingReqPacket()
|
void HandleIncomingPingReqPacket()
|
||||||
{
|
{
|
||||||
// See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
|
// See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
|
||||||
@@ -438,9 +453,13 @@ namespace MQTTnet.Server
|
|||||||
DisconnectPacket = disconnectPacket;
|
DisconnectPacket = disconnectPacket;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (currentPacket is MqttAuthPacket authPacket)
|
||||||
|
{
|
||||||
|
await HandleIncomingAuthPacket(authPacket);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("Packet not allowed");
|
throw new MqttProtocolViolationException("Packet not allowed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
// The _sessions dictionary contains all session, the _subscriberSessions hash set contains subscriber sessions only.
|
// The _sessions dictionary contains all session, the _subscriberSessions hash set contains subscriber sessions only.
|
||||||
// See the MqttSubscription object for a detailed explanation.
|
// See the MqttSubscription object for a detailed explanation.
|
||||||
readonly MqttSessionsStorage _sessionsStorage = new MqttSessionsStorage();
|
readonly Dictionary<string, MqttSession> _sessions = new Dictionary<string, MqttSession>(4096);
|
||||||
|
|
||||||
readonly ReaderWriterLockSlim _sessionsManagementLock = new ReaderWriterLockSlim();
|
readonly ReaderWriterLockSlim _sessionsManagementLock = new ReaderWriterLockSlim();
|
||||||
readonly HashSet<MqttSession> _subscriberSessions = new HashSet<MqttSession>();
|
readonly HashSet<MqttSession> _subscriberSessions = new HashSet<MqttSession>();
|
||||||
@@ -58,7 +58,7 @@ namespace MQTTnet.Server
|
|||||||
_eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer));
|
_eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CloseAllConnections()
|
public async Task CloseAllConnectionsAsync()
|
||||||
{
|
{
|
||||||
List<MqttClient> connections;
|
List<MqttClient> connections;
|
||||||
lock (_clients)
|
lock (_clients)
|
||||||
@@ -78,16 +78,21 @@ namespace MQTTnet.Server
|
|||||||
_logger.Verbose("Deleting session for client '{0}'.", clientId);
|
_logger.Verbose("Deleting session for client '{0}'.", clientId);
|
||||||
|
|
||||||
MqttClient connection;
|
MqttClient connection;
|
||||||
|
|
||||||
lock (_clients)
|
lock (_clients)
|
||||||
{
|
{
|
||||||
_clients.TryGetValue(clientId, out connection);
|
_clients.TryGetValue(clientId, out connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttSession session;
|
MqttSession session;
|
||||||
|
|
||||||
_sessionsManagementLock.EnterWriteLock();
|
_sessionsManagementLock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_sessionsStorage.TryRemoveSession(clientId, out session))
|
_sessions.TryGetValue(clientId, out session);
|
||||||
|
_sessions.Remove(clientId);
|
||||||
|
|
||||||
|
if (session != null)
|
||||||
{
|
{
|
||||||
_subscriberSessions.Remove(session);
|
_subscriberSessions.Remove(session);
|
||||||
}
|
}
|
||||||
@@ -106,7 +111,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error(exception, "Error while deleting session '{0}'", clientId);
|
_logger.Error(exception, $"Error while deleting session '{clientId}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -119,12 +124,12 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error(exception, "Error while executing session deleted event for session '{0}'", clientId);
|
_logger.Error(exception, $"Error while executing session deleted event for session '{clientId}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
session?.Dispose();
|
session?.Dispose();
|
||||||
|
|
||||||
_logger.Verbose("Session of client '{0}' deleted", clientId);
|
_logger.Verbose("Session for client '{0}' deleted.", clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage(
|
public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage(
|
||||||
@@ -264,14 +269,20 @@ namespace MQTTnet.Server
|
|||||||
_sessionsManagementLock.EnterWriteLock();
|
_sessionsManagementLock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_sessionsStorage.Dispose();
|
foreach (var sessionItem in _sessions)
|
||||||
|
{
|
||||||
|
sessionItem.Value.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_sessionsManagementLock.ExitWriteLock();
|
_sessionsManagementLock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
_sessionsManagementLock?.Dispose();
|
if (_sessionsManagementLock != null)
|
||||||
|
{
|
||||||
|
_sessionsManagementLock.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttClient GetClient(string id)
|
public MqttClient GetClient(string id)
|
||||||
@@ -295,17 +306,17 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IList<MqttClientStatus>> GetClientsStatus()
|
public Task<IList<MqttClientStatus>> GetClientStatusesAsync()
|
||||||
{
|
{
|
||||||
var result = new List<MqttClientStatus>();
|
var result = new List<MqttClientStatus>();
|
||||||
|
|
||||||
lock (_clients)
|
lock (_clients)
|
||||||
{
|
{
|
||||||
foreach (var client in _clients.Values)
|
foreach (var connection in _clients.Values)
|
||||||
{
|
{
|
||||||
var clientStatus = new MqttClientStatus(client)
|
var clientStatus = new MqttClientStatus(connection)
|
||||||
{
|
{
|
||||||
Session = new MqttSessionStatus(client.Session)
|
Session = new MqttSessionStatus(connection.Session)
|
||||||
};
|
};
|
||||||
|
|
||||||
result.Add(clientStatus);
|
result.Add(clientStatus);
|
||||||
@@ -315,16 +326,16 @@ namespace MQTTnet.Server
|
|||||||
return Task.FromResult((IList<MqttClientStatus>)result);
|
return Task.FromResult((IList<MqttClientStatus>)result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IList<MqttSessionStatus>> GetSessionsStatus()
|
public Task<IList<MqttSessionStatus>> GetSessionStatusAsync()
|
||||||
{
|
{
|
||||||
var result = new List<MqttSessionStatus>();
|
var result = new List<MqttSessionStatus>();
|
||||||
|
|
||||||
_sessionsManagementLock.EnterReadLock();
|
_sessionsManagementLock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var session in _sessionsStorage.ReadAllSessions())
|
foreach (var sessionItem in _sessions)
|
||||||
{
|
{
|
||||||
var sessionStatus = new MqttSessionStatus(session);
|
var sessionStatus = new MqttSessionStatus(sessionItem.Value);
|
||||||
result.Add(sessionStatus);
|
result.Add(sessionStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,7 +360,7 @@ namespace MQTTnet.Server
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
|
var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter, cancellationToken).ConfigureAwait(false);
|
||||||
var connAckPacket = MqttPacketFactories.ConnAck.Create(validatingConnectionEventArgs);
|
var connAckPacket = MqttPacketFactories.ConnAck.Create(validatingConnectionEventArgs);
|
||||||
|
|
||||||
if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success)
|
if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success)
|
||||||
@@ -366,7 +377,9 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientConnectedEventArgs(connectPacket,
|
var eventArgs = new ClientConnectedEventArgs(
|
||||||
|
connectPacket.ClientId,
|
||||||
|
connectPacket.Username,
|
||||||
channelAdapter.PacketFormatterAdapter.ProtocolVersion,
|
channelAdapter.PacketFormatterAdapter.ProtocolVersion,
|
||||||
channelAdapter.Endpoint,
|
channelAdapter.Endpoint,
|
||||||
client.Session.Items);
|
client.Session.Items);
|
||||||
@@ -384,7 +397,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Error(exception, exception.Message);
|
_logger.Error(exception, $"Error while validating client {channelAdapter.Endpoint}. {exception.Message}");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -473,7 +486,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
if (!_options.EnablePersistentSessions)
|
if (!_options.EnablePersistentSessions)
|
||||||
{
|
{
|
||||||
_sessionsStorage.Clear();
|
_sessions.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,49 +586,45 @@ namespace MQTTnet.Server
|
|||||||
MqttSession session;
|
MqttSession session;
|
||||||
|
|
||||||
// Create a new session (if required).
|
// Create a new session (if required).
|
||||||
if (!_sessionsStorage.TryGetSession(connectPacket.ClientId, out oldSession))
|
if (!_sessions.TryGetValue(connectPacket.ClientId, out oldSession))
|
||||||
{
|
{
|
||||||
session = CreateSession(connectPacket, validatingConnectionEventArgs, sessionShouldPersist);
|
session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (connectPacket.CleanSession)
|
if (connectPacket.CleanSession)
|
||||||
{
|
{
|
||||||
_logger.Verbose("Deleting existing session of client '{0}' due to clean start", connectPacket.ClientId);
|
_logger.Verbose("Deleting existing session of client '{0}' due to clean start.", connectPacket.ClientId);
|
||||||
_subscriberSessions.Remove(oldSession);
|
_subscriberSessions.Remove(oldSession);
|
||||||
session = CreateSession(connectPacket, validatingConnectionEventArgs, sessionShouldPersist);
|
session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Verbose("Reusing existing session of client '{0}'", connectPacket.ClientId);
|
_logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
|
||||||
session = oldSession;
|
session = oldSession;
|
||||||
oldSession = null;
|
oldSession = null;
|
||||||
|
|
||||||
// Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval
|
// Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval
|
||||||
session.IsPersistent = sessionShouldPersist;
|
session.IsPersistent = sessionShouldPersist;
|
||||||
session.DisconnectedTimestamp = null;
|
|
||||||
session.Recover();
|
|
||||||
|
|
||||||
connAckPacket.IsSessionPresent = true;
|
connAckPacket.IsSessionPresent = true;
|
||||||
|
session.Recover();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_sessionsStorage.UpdateSession(connectPacket.ClientId, session);
|
_sessions[connectPacket.ClientId] = session;
|
||||||
|
|
||||||
// Create a new client (always required).
|
// Create a new client (always required).
|
||||||
lock (_clients)
|
_clients.TryGetValue(connectPacket.ClientId, out oldClient);
|
||||||
|
if (oldClient != null)
|
||||||
{
|
{
|
||||||
_clients.TryGetValue(connectPacket.ClientId, out oldClient);
|
// This will stop the current client from sending and receiving but remains the connection active
|
||||||
if (oldClient != null)
|
// for a later DISCONNECT packet.
|
||||||
{
|
oldClient.IsTakenOver = true;
|
||||||
// This will stop the current client from sending and receiving but remains the connection active
|
|
||||||
// for a later DISCONNECT packet.
|
|
||||||
oldClient.IsTakenOver = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
client = CreateClient(connectPacket, channelAdapter, session);
|
|
||||||
_clients[connectPacket.ClientId] = client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client = CreateClient(connectPacket, channelAdapter, session);
|
||||||
|
|
||||||
|
_clients[connectPacket.ClientId] = client;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -647,14 +656,11 @@ namespace MQTTnet.Server
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttSession CreateSession(MqttConnectPacket connectPacket, ValidatingConnectionEventArgs validatingConnectionEventArgs, bool isPersistent)
|
MqttSession CreateSession(string clientId, IDictionary sessionItems, bool isPersistent)
|
||||||
{
|
{
|
||||||
_logger.Verbose("Created new session for client '{0}'", connectPacket.ClientId);
|
_logger.Verbose("Created new session for client '{0}'.", clientId);
|
||||||
|
|
||||||
return new MqttSession(connectPacket, validatingConnectionEventArgs.SessionItems, _options, _eventContainer, _retainedMessagesManager, this)
|
return new MqttSession(clientId, isPersistent, sessionItems, _options, _eventContainer, _retainedMessagesManager, this);
|
||||||
{
|
|
||||||
IsPersistent = isPersistent
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task FireApplicationMessageNotConsumedEvent(MqttApplicationMessage applicationMessage, string senderId)
|
async Task FireApplicationMessageNotConsumedEvent(MqttApplicationMessage applicationMessage, string senderId)
|
||||||
@@ -673,7 +679,7 @@ namespace MQTTnet.Server
|
|||||||
_sessionsManagementLock.EnterReadLock();
|
_sessionsManagementLock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_sessionsStorage.TryGetSession(clientId, out var session))
|
if (!_sessions.TryGetValue(clientId, out var session))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
|
||||||
}
|
}
|
||||||
@@ -713,11 +719,11 @@ namespace MQTTnet.Server
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<ValidatingConnectionEventArgs> ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
|
async Task<ValidatingConnectionEventArgs> ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// TODO: Load session items from persisted sessions in the future.
|
// TODO: Load session items from persisted sessions in the future.
|
||||||
var sessionItems = new ConcurrentDictionary<object, object>();
|
var sessionItems = new ConcurrentDictionary<object, object>();
|
||||||
var eventArgs = new ValidatingConnectionEventArgs(connectPacket, channelAdapter, sessionItems);
|
var eventArgs = new ValidatingConnectionEventArgs(connectPacket, channelAdapter, sessionItems, cancellationToken);
|
||||||
await _eventContainer.ValidatingConnectionEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _eventContainer.ValidatingConnectionEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
|
|
||||||
// Check the client ID and set a random one if supported.
|
// Check the client ID and set a random one if supported.
|
||||||
|
|||||||
@@ -162,7 +162,10 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_subscriptionsLock?.Dispose();
|
if (_subscriptionsLock != null)
|
||||||
|
{
|
||||||
|
_subscriptionsLock.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
public async Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
return Task.FromResult(message);
|
return Task.FromResult(message);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult<MqttApplicationMessage>(null);
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ClearMessages()
|
public async Task ClearMessages()
|
||||||
|
|||||||
@@ -17,11 +17,14 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public AsyncEvent<ClientDisconnectedEventArgs> ClientDisconnectedEvent { get; } = new AsyncEvent<ClientDisconnectedEventArgs>();
|
public AsyncEvent<ClientDisconnectedEventArgs> ClientDisconnectedEvent { get; } = new AsyncEvent<ClientDisconnectedEventArgs>();
|
||||||
|
|
||||||
|
public AsyncEvent<ClientReAuthenticatingEventArgs> ClientReAuthenticatingEvent { get; } = new AsyncEvent<ClientReAuthenticatingEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<ClientSubscribedTopicEventArgs> ClientSubscribedTopicEvent { get; } = new AsyncEvent<ClientSubscribedTopicEventArgs>();
|
public AsyncEvent<ClientSubscribedTopicEventArgs> ClientSubscribedTopicEvent { get; } = new AsyncEvent<ClientSubscribedTopicEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<ClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopicEvent { get; } = new AsyncEvent<ClientUnsubscribedTopicEventArgs>();
|
public AsyncEvent<ClientUnsubscribedTopicEventArgs> ClientUnsubscribedTopicEvent { get; } = new AsyncEvent<ClientUnsubscribedTopicEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<InterceptingClientApplicationMessageEnqueueEventArgs> InterceptingClientEnqueueEvent { get; } = new AsyncEvent<InterceptingClientApplicationMessageEnqueueEventArgs>();
|
public AsyncEvent<InterceptingClientApplicationMessageEnqueueEventArgs> InterceptingClientEnqueueEvent { get; } =
|
||||||
|
new AsyncEvent<InterceptingClientApplicationMessageEnqueueEventArgs>();
|
||||||
|
|
||||||
public AsyncEvent<InterceptingPacketEventArgs> InterceptingInboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
|
public AsyncEvent<InterceptingPacketEventArgs> InterceptingInboundPacketEvent { get; } = new AsyncEvent<InterceptingPacketEventArgs>();
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ namespace MQTTnet.Server
|
|||||||
readonly MqttPacketBus _packetBus = new MqttPacketBus();
|
readonly MqttPacketBus _packetBus = new MqttPacketBus();
|
||||||
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
|
|
||||||
readonly MqttConnectPacket _connectPacket;
|
|
||||||
readonly MqttServerOptions _serverOptions;
|
readonly MqttServerOptions _serverOptions;
|
||||||
readonly MqttClientSubscriptionsManager _subscriptionsManager;
|
readonly MqttClientSubscriptionsManager _subscriptionsManager;
|
||||||
|
|
||||||
@@ -32,16 +31,18 @@ namespace MQTTnet.Server
|
|||||||
HashSet<string> _subscribedTopics;
|
HashSet<string> _subscribedTopics;
|
||||||
|
|
||||||
public MqttSession(
|
public MqttSession(
|
||||||
MqttConnectPacket connectPacket,
|
string clientId,
|
||||||
|
bool isPersistent,
|
||||||
IDictionary items,
|
IDictionary items,
|
||||||
MqttServerOptions serverOptions,
|
MqttServerOptions serverOptions,
|
||||||
MqttServerEventContainer eventContainer,
|
MqttServerEventContainer eventContainer,
|
||||||
MqttRetainedMessagesManager retainedMessagesManager,
|
MqttRetainedMessagesManager retainedMessagesManager,
|
||||||
MqttClientSessionsManager clientSessionsManager)
|
MqttClientSessionsManager clientSessionsManager)
|
||||||
{
|
{
|
||||||
|
Id = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||||
|
IsPersistent = isPersistent;
|
||||||
Items = items ?? throw new ArgumentNullException(nameof(items));
|
Items = items ?? throw new ArgumentNullException(nameof(items));
|
||||||
|
|
||||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
|
||||||
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
||||||
_clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager));
|
_clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager));
|
||||||
|
|
||||||
@@ -49,14 +50,10 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DateTime CreatedTimestamp { get; } = DateTime.UtcNow;
|
public DateTime CreatedTimestamp { get; } = DateTime.UtcNow;
|
||||||
|
|
||||||
public DateTime? DisconnectedTimestamp { get; set; }
|
|
||||||
|
|
||||||
public bool HasSubscribedTopics => _subscribedTopics != null && _subscribedTopics.Count > 0;
|
public bool HasSubscribedTopics => _subscribedTopics != null && _subscribedTopics.Count > 0;
|
||||||
|
|
||||||
public uint ExpiryInterval => _connectPacket.SessionExpiryInterval;
|
public string Id { get; }
|
||||||
|
|
||||||
public string Id => _connectPacket.ClientId;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Session should persist if CleanSession was set to false (Mqtt3) or if SessionExpiryInterval != 0 (Mqtt5)
|
/// Session should persist if CleanSession was set to false (Mqtt3) or if SessionExpiryInterval != 0 (Mqtt5)
|
||||||
|
|||||||
@@ -1,93 +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.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
|
||||||
{
|
|
||||||
public sealed class MqttSessionsStorage
|
|
||||||
{
|
|
||||||
readonly Dictionary<string, MqttSession> _sessions = new Dictionary<string, MqttSession>(4096);
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
// Make sure that the sessions are also getting disposed!
|
|
||||||
// This object can be reused after disposing.
|
|
||||||
Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
foreach (var sessionItem in _sessions)
|
|
||||||
{
|
|
||||||
sessionItem.Value.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_sessions.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyCollection<MqttSession> ReadAllSessions()
|
|
||||||
{
|
|
||||||
return new ReadOnlyCollection<MqttSession>(_sessions.Values.ToList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetSession(string id, out MqttSession session)
|
|
||||||
{
|
|
||||||
if (id == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_sessions.TryGetValue(id, out session))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the Session Expiry Interval is 0xFFFFFFFF (UINT_MAX), the Session does not expire.
|
|
||||||
if (session.ExpiryInterval > 0 && session.ExpiryInterval != uint.MaxValue)
|
|
||||||
{
|
|
||||||
if (session.DisconnectedTimestamp.HasValue)
|
|
||||||
{
|
|
||||||
if (DateTime.UtcNow > session.DisconnectedTimestamp.Value.AddSeconds(session.ExpiryInterval))
|
|
||||||
{
|
|
||||||
TryRemoveSession(id, out var oldSession);
|
|
||||||
oldSession?.Dispose();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryRemoveSession(string id, out MqttSession session)
|
|
||||||
{
|
|
||||||
if (id == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_sessions.TryGetValue(id, out session))
|
|
||||||
{
|
|
||||||
_sessions.Remove(id);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateSession(string id, MqttSession session)
|
|
||||||
{
|
|
||||||
if (id == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
_sessions[id] = session;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -73,6 +73,12 @@ namespace MQTTnet.Server
|
|||||||
remove => _eventContainer.ClientDisconnectedEvent.RemoveHandler(value);
|
remove => _eventContainer.ClientDisconnectedEvent.RemoveHandler(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event Func<ClientReAuthenticatingEventArgs, Task> ClientReAuthenticatingAsync
|
||||||
|
{
|
||||||
|
add => _eventContainer.ClientReAuthenticatingEvent.AddHandler(value);
|
||||||
|
remove => _eventContainer.ClientReAuthenticatingEvent.RemoveHandler(value);
|
||||||
|
}
|
||||||
|
|
||||||
public event Func<ClientSubscribedTopicEventArgs, Task> ClientSubscribedTopicAsync
|
public event Func<ClientSubscribedTopicEventArgs, Task> ClientSubscribedTopicAsync
|
||||||
{
|
{
|
||||||
add => _eventContainer.ClientSubscribedTopicEvent.AddHandler(value);
|
add => _eventContainer.ClientSubscribedTopicEvent.AddHandler(value);
|
||||||
@@ -200,14 +206,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
|
|
||||||
return _clientSessionsManager.GetClientsStatus();
|
return _clientSessionsManager.GetClientStatusesAsync();
|
||||||
}
|
|
||||||
|
|
||||||
public Task<IList<MqttApplicationMessage>> GetRetainedMessagesAsync()
|
|
||||||
{
|
|
||||||
ThrowIfNotStarted();
|
|
||||||
|
|
||||||
return _retainedMessagesManager.GetMessages();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<MqttApplicationMessage> GetRetainedMessageAsync(string topic)
|
public Task<MqttApplicationMessage> GetRetainedMessageAsync(string topic)
|
||||||
@@ -222,20 +221,21 @@ namespace MQTTnet.Server
|
|||||||
return _retainedMessagesManager.GetMessage(topic);
|
return _retainedMessagesManager.GetMessage(topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<IList<MqttApplicationMessage>> GetRetainedMessagesAsync()
|
||||||
|
{
|
||||||
|
ThrowIfNotStarted();
|
||||||
|
|
||||||
|
return _retainedMessagesManager.GetMessages();
|
||||||
|
}
|
||||||
|
|
||||||
public Task<IList<MqttSessionStatus>> GetSessionsAsync()
|
public Task<IList<MqttSessionStatus>> GetSessionsAsync()
|
||||||
{
|
{
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
|
|
||||||
return _clientSessionsManager.GetSessionsStatus();
|
return _clientSessionsManager.GetSessionStatusAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("Use InjectApplicationMessageAsync instead.")]
|
|
||||||
public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage, CancellationToken cancellationToken = default)
|
public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage, CancellationToken cancellationToken = default)
|
||||||
{
|
|
||||||
return InjectApplicationMessageAsync(injectedApplicationMessage, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task InjectApplicationMessageAsync(InjectedMqttApplicationMessage injectedApplicationMessage, CancellationToken cancellationToken = default)
|
|
||||||
{
|
{
|
||||||
if (injectedApplicationMessage == null)
|
if (injectedApplicationMessage == null)
|
||||||
{
|
{
|
||||||
@@ -298,7 +298,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
_cancellationTokenSource.Cancel(false);
|
_cancellationTokenSource.Cancel(false);
|
||||||
|
|
||||||
await _clientSessionsManager.CloseAllConnections().ConfigureAwait(false);
|
await _clientSessionsManager.CloseAllConnectionsAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var adapter in _adapters)
|
foreach (var adapter in _adapters)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,38 +13,12 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public static class MqttServerExtensions
|
public static class MqttServerExtensions
|
||||||
{
|
{
|
||||||
[Obsolete("Use InjectApplicationMessageAsync instead.")]
|
|
||||||
public static Task InjectApplicationMessage(
|
public static Task InjectApplicationMessage(
|
||||||
this MqttServer server,
|
this MqttServer server,
|
||||||
string topic,
|
string topic,
|
||||||
string payload = null,
|
string payload = null,
|
||||||
MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
|
MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
|
||||||
bool retain = false)
|
bool retain = false)
|
||||||
{
|
|
||||||
return InjectApplicationMessageAsync(server, topic, payload, qualityOfServiceLevel, retain);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Task InjectApplicationMessageAsync(this MqttServer server, MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
if (server == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(server));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationMessage == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
return server.InjectApplicationMessageAsync(new InjectedMqttApplicationMessage(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Task InjectApplicationMessageAsync(
|
|
||||||
this MqttServer server,
|
|
||||||
string topic,
|
|
||||||
string payload = null,
|
|
||||||
MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
|
|
||||||
bool retain = false)
|
|
||||||
{
|
{
|
||||||
if (server == null)
|
if (server == null)
|
||||||
{
|
{
|
||||||
@@ -62,7 +36,7 @@ namespace MQTTnet.Server
|
|||||||
payloadBuffer = Encoding.UTF8.GetBytes(stringPayload);
|
payloadBuffer = Encoding.UTF8.GetBytes(stringPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
return server.InjectApplicationMessageAsync(
|
return server.InjectApplicationMessage(
|
||||||
new InjectedMqttApplicationMessage(
|
new InjectedMqttApplicationMessage(
|
||||||
new MqttApplicationMessage
|
new MqttApplicationMessage
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -131,18 +131,6 @@ namespace MQTTnet.Server
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Usually the MQTT packets can be send partially to improve memory allocations.
|
|
||||||
/// This is done by using multiple TCP packets or WebSocket frames etc.
|
|
||||||
/// Unfortunately not all clients do support this and will close the connection when receiving partial packets.
|
|
||||||
/// </summary>
|
|
||||||
public MqttServerOptionsBuilder WithoutPacketFragmentation()
|
|
||||||
{
|
|
||||||
_options.DefaultEndpointOptions.AllowPacketFragmentation = false;
|
|
||||||
_options.TlsEndpointOptions.AllowPacketFragmentation = false;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MqttServerOptionsBuilder WithPersistentSessions(bool value = true)
|
public MqttServerOptionsBuilder WithPersistentSessions(bool value = true)
|
||||||
{
|
{
|
||||||
_options.EnablePersistentSessions = value;
|
_options.EnablePersistentSessions = value;
|
||||||
|
|||||||
@@ -23,14 +23,6 @@ namespace MQTTnet.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool? KeepAlive { get; set; }
|
public bool? KeepAlive { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
|
|
||||||
/// or WebSocket frames etc. Unfortunately not all clients do support this feature and
|
|
||||||
/// will close the connection when receiving such packets. If such clients are connecting to this
|
|
||||||
/// server the flag must be set to _false_.
|
|
||||||
/// </summary>
|
|
||||||
public bool AllowPacketFragmentation { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the TCP keep alive interval.
|
/// Gets or sets the TCP keep alive interval.
|
||||||
/// The value _null_ indicates that the OS and framework defaults should be used.
|
/// The value _null_ indicates that the OS and framework defaults should be used.
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public DateTime CreatedTimestamp => _session.CreatedTimestamp;
|
public DateTime CreatedTimestamp => _session.CreatedTimestamp;
|
||||||
|
|
||||||
public DateTime? DisconnectedTimestamp => _session.DisconnectedTimestamp;
|
|
||||||
|
|
||||||
public uint ExpiryInterval => _session.ExpiryInterval;
|
|
||||||
|
|
||||||
public string Id => _session.Id;
|
public string Id => _session.Id;
|
||||||
|
|
||||||
public IDictionary Items => _session.Items;
|
public IDictionary Items => _session.Items;
|
||||||
|
|||||||
Reference in New Issue
Block a user