Compare commits

..
Author SHA1 Message Date
Christian df0b268a08 Add new API for extended authentication 2023-05-07 18:27:11 +02:00
Christian addf812d58 Add warning 2023-05-06 14:17:39 +02:00
Christian 533b8e22c2 Fix breaking changes 2023-05-06 13:09:02 +02:00
Christian 3aaf101b3b Merge branch 'master' into 1681-misleading-signature-of-mqttclientconnectasync-mqttclientconnectresultcode-expected-but-throws-exception 2023-05-06 13:03:13 +02:00
Christian 449624fd72 Expose proper connect result in clients Disconnected event (#1728) 2023-05-06 12:49:41 +02:00
Christian 99f4f46062 Add new overloads for packet payload and will payload (#1721) 2023-05-04 22:19:06 +02:00
b08d11c069 replace lock/semaphore by readerwriterlockslim to avoid lock contenti… (#1716)
* replace lock/semaphore by readerwriterlockslim to avoid lock contention while dispatch messages

* Update MqttClientSubscriptionsManager.cs

removed comment out codes

* Update ReleaseNotes.md

---------

Co-authored-by: Zeheng Feng <hf944813@broadcom.com>
Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-05-01 13:53:15 +02:00
Christian ac41e838ec Merge branch 'master' of https://github.com/dotnet/MQTTnet 2023-05-01 12:20:26 +02:00
Christian 836ddf3bbc Update version 2023-05-01 12:20:20 +02:00
Christian 0e815c626c Add validation of string lengths (#1718) 2023-05-01 12:03:23 +02:00
Christian 9d9076ba77 Fix build 2023-04-29 13:47:38 +02:00
Christian af1c151e8e Merge branch 'master' into 1681-misleading-signature-of-mqttclientconnectasync-mqttclientconnectresultcode-expected-but-throws-exception 2023-04-29 12:45:17 +02:00
Christian 4b7d035e9e Reset release notes 2023-04-29 12:37:36 +02:00
Christian 202e299f65 Move code into dedicated components 2023-03-08 17:04:44 +01:00
Christian 129348c07b Refactor connection creation 2023-02-22 21:45:40 +01:00
50 changed files with 1519 additions and 694 deletions
+4 -20
View File
@@ -1,20 +1,4 @@
* [Core] Added net7.0 builds (#1635, thanks to @YAJeff).
* [Core] Added support for payload segments (#1585, thanks to @xljiulang)
* [Client] Added support for passing MQTT v5 options (User properties etc.) for disconnects.
* [Client] An internal exception is no longer caught silently when calling _DisconnectAsync_ to indicate that the disconnect is not clean (BREAKING CHANGE).
* [Client] MQTTv5 features are now checked and an exception is thrown if they are used when using protocol version 3.1.1 and lower. These checks can be disabled in client options. (BREAKING CHANGE!).
* [Client] Added support for disabling packet fragmentation (required for i.e. AWS, #1690, thanks to @logicaloud).
* [Client] Added new overloads for options builder.
* [Client] Renamed _InspectPackage_ to _InspectPacketAsync_ (BREAKING CHANGE).
* [ManagedClient] Exposed added and removed topics in _ManagedProcessFailedEventArgs_ (#1678, thanks to @scottbrogden-iheartmedia).
* [Server] Exposed MQTT v5 sent properties from the affected client in _ClientDisconnectedAsync_ event.
* [Server] Fixed wrong client ID passed to _InterceptingUnsubscriptionEventArgs_ (#1631, thanks to @ghord).
* [Server] Exposed socket settings for TCP keep alive in TCP options (#1544).
* [Server] Exposed server session items at server level and allow custom session items for injected application messages (#1638).
* [Server] Improved performance for retained message handling when subscribing using "DoNotSendOnSubscribe" or "SendAtSubscribeIfNewSubscriptionOnly" (#1661, thanks to @Int32Overflow).
* [Server] Added support for changing the used TLS certificate while the server is running (#1652, thanks to @YAJeff). The certificate provider will now be invoked for every new connection!
* [Server] Added a new API method which allows reading a single retained message without the need to processing the entire set of retained messages (#1659).
* [Server] Added a new event (InterceptingClientEnqueueAsync) which allows intercepting when an application message is enqueued for a client (#1648).
* [Server] Fixed race condition when handling connections which leads to stopped message transfers (#1677, thanks to @RazvanEmilR).
* [Server] Fixed not delivered packets to other sessions if the _InterceptingClientEnqueueEvent_ is used and accepting the packet is denied (#1685, thanks to @RossHNPC).
* [Server] Added missing user properties to subscribe and unsubscribe interceptor (#1702).
* [Core] Add validation of maximum string lengths (#1718).
* [Client] Added overloads for setting packet payload and will payload (#1720).
* [Client] The proper connect result is now exposed in the _Disconnected_ event when authentication fails (#1139).
* [Server] Improved performance by changing internal locking strategy for subscriptions (#1716, thanks to @zeheng).
+1 -1
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request]
env:
VERSION: "4.2.0.${{github.run_number}}"
VERSION: "4.2.1.${{github.run_number}}"
jobs:
build:
@@ -30,17 +30,17 @@ namespace MQTTnet.Benchmarks
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string.");
writer.WriteBinaryData(_demoPayload);
writer.WriteBinary(_demoPayload);
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489);
writer.WriteBinaryData(_demoPayload);
writer.WriteBinary(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(_demoPayload);
writer.WriteBinary(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
}
@@ -83,11 +83,11 @@ namespace MQTTnet.Benchmarks
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574589);
writer.WriteBinaryData(_demoPayload);
writer.WriteBinary(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(_demoPayload);
writer.WriteBinary(_demoPayload);
writer.Reset(0);
}
@@ -12,22 +12,37 @@ namespace MQTTnet.Extensions.ManagedClient
{
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (mqttClient == null)
{
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
}
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
}
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
}
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory _)
{
return new ManagedMqttClientOptionsBuilder();
}
}
}
@@ -22,6 +22,38 @@ namespace MQTTnet.Tests.Clients.ManagedMqttClient
[TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass
{
[TestMethod]
public async Task Expose_Custom_Connection_Error()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
return CompletedTask.Instance;
};
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
MqttClientDisconnectedEventArgs disconnectedArgs = null;
managedClient.DisconnectedAsync += args =>
{
disconnectedArgs = args;
return CompletedTask.Instance;
};
var clientOptions = testEnvironment.Factory.CreateManagedMqttClientOptionsBuilder().WithClientOptions(testEnvironment.CreateDefaultClientOptions()).Build();
await managedClient.StartAsync(clientOptions);
await LongTestDelay();
Assert.IsNotNull(disconnectedArgs);
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, disconnectedArgs.ConnectResult.ResultCode);
}
}
[TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected()
{
@@ -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);
}
}
}
}
@@ -127,6 +127,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
// Perform a clean disconnect.
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
}
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
namespace MQTTnet.Tests.Formatter
@@ -47,23 +48,32 @@ namespace MQTTnet.Tests.Formatter
Assert.AreEqual(1234U, reader.ReadVariableByteInteger());
Assert.AreEqual(9876U, reader.ReadVariableByteInteger());
}
[TestMethod]
[ExpectedException(typeof(MqttProtocolViolationException))]
public void Throw_If_String_Too_Long()
{
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString(string.Empty.PadLeft(65536));
}
[TestMethod]
public void Write_And_Read_Multiple_Times()
{
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string.");
writer.WriteBinaryData(new byte[1234]);
writer.WriteBinary(new byte[1234]);
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489);
writer.WriteBinaryData(new byte[48]);
writer.WriteBinary(new byte[48]);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinaryData(new byte[3]);
writer.WriteBinary(new byte[3]);
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
@@ -245,7 +245,7 @@ namespace MQTTnet.Tests.Mockups
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
{
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort);
return Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort).WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
}
public ILowLevelMqttClient CreateLowLevelClient()
@@ -277,8 +277,7 @@ namespace MQTTnet.Tests.Mockups
// Null is used when the client id is assigned from the server!
if (!string.IsNullOrEmpty(e.ClientId) && !e.ClientId.StartsWith(TestContext.TestName))
{
TrackException(new InvalidOperationException($"Invalid client ID used ({e.ClientId}). It must start with UnitTest name."));
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
Assert.Fail($"Client ID does not start with test name ({TestContext.TestName}).");
}
}
@@ -292,23 +291,12 @@ namespace MQTTnet.Tests.Mockups
{
foreach (var mqttClient in _clients)
{
try
{
//mqttClient.DisconnectAsync().GetAwaiter().GetResult();
}
catch
{
// This can happen when the test already disconnected the client.
}
finally
{
mqttClient?.Dispose();
}
mqttClient?.Dispose();
}
foreach (var lowLevelMqttClient in _lowLevelClients)
{
lowLevelMqttClient.Dispose();
lowLevelMqttClient?.Dispose();
}
try
@@ -24,10 +24,10 @@ namespace MQTTnet.Tests
writer.WriteString("1234567890");
Assert.AreEqual(10 + 2, writer.Length);
writer.WriteBinaryData(new byte[300]);
writer.WriteBinary(new byte[300]);
Assert.AreEqual(300 + 2 + 12, writer.Length);
writer.WriteBinaryData(new byte[5000]);
writer.WriteBinary(new byte[5000]);
Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length);
}
}
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client
{
public sealed class MqttClientPartialAuthenticationResponse
{
public byte[] AuthenticationData { get; set; }
public MqttAuthenticateReasonCode ReasonCode { get; set; }
public List<MqttUserProperty> UserProperties { get; set; }
}
}
@@ -0,0 +1,111 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client.Internal;
using MQTTnet.Exceptions;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client
{
public sealed class MqttExtendedAuthenticationExchangeEventArgs : EventArgs
{
static readonly IReadOnlyCollection<MqttUserProperty> EmptyUserProperties = new List<MqttUserProperty>();
readonly IMqttAuthenticationTransportStrategy _transportStrategy;
public MqttExtendedAuthenticationExchangeEventArgs(MqttAuthPacket authPacket, IMqttAuthenticationTransportStrategy transportStrategy)
{
if (authPacket == null)
{
throw new ArgumentNullException(nameof(authPacket));
}
_transportStrategy = transportStrategy ?? throw new ArgumentNullException(nameof(transportStrategy));
ReasonCode = authPacket.ReasonCode;
ReasonString = authPacket.ReasonString;
AuthenticationMethod = authPacket.AuthenticationMethod;
AuthenticationData = authPacket.AuthenticationData;
UserProperties = authPacket.UserProperties ?? EmptyUserProperties;
}
/// <summary>
/// Gets the authentication data.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public byte[] AuthenticationData { get; }
/// <summary>
/// Gets the authentication method.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string AuthenticationMethod { get; }
/// <summary>
/// Gets the reason code.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttAuthenticateReasonCode ReasonCode { get; }
/// <summary>
/// Gets the reason string.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString { get; }
// /// <summary>
// /// Gets the response which will be sent to the server.
// /// </summary>
// public MqttExtendedAuthenticationExchangeResponse Response { get; } = new MqttExtendedAuthenticationExchangeResponse();
/// <summary>
/// Gets or sets the user properties.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; }
public async Task<MqttClientPartialAuthenticationResponse> ReceiveAuthenticationDataAsync(CancellationToken cancellationToken)
{
var receivePacket = await _transportStrategy.ReceivePacketAsync(AuthenticationMethod, cancellationToken).ConfigureAwait(false);
if (receivePacket == null)
{
throw new MqttCommunicationException("The client closed the connection.");
}
return new MqttClientPartialAuthenticationResponse
{
ReasonCode = receivePacket.ReasonCode,
AuthenticationData = receivePacket.AuthenticationData,
UserProperties = receivePacket.UserProperties
};
}
public Task SendAuthenticationDataAsync(
MqttAuthenticateReasonCode reasonCode,
byte[] authenticationData = null,
string reasonString = null,
List<MqttUserProperty> userProperties = null,
CancellationToken cancellationToken = default)
{
// The authentication method will never change so we must use the already known one [MQTT-4.12.0-5].
//var authPacket = MqttPacketFactories.Auth.Create(AuthenticationMethod, reasonCode);
var authPacket = new MqttAuthPacket
{
AuthenticationMethod = AuthenticationMethod,
AuthenticationData = authenticationData,
UserProperties = userProperties,
ReasonCode = reasonCode,
ReasonString = reasonString
};
return _transportStrategy.SendPacketAsync(authPacket, cancellationToken);
}
}
}
@@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
namespace MQTTnet.Client
{
public sealed class MqttReAuthenticationOptions
{
/// <summary>
/// Gets or sets the authentication data.
/// Authentication data is binary information used to transmit multiple iterations of cryptographic secrets of protocol
/// steps.
/// The content of the authentication data is highly dependent on the specific implementation of the authentication
/// method.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public byte[] AuthenticationData { get; set; }
/// <summary>
/// Gets or sets the user properties.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
}
}
@@ -11,110 +11,112 @@ namespace MQTTnet.Client
public sealed class MqttClientConnectResult
{
/// <summary>
/// Gets the result code.
/// MQTTv5 only.
/// </summary>
public MqttClientConnectResultCode ResultCode { get; internal set; }
/// <summary>
/// Gets a value indicating whether a session was already available or not.
/// MQTTv5 only.
/// </summary>
public bool IsSessionPresent { get; internal set; }
/// <summary>
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server.
/// MQTTv5 only.
/// </summary>
public bool WildcardSubscriptionAvailable { get; internal set; }
/// <summary>
/// Gets whether the server supports retained messages.
/// MQTTv5 only.
/// </summary>
public bool RetainAvailable { get; internal set; }
/// <summary>
/// Gets the client identifier which was chosen by the server.
/// MQTTv5 only.
/// Gets the client identifier which was chosen by the server.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string AssignedClientIdentifier { get; internal set; }
/// <summary>
/// Gets the authentication method.
/// MQTTv5 only.
/// Gets the authentication data.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public byte[] AuthenticationData { get; internal set; }
/// <summary>
/// Gets the authentication method.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string AuthenticationMethod { get; internal set; }
/// <summary>
/// Gets the authentication data.
/// MQTTv5 only.
/// Gets a value indicating whether a session was already available or not.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public byte[] AuthenticationData { get; internal set; }
public bool IsSessionPresent { get; internal set; }
public uint? MaximumPacketSize { get; internal set; }
/// <summary>
/// Gets the reason string.
/// MQTTv5 only.
/// </summary>
public string ReasonString { get; internal set; }
public ushort? ReceiveMaximum { get; internal set; }
/// <summary>
/// Gets the maximum QoS which is supported by the server.
/// MQTTv5 only.
/// Gets the maximum QoS which is supported by the server.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttQualityOfServiceLevel MaximumQoS { get; internal set; }
/// <summary>
/// Gets the response information.
/// MQTTv5 only.
/// Gets the reason string.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString { get; internal set; }
public ushort? ReceiveMaximum { get; internal set; }
/// <summary>
/// Gets the response information.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ResponseInformation { get; internal set; }
/// <summary>
/// Gets the maximum value for a topic alias. 0 means not supported.
/// MQTTv5 only.
/// Gets the result code.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public ushort TopicAliasMaximum { get; internal set; }
public MqttClientConnectResultCode ResultCode { get; internal set; }
/// <summary>
/// Gets an alternate server which should be used instead of the current one.
/// MQTTv5 only.
/// Gets whether the server supports retained messages.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ServerReference { get; internal set; }
public bool RetainAvailable { get; internal set; }
/// <summary>
/// MQTTv5 only.
/// Gets the keep alive interval which was chosen by the server instead of the
/// keep alive interval from the client CONNECT packet.
/// A value of 0 indicates that the feature is not used.
/// MQTTv5 only.
/// Gets the keep alive interval which was chosen by the server instead of the
/// keep alive interval from the client CONNECT packet.
/// A value of 0 indicates that the feature is not used.
/// </summary>
public ushort ServerKeepAlive { get; internal set; }
/// <summary>
/// Gets an alternate server which should be used instead of the current one.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ServerReference { get; internal set; }
public uint? SessionExpiryInterval { get; internal set; }
/// <summary>
/// Gets a value indicating whether the subscription identifiers are available or not.
/// MQTTv5 only.
/// </summary>
public bool SubscriptionIdentifiersAvailable { get; internal set; }
/// <summary>
/// Gets a value indicating whether the shared subscriptions are available or not.
/// MQTTv5 only.
/// Gets a value indicating whether the shared subscriptions are available or not.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public bool SharedSubscriptionAvailable { get; internal set; }
/// <summary>
/// Gets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// MQTTv5 only.
/// Gets a value indicating whether the subscription identifiers are available or not.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public bool SubscriptionIdentifiersAvailable { get; internal set; }
/// <summary>
/// Gets the maximum value for a topic alias. 0 means not supported.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public ushort TopicAliasMaximum { get; internal set; }
/// <summary>
/// Gets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
/// packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; internal set; }
/// <summary>
/// Gets a value indicating whether wildcards can be used in subscriptions at the current server.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public bool WildcardSubscriptionAvailable { get; internal set; }
}
}
}
@@ -1,13 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
namespace MQTTnet.Client
{
public interface IMqttExtendedAuthenticationExchangeHandler
{
Task HandleRequestAsync(MqttExtendedAuthenticationExchangeContext context);
}
}
@@ -1,62 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client
{
public class MqttExtendedAuthenticationExchangeContext
{
public MqttExtendedAuthenticationExchangeContext(MqttAuthPacket authPacket, MqttClient client)
{
if (authPacket == null) throw new ArgumentNullException(nameof(authPacket));
ReasonCode = authPacket.ReasonCode;
ReasonString = authPacket.ReasonString;
AuthenticationMethod = authPacket.AuthenticationMethod;
AuthenticationData = authPacket.AuthenticationData;
UserProperties = authPacket.UserProperties;
Client = client ?? throw new ArgumentNullException(nameof(client));
}
/// <summary>
/// Gets the reason code.
/// Hint: MQTT 5 feature only.
/// </summary>
public MqttAuthenticateReasonCode ReasonCode { get; }
/// <summary>
/// Gets the reason string.
/// Hint: MQTT 5 feature only.
/// </summary>
public string ReasonString { get; }
/// <summary>
/// Gets the authentication method.
/// Hint: MQTT 5 feature only.
/// </summary>
public string AuthenticationMethod { get; }
/// <summary>
/// Gets the authentication data.
/// Hint: MQTT 5 feature only.
/// </summary>
public byte[] AuthenticationData { get; }
/// <summary>
/// Gets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only.
/// </summary>
public List<MqttUserProperty> UserProperties { get; }
public MqttClient Client { get; }
}
}
@@ -1,42 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client
{
public class MqttExtendedAuthenticationExchangeData
{
/// <summary>
/// Gets or sets the reason code.
/// Hint: MQTT 5 feature only.
/// </summary>
public MqttAuthenticateReasonCode ReasonCode { get; set; }
/// <summary>
/// Gets or sets the reason string.
/// Hint: MQTT 5 feature only.
/// </summary>
public string ReasonString { get; set; }
/// <summary>
/// Gets or sets the authentication data.
/// Authentication data is binary information used to transmit multiple iterations of cryptographic secrets of protocol steps.
/// The content of the authentication data is highly dependent on the specific implementation of the authentication method.
/// Hint: MQTT 5 feature only.
/// </summary>
public byte[] AuthenticationData { get; set; }
/// <summary>
/// Gets or sets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only.
/// </summary>
public List<MqttUserProperty> UserProperties { get; }
}
}
+4 -2
View File
@@ -8,13 +8,15 @@ namespace MQTTnet.Client
public interface IMqttClient : IDisposable
{
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
event Func<MqttExtendedAuthenticationExchangeEventArgs, Task> ExtendedAuthenticationExchangeAsync;
event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
bool IsConnected { get; }
@@ -29,7 +31,7 @@ namespace MQTTnet.Client
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
Task ReAuthenticateAsync(MqttReAuthenticationOptions options, CancellationToken cancellationToken = default);
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
@@ -0,0 +1,225 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Client.Internal
{
public sealed class MqttInitialAuthenticationStrategy : IMqttAuthenticationTransportStrategy
{
readonly IMqttChannelAdapter _channelAdapter;
public MqttInitialAuthenticationStrategy(IMqttChannelAdapter channelAdapter)
{
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
}
public Task SendPacketAsync(MqttAuthPacket authPacket, CancellationToken cancellationToken)
{
if (authPacket == null)
{
throw new ArgumentNullException(nameof(authPacket));
}
return _channelAdapter.SendPacketAsync(authPacket, cancellationToken);
}
public async Task<MqttAuthPacket> ReceivePacketAsync(string authenticationMethod, CancellationToken cancellationToken)
{
if (authenticationMethod == null)
{
throw new ArgumentNullException(nameof(authenticationMethod));
}
var receivePacket = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
if (receivePacket is MqttAuthPacket authPacket)
{
if (!string.Equals(authPacket.AuthenticationMethod, authenticationMethod, StringComparison.Ordinal))
{
throw new MqttProtocolViolationException("The authentication method is not allowed to change while authenticating.");
}
return authPacket;
}
if (receivePacket == null)
{
throw new MqttCommunicationException("The server closed the connection.");
}
throw new MqttProtocolViolationException("Expected an AUTH packet from the client.");
}
}
public interface IMqttAuthenticationTransportStrategy
{
Task SendPacketAsync(MqttAuthPacket authPacket, CancellationToken cancellationToken);
Task<MqttAuthPacket> ReceivePacketAsync(string authenticationMethod, CancellationToken cancellationToken);
}
public sealed class MqttClientAuthenticationHandler
{
readonly MqttClient _client;
readonly AsyncEvent<MqttExtendedAuthenticationExchangeEventArgs> _extendedAuthenticationExchangeEvent = new AsyncEvent<MqttExtendedAuthenticationExchangeEventArgs>();
readonly MqttNetSourceLogger _logger;
public MqttClientAuthenticationHandler(MqttClient client, IMqttNetLogger logger)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_client = client ?? throw new ArgumentNullException(nameof(client));
_logger = logger.WithSource(nameof(MqttClientAuthenticationHandler));
}
public void AddHandler(Func<MqttExtendedAuthenticationExchangeEventArgs, Task> handler)
{
_extendedAuthenticationExchangeEvent.AddHandler(handler);
}
public async Task<MqttClientConnectResult> Authenticate(IMqttChannelAdapter channelAdapter, MqttClientOptions options, CancellationToken cancellationToken)
{
if (channelAdapter == null)
{
throw new ArgumentNullException(nameof(channelAdapter));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
// From the RFC:
// The CONNACK packet is the packet sent by the Server in response to a CONNECT packet received from a Client.
// The Server MUST send a CONNACK with a 0x00 (Success) Reason Code before sending any Packet other than AUTH [MQTT-3.2.0-1].
// The Server MUST NOT send more than one CONNACK in a Network Connection [MQTT-3.2.0-2].
MqttClientConnectResult result;
try
{
// Send the CONNECT packet..
var connectPacket = MqttPacketFactories.Connect.Create(options);
await _client.Send(connectPacket, cancellationToken).ConfigureAwait(false);
// Wait for the CONNACK packet...
MqttConnAckPacket connAckPacket = null;
while (!cancellationToken.IsCancellationRequested)
{
var receivedPacket = await channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
if (receivedPacket is MqttAuthPacket initialAuthPacket)
{
// MQTT v3.1.1 cannot send an AUTH packet.
// If the Server requires additional information to complete the authentication, it can send an AUTH packet to the Client.
// This packet MUST contain a Reason Code of 0x18 (Continue authentication)
if (initialAuthPacket.ReasonCode != MqttAuthenticateReasonCode.ContinueAuthentication)
{
throw new MqttProtocolViolationException("Wrong reason code received [MQTT-4.12.0-2].");
}
//var response = await HandleExtendedAuthentication(authPacket, channelAdapter).ConfigureAwait(false);
await HandleExtendedAuthentication(initialAuthPacket, new MqttInitialAuthenticationStrategy(channelAdapter)).ConfigureAwait(false);
}
else if (receivedPacket is MqttConnAckPacket connAckPacketBuffer)
{
connAckPacket = connAckPacketBuffer;
// The CONNACK packet is the last packet when authenticating so there is no further AUTH packet allowed!
break;
}
else
{
throw new MqttProtocolViolationException($"Received {receivedPacket.GetRfcName()} while authenticating.");
}
}
cancellationToken.ThrowIfCancellationRequested();
// If the initial CONNECT packet included an Authentication Method property then all AUTH packets,
// and any successful CONNACK packet MUST include an Authentication Method Property with the same
// value as in the CONNECT packet [MQTT-4.12.0-5].
if (!string.IsNullOrEmpty(connectPacket.AuthenticationMethod))
{
if (!string.Equals(connectPacket.AuthenticationMethod, connAckPacket?.AuthenticationMethod))
{
throw new MqttProtocolViolationException("The CONNACK packet does not have the same authentication method as the CONNECT packet.");
}
}
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion);
}
catch (Exception exception)
{
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
}
// This is no feature. It is basically a backward compatibility option and should be removed in the future.
// The client should not throw any exception if the transport layer connection was successful and the server
// did send a proper ACK packet.
if (options.ThrowOnNonSuccessfulResponseFromServer)
{
_logger.Warning(
"Client will now throw an _MqttConnectingFailedException_. This is obsolete and will be removed in the future. Consider setting _ThrowOnNonSuccessfulResponseFromServer=False_ in client options.");
if (result.ResultCode != MqttClientConnectResultCode.Success)
{
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
}
}
_logger.Verbose("Authenticated MQTT connection with server established.");
return result;
}
public Task HandleExtendedAuthentication(MqttAuthPacket initialAuthPacket, IMqttAuthenticationTransportStrategy transportStrategy)
{
ValidateEventHandler();
var eventArgs = new MqttExtendedAuthenticationExchangeEventArgs(initialAuthPacket, transportStrategy);
return _extendedAuthenticationExchangeEvent.InvokeAsync(eventArgs);
}
void ValidateEventHandler()
{
if (!_extendedAuthenticationExchangeEvent.HasHandlers)
{
throw new InvalidOperationException("Cannot handle extended authentication without attached event handler.");
}
}
public void RemoveHandler(Func<MqttExtendedAuthenticationExchangeEventArgs, Task> handler)
{
_extendedAuthenticationExchangeEvent.RemoveHandler(handler);
}
public async Task ReAuthenticate(MqttReAuthenticationOptions options, CancellationToken cancellationToken)
{
ValidateEventHandler();
var authPacket = MqttPacketFactories.Auth.CreateReAuthenticationPacket(_client.Options, options);
// Sending the AUTH packet will trigger the re authentication and the event handler will
// handle the details.
await _client.Send(authPacket, cancellationToken).ConfigureAwait(false);
}
}
}
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Diagnostics;
using MQTTnet.Internal;
namespace MQTTnet.Client.Internal
{
public sealed class MqttClientEvents
{
public AsyncEvent<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceivedEvent { get; } = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
public AsyncEvent<MqttClientConnectingEventArgs> ConnectingEvent { get; } = new AsyncEvent<MqttClientConnectingEventArgs>();
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
public AsyncEvent<InspectMqttPacketEventArgs> InspectPacketEvent { get; } = new AsyncEvent<InspectMqttPacketEventArgs>();
}
}
@@ -0,0 +1,126 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
namespace MQTTnet.Client.Internal
{
public sealed class MqttClientKeepAliveHandler
{
readonly MqttClient _client;
readonly MqttNetSourceLogger _logger;
CancellationTokenSource _cancellationToken;
TimeSpan _keepAliveInterval;
Task _keepAlivePacketsSenderTask;
DateTime _lastPacketSentTimestamp;
public MqttClientKeepAliveHandler(MqttClient client, IMqttNetLogger logger)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
_logger = logger.WithSource(nameof(MqttClientKeepAliveHandler));
}
public void Disable()
{
try
{
_cancellationToken?.Cancel(false);
}
finally
{
_cancellationToken?.Dispose();
_cancellationToken = null;
}
}
public void Enable(MqttClientConnectResult connectResult)
{
if (connectResult == null)
{
throw new ArgumentNullException(nameof(connectResult));
}
_keepAliveInterval = _client.Options.KeepAlivePeriod;
if (connectResult.ServerKeepAlive > 0)
{
_logger.Info("Using keep alive value {0} sent from the server", connectResult.ServerKeepAlive);
_keepAliveInterval = TimeSpan.FromSeconds(connectResult.ServerKeepAlive);
}
if (_keepAliveInterval == TimeSpan.Zero)
{
return;
}
_cancellationToken = new CancellationTokenSource();
_keepAlivePacketsSenderTask = Task.Run(() => SendKeepAliveMessagesLoop(_cancellationToken.Token), _cancellationToken.Token);
}
public void TrackSentPacket()
{
// Only use UTC to avoid performance hit by loading current time zone all the time.
_lastPacketSentTimestamp = DateTime.UtcNow;
}
async Task SendKeepAliveMessagesLoop(CancellationToken cancellationToken)
{
try
{
_logger.Verbose("Start sending keep alive packets.");
while (!cancellationToken.IsCancellationRequested)
{
// Values described here: [MQTT-3.1.2-24].
var timeWithoutPacketSent = DateTime.UtcNow - _lastPacketSentTimestamp;
if (timeWithoutPacketSent > _keepAliveInterval)
{
using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
timeout.CancelAfter(_client.Options.Timeout);
await _client.PingAsync(timeout.Token).ConfigureAwait(false);
}
}
// Wait a fixed time in all cases. Calculation of the remaining time is complicated
// due to some edge cases and was buggy in the past. Now we wait several ms because the
// min keep alive value is one second so that the server will wait 1.5 seconds for a PING packet.
await Task.Delay(250, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
if (_client._cleanDisconnectInitiated)
{
return;
}
if (exception is OperationCanceledException)
{
return;
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while sending/receiving keep alive packets");
}
else
{
_logger.Error(exception, "Error exception while sending/receiving keep alive packets");
}
await _client.DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped sending keep alive packets");
}
}
}
}
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Client.Internal
{
public static class MqttClientResultFactory
{
public static readonly MqttClientConnectResultFactory ConnectResult = new MqttClientConnectResultFactory();
public static readonly MqttClientPublishResultFactory PublishResult = new MqttClientPublishResultFactory();
public static readonly MqttClientSubscribeResultFactory SubscribeResult = new MqttClientSubscribeResultFactory();
public static readonly MqttClientUnsubscribeResultFactory UnsubscribeResult = new MqttClientUnsubscribeResultFactory();
}
}
+259 -303
View File
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Client.Internal;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
@@ -20,25 +21,19 @@ namespace MQTTnet.Client
public sealed class MqttClient : Disposable, IMqttClient
{
readonly IMqttClientAdapterFactory _adapterFactory;
readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory();
readonly MqttClientSubscribeResultFactory _clientSubscribeResultFactory = new MqttClientSubscribeResultFactory();
readonly MqttClientUnsubscribeResultFactory _clientUnsubscribeResultFactory = new MqttClientUnsubscribeResultFactory();
readonly AsyncEvent<MqttClientConnectedEventArgs> _connectedEvent = new AsyncEvent<MqttClientConnectedEventArgs>();
readonly AsyncEvent<MqttClientConnectingEventArgs> _connectingEvent = new AsyncEvent<MqttClientConnectingEventArgs>();
readonly AsyncEvent<MqttClientDisconnectedEventArgs> _disconnectedEvent = new AsyncEvent<MqttClientDisconnectedEventArgs>();
readonly MqttClientAuthenticationHandler _authenticationHandler;
readonly object _disconnectLock = new object();
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
readonly MqttClientEvents _events = new MqttClientEvents();
readonly MqttClientKeepAliveHandler _keepAliveHandler;
readonly MqttNetSourceLogger _logger;
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
readonly IMqttNetLogger _rootLogger;
IMqttChannelAdapter _adapter;
IMqttChannelAdapter _channelAdapter;
bool _cleanDisconnectInitiated;
internal bool _cleanDisconnectInitiated;
CancellationTokenSource _clientAlive;
volatile int _connectionStatus;
// The value for this field can be set from two different enums.
@@ -46,11 +41,6 @@ namespace MQTTnet.Client
int _disconnectReason;
string _disconnectReasonString;
List<MqttUserProperty> _disconnectUserProperties;
Task _keepAlivePacketsSenderTask;
DateTime _lastPacketSentTimestamp;
CancellationTokenSource _mqttClientAlive;
MqttPacketDispatcher _packetDispatcher;
Task _packetReceiverTask;
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
@@ -61,36 +51,45 @@ namespace MQTTnet.Client
_adapterFactory = channelFactory ?? throw new ArgumentNullException(nameof(channelFactory));
_rootLogger = logger ?? throw new ArgumentNullException(nameof(logger));
_logger = logger.WithSource(nameof(MqttClient));
_authenticationHandler = new MqttClientAuthenticationHandler(this, logger);
_keepAliveHandler = new MqttClientKeepAliveHandler(this, logger);
}
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
{
add => _applicationMessageReceivedEvent.AddHandler(value);
remove => _applicationMessageReceivedEvent.RemoveHandler(value);
add => _events.ApplicationMessageReceivedEvent.AddHandler(value);
remove => _events.ApplicationMessageReceivedEvent.RemoveHandler(value);
}
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
{
add => _connectedEvent.AddHandler(value);
remove => _connectedEvent.RemoveHandler(value);
add => _events.ConnectedEvent.AddHandler(value);
remove => _events.ConnectedEvent.RemoveHandler(value);
}
public event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync
{
add => _connectingEvent.AddHandler(value);
remove => _connectingEvent.RemoveHandler(value);
add => _events.ConnectingEvent.AddHandler(value);
remove => _events.ConnectingEvent.RemoveHandler(value);
}
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
{
add => _disconnectedEvent.AddHandler(value);
remove => _disconnectedEvent.RemoveHandler(value);
add => _events.DisconnectedEvent.AddHandler(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
{
add => _inspectPacketEvent.AddHandler(value);
remove => _inspectPacketEvent.RemoveHandler(value);
add => _events.InspectPacketEvent.AddHandler(value);
remove => _events.InspectPacketEvent.RemoveHandler(value);
}
public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
@@ -114,21 +113,19 @@ namespace MQTTnet.Client
{
Options = options;
if (_connectingEvent.HasHandlers)
if (_events.ConnectingEvent.HasHandlers)
{
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
await _events.ConnectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
}
Cleanup();
_packetIdentifierProvider.Reset();
_packetDispatcher = new MqttPacketDispatcher();
_clientAlive = new CancellationTokenSource();
_mqttClientAlive = new CancellationTokenSource();
var mqttClientAliveToken = _mqttClientAlive.Token;
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_inspectPacketEvent, _rootLogger), _rootLogger);
_adapter = adapter;
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_events.InspectPacketEvent, _rootLogger), _rootLogger);
_channelAdapter = adapter;
if (cancellationToken.CanBeCanceled)
{
@@ -144,39 +141,26 @@ namespace MQTTnet.Client
}
}
_lastPacketSentTimestamp = DateTime.UtcNow;
var keepAliveInterval = Options.KeepAlivePeriod;
if (connectResult.ServerKeepAlive > 0)
{
_logger.Info($"Using keep alive value ({connectResult.ServerKeepAlive}) sent from the server.");
keepAliveInterval = TimeSpan.FromSeconds(connectResult.ServerKeepAlive);
}
if (keepAliveInterval != TimeSpan.Zero)
{
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(mqttClientAliveToken), mqttClientAliveToken);
}
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
_logger.Info("Connected.");
_logger.Info("Connected");
if (_connectedEvent.HasHandlers)
{
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
await _connectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
}
await OnConnected(connectResult).ConfigureAwait(false);
return connectResult;
}
catch (Exception exception)
{
if (exception is MqttConnectingFailedException connectingFailedException)
{
connectResult = connectingFailedException.Result;
}
_disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError;
_logger.Error(exception, "Error while connecting with server.");
_logger.Error(exception, "Error while connecting with server");
await DisconnectInternalAsync(null, exception, connectResult).ConfigureAwait(false);
await DisconnectInternal(null, exception, connectResult).ConfigureAwait(false);
throw;
}
@@ -210,7 +194,7 @@ namespace MQTTnet.Client
if (Options.ValidateFeatures)
{
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
}
// Sending the DISCONNECT may fail due to connection issues. The resulting exception
@@ -219,19 +203,19 @@ namespace MQTTnet.Client
if (cancellationToken.CanBeCanceled)
{
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
await Send(disconnectPacket, cancellationToken).ConfigureAwait(false);
}
else
{
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
await SendAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
await Send(disconnectPacket, timeout.Token).ConfigureAwait(false);
}
}
}
finally
{
await DisconnectCoreAsync(null, null, null, clientWasConnected).ConfigureAwait(false);
await DisconnectCore(null, null, null, clientWasConnected).ConfigureAwait(false);
}
}
@@ -239,13 +223,13 @@ namespace MQTTnet.Client
{
if (cancellationToken.CanBeCanceled)
{
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
}
else
{
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false);
await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false);
}
}
}
@@ -261,7 +245,7 @@ namespace MQTTnet.Client
if (Options.ValidateFeatures)
{
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion);
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
}
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
@@ -274,11 +258,11 @@ namespace MQTTnet.Client
}
case MqttQualityOfServiceLevel.AtLeastOnce:
{
return PublishAtLeastOnceAsync(publishPacket, cancellationToken);
return PublishAtLeastOnce(publishPacket, cancellationToken);
}
case MqttQualityOfServiceLevel.ExactlyOnce:
{
return PublishExactlyOnceAsync(publishPacket, cancellationToken);
return PublishExactlyOnce(publishPacket, cancellationToken);
}
default:
{
@@ -287,26 +271,25 @@ namespace MQTTnet.Client
}
}
public Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default)
public Task ReAuthenticateAsync(MqttReAuthenticationOptions options, CancellationToken cancellationToken = default)
{
if (data == null)
if (options == null)
{
throw new ArgumentNullException(nameof(data));
throw new ArgumentNullException(nameof(options));
}
ThrowIfDisposed();
ThrowIfNotConnected();
var authPacket = new MqttAuthPacket
if (cancellationToken.CanBeCanceled)
{
// This must always be equal to the value from the CONNECT packet. So we use it here to ensure that.
AuthenticationMethod = Options.AuthenticationMethod,
AuthenticationData = data.AuthenticationData,
ReasonString = data.ReasonString,
UserProperties = data.UserProperties
};
return _authenticationHandler.ReAuthenticate(options, cancellationToken);
}
return SendAsync(authPacket, cancellationToken);
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
return _authenticationHandler.ReAuthenticate(options, timeout.Token);
}
}
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default)
@@ -323,7 +306,7 @@ namespace MQTTnet.Client
if (Options.ValidateFeatures)
{
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
}
ThrowIfDisposed();
@@ -335,17 +318,17 @@ namespace MQTTnet.Client
MqttSubAckPacket subAckPacket;
if (cancellationToken.CanBeCanceled)
{
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
subAckPacket = await Request<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
}
else
{
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, timeout.Token).ConfigureAwait(false);
subAckPacket = await Request<MqttSubAckPacket>(subscribePacket, timeout.Token).ConfigureAwait(false);
}
}
return _clientSubscribeResultFactory.Create(subscribePacket, subAckPacket);
return MqttClientResultFactory.SubscribeResult.Create(subscribePacket, subAckPacket);
}
public async Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default)
@@ -365,7 +348,7 @@ namespace MQTTnet.Client
if (Options.ValidateFeatures)
{
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _channelAdapter.PacketFormatterAdapter.ProtocolVersion);
}
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
@@ -374,17 +357,38 @@ namespace MQTTnet.Client
MqttUnsubAckPacket unsubAckPacket;
if (cancellationToken.CanBeCanceled)
{
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
unsubAckPacket = await Request<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
}
else
{
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, timeout.Token).ConfigureAwait(false);
unsubAckPacket = await Request<MqttUnsubAckPacket>(unsubscribePacket, timeout.Token).ConfigureAwait(false);
}
}
return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket);
return MqttClientResultFactory.UnsubscribeResult.Create(unsubscribePacket, unsubAckPacket);
}
internal Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult)
{
var clientWasConnected = IsConnected;
if (!DisconnectIsPendingOrFinished())
{
return DisconnectCore(sender, exception, connectResult, clientWasConnected);
}
return CompletedTask.Instance;
}
internal Task Send(MqttPacket packet, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
_keepAliveHandler.TrackSentPacket();
return _channelAdapter.SendPacketAsync(packet, cancellationToken);
}
protected override void Dispose(bool disposing)
@@ -408,7 +412,7 @@ namespace MQTTnet.Client
if (!eventArgs.ProcessingFailed)
{
var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs);
return SendAsync(pubAckPacket, cancellationToken);
return Send(pubAckPacket, cancellationToken);
}
}
else if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
@@ -416,7 +420,7 @@ namespace MQTTnet.Client
if (!eventArgs.ProcessingFailed)
{
var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs);
return SendAsync(pubRecPacket, cancellationToken);
return Send(pubRecPacket, cancellationToken);
}
}
else
@@ -427,50 +431,22 @@ namespace MQTTnet.Client
return CompletedTask.Instance;
}
async Task<MqttClientConnectResult> AuthenticateAsync(MqttClientOptions options, CancellationToken cancellationToken)
{
MqttClientConnectResult result;
try
{
var connectPacket = MqttPacketFactories.Connect.Create(options);
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
var clientConnectResultFactory = new MqttClientConnectResultFactory();
result = clientConnectResultFactory.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion);
}
catch (Exception exception)
{
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
}
if (result.ResultCode != MqttClientConnectResultCode.Success)
{
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
}
_logger.Verbose("Authenticated MQTT connection with server established.");
return result;
}
void Cleanup()
{
try
{
_mqttClientAlive?.Cancel(false);
_clientAlive?.Cancel(false);
}
finally
{
_mqttClientAlive?.Dispose();
_mqttClientAlive = null;
_clientAlive?.Dispose();
_clientAlive = null;
_publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = null;
_adapter?.Dispose();
_adapter = null;
_channelAdapter?.Dispose();
_channelAdapter = null;
_packetDispatcher?.Dispose();
_packetDispatcher = null;
@@ -484,37 +460,46 @@ namespace MQTTnet.Client
async Task<MqttClientConnectResult> ConnectInternal(CancellationToken cancellationToken)
{
var backgroundCancellationToken = _mqttClientAlive.Token;
var clientAliveToken = _clientAlive.Token;
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(clientAliveToken, cancellationToken))
{
_logger.Verbose("Trying to connect with server '{0}'", Options.ChannelOptions);
await _adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
await _channelAdapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
_logger.Verbose("Connection with server established");
_publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
var connectResult = await _authenticationHandler.Authenticate(_channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false);
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
if (connectResult.ResultCode == MqttClientConnectResultCode.Success)
{
_publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
return await AuthenticateAsync(Options, effectiveCancellationToken.Token).ConfigureAwait(false);
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPacketsLoop(clientAliveToken), clientAliveToken);
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(clientAliveToken), clientAliveToken);
_keepAliveHandler.Enable(connectResult);
}
return connectResult;
}
}
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
async Task DisconnectCore(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
{
TryInitiateDisconnect();
try
{
if (_adapter != null)
if (_channelAdapter != null)
{
_logger.Verbose("Disconnecting [Timeout={0}]", Options.Timeout);
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
await _adapter.DisconnectAsync(timeout.Token).ConfigureAwait(false);
await _channelAdapter.DisconnectAsync(timeout.Token).ConfigureAwait(false);
}
}
@@ -531,9 +516,10 @@ namespace MQTTnet.Client
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger);
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
_keepAliveHandler.Disable();
await Task.WhenAll(receiverTask, publishPacketReceiverTask).ConfigureAwait(false);
}
catch (Exception innerException)
{
@@ -557,17 +543,17 @@ namespace MQTTnet.Client
// This handler must be executed in a new thread because otherwise a dead lock may happen
// when trying to reconnect in that handler etc.
Task.Run(() => _disconnectedEvent.InvokeAsync(eventArgs)).RunInBackground(_logger);
Task.Run(() => _events.DisconnectedEvent.InvokeAsync(eventArgs)).RunInBackground(_logger);
}
}
Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult)
Task DisconnectInternal(Task sender, Exception exception, MqttClientConnectResult connectResult)
{
var clientWasConnected = IsConnected;
if (!DisconnectIsPendingOrFinished())
{
return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected);
return DisconnectCore(sender, exception, connectResult, clientWasConnected);
}
return CompletedTask.Instance;
@@ -612,21 +598,21 @@ namespace MQTTnet.Client
}
}
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessage(MqttPublishPacket publishPacket)
{
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
await _events.ApplicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
return eventArgs;
}
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket)
Task OnConnected(MqttClientConnectResult connectResult)
{
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
if (extendedAuthenticationExchangeHandler != null)
if (_events.ConnectedEvent.HasHandlers)
{
return extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this));
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
return _events.ConnectedEvent.InvokeAsync(eventArgs);
}
return CompletedTask.Instance;
@@ -641,10 +627,10 @@ namespace MQTTnet.Client
// Also dispatch disconnect to waiting threads to generate a proper exception.
_packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket));
return DisconnectInternalAsync(_packetReceiverTask, null, null);
return DisconnectInternal(_packetReceiverTask, null, null);
}
async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken)
async Task ProcessReceivedPublishPacketsLoop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
@@ -657,7 +643,7 @@ namespace MQTTnet.Client
}
var publishPacket = publishPacketDequeueResult.Item;
var eventArgs = await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false);
var eventArgs = await HandleReceivedApplicationMessage(publishPacket).ConfigureAwait(false);
if (eventArgs.AutoAcknowledge)
{
@@ -681,7 +667,7 @@ namespace MQTTnet.Client
// The packet is unknown. Probably due to a restart of the client.
// So wen send this to the server to trigger a full resend of the message.
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
return SendAsync(pubRelPacket, cancellationToken);
return Send(pubRelPacket, cancellationToken);
}
return CompletedTask.Instance;
@@ -690,53 +676,150 @@ namespace MQTTnet.Client
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{
var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendAsync(pubCompPacket, cancellationToken);
return Send(pubCompPacket, cancellationToken);
}
async Task<MqttClientPublishResult> PublishAtLeastOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
async Task<MqttClientPublishResult> PublishAtLeastOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var pubAckPacket = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(pubAckPacket);
var pubAckPacket = await Request<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
return MqttClientResultFactory.PublishResult.Create(pubAckPacket);
}
async Task<MqttClientPublishResult> PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
await Send(publishPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(null);
return MqttClientResultFactory.PublishResult.Create(null);
}
async Task<MqttClientPublishResult> PublishExactlyOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
async Task<MqttClientPublishResult> PublishExactlyOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
var pubRecPacket = await Request<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
var pubCompPacket = await Request<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
return _clientPublishResultFactory.Create(pubRecPacket, pubCompPacket);
return MqttClientResultFactory.PublishResult.Create(pubRecPacket, pubCompPacket);
}
async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
async Task<MqttPacket> Receive(CancellationToken cancellationToken)
{
var packetTask = _channelAdapter.ReceivePacketAsync(cancellationToken);
MqttPacket packet;
if (packetTask.IsCompleted)
{
packet = packetTask.Result;
}
else
{
packet = await packetTask.ConfigureAwait(false);
}
return packet;
}
async Task ReceivePacketsLoop(CancellationToken cancellationToken)
{
try
{
_logger.Verbose("Start receiving packets.");
while (!cancellationToken.IsCancellationRequested)
{
var packet = await Receive(cancellationToken).ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (packet == null)
{
await DisconnectInternal(_packetReceiverTask, null, null).ConfigureAwait(false);
return;
}
await TryProcessReceivedPacket(packet, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
if (_cleanDisconnectInitiated)
{
return;
}
if (exception is AggregateException aggregateException)
{
exception = aggregateException.GetBaseException();
}
if (exception is OperationCanceledException)
{
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while receiving packets.");
}
else
{
_logger.Error(exception, "Error while receiving packets.");
}
_packetDispatcher.FailAll(exception);
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped receiving packets.");
}
}
internal async Task<TPacket> Receive<TPacket>(int packetIdentifier, CancellationToken cancellationToken) where TPacket : MqttPacket
{
using (var packetAwaitable = _packetDispatcher.AddAwaitable<TPacket>(packetIdentifier))
{
return await packetAwaitable.WaitOneAsync(cancellationToken);
}
}
internal async Task<TResponsePacket> Request<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
{
cancellationToken.ThrowIfCancellationRequested();
ushort packetIdentifier = 0;
// Some packets are a direct response to another packet but have no packet identifier for correlation!
int packetIdentifier;
if (requestPacket is MqttPacketWithIdentifier packetWithIdentifier)
{
packetIdentifier = packetWithIdentifier.PacketIdentifier;
}
else if (requestPacket is MqttPingReqPacket)
{
packetIdentifier = MqttPacketDispatcher.PingRespPacketFakeIdentifier;
}
else if (requestPacket is MqttAuthPacket)
{
packetIdentifier = MqttPacketDispatcher.AuthPacketFakeIdentifier;
}
else
{
throw new MqttProtocolViolationException($"Expecting a response for packet {requestPacket.GetRfcName()} is not supported.");
}
using (var packetAwaitable = _packetDispatcher.AddAwaitable<TResponsePacket>(packetIdentifier))
{
try
{
await SendAsync(requestPacket, cancellationToken).ConfigureAwait(false);
await Send(requestPacket, cancellationToken).ConfigureAwait(false);
}
catch (Exception exception)
{
@@ -760,15 +843,6 @@ namespace MQTTnet.Client
}
}
Task SendAsync(MqttPacket packet, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
_lastPacketSentTimestamp = DateTime.UtcNow;
return _adapter.SendPacketAsync(packet, cancellationToken);
}
void ThrowIfConnected(string message)
{
if (IsConnected)
@@ -814,7 +888,7 @@ namespace MQTTnet.Client
{
try
{
_mqttClientAlive?.Cancel(false);
_clientAlive?.Cancel(false);
}
catch (Exception exception)
{
@@ -823,7 +897,7 @@ namespace MQTTnet.Client
}
}
async Task TryProcessReceivedPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
async Task TryProcessReceivedPacket(MqttPacket packet, CancellationToken cancellationToken)
{
try
{
@@ -843,23 +917,31 @@ namespace MQTTnet.Client
{
await ProcessReceivedDisconnectPacket(disconnectPacket).ConfigureAwait(false);
}
else if (packet is MqttAuthPacket authPacket)
{
await ProcessReceivedAuthPacket(authPacket).ConfigureAwait(false);
}
// else if (packet is MqttAuthPacket authPacket)
// {
// await _authenticationHandler.HandleExtendedAuthentication(authPacket, new MqttInitialAuthenticationStrategy(_channelAdapter)).ConfigureAwait(false);
// }
else if (packet is MqttPingRespPacket)
{
_packetDispatcher.TryDispatch(packet);
}
else if (packet is MqttPingReqPacket)
{
throw new MqttProtocolViolationException("The PINGREQ Packet is sent from a Client to the Server only.");
throw new MqttProtocolViolationException("The PINGREQ packet can only be sent from client to server.");
}
else if (packet is MqttConnAckPacket)
{
throw new MqttProtocolViolationException("The CONNACK packet can only be send while connecting.");
}
else if (packet is MqttConnectPacket)
{
throw new MqttProtocolViolationException("The CONNECT packet can only be sent from client to server.");
}
else
{
if (!_packetDispatcher.TryDispatch(packet))
{
throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
throw new MqttProtocolViolationException($"Received unexpected {packet} packet.");
}
}
}
@@ -884,133 +966,7 @@ namespace MQTTnet.Client
_packetDispatcher.FailAll(exception);
await DisconnectInternalAsync(_packetReceiverTask, exception, null).ConfigureAwait(false);
}
}
async Task TryReceivePacketsAsync(CancellationToken cancellationToken)
{
try
{
_logger.Verbose("Start receiving packets.");
while (!cancellationToken.IsCancellationRequested)
{
MqttPacket packet;
var packetTask = _adapter.ReceivePacketAsync(cancellationToken);
if (packetTask.IsCompleted)
{
packet = packetTask.Result;
}
else
{
packet = await packetTask.ConfigureAwait(false);
}
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (packet == null)
{
await DisconnectInternalAsync(_packetReceiverTask, null, null).ConfigureAwait(false);
return;
}
await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
if (_cleanDisconnectInitiated)
{
return;
}
if (exception is AggregateException aggregateException)
{
exception = aggregateException.GetBaseException();
}
if (exception is OperationCanceledException)
{
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while receiving packets.");
}
else
{
_logger.Error(exception, "Error while receiving packets.");
}
_packetDispatcher.FailAll(exception);
await DisconnectInternalAsync(_packetReceiverTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped receiving packets.");
}
}
async Task TrySendKeepAliveMessagesAsync(CancellationToken cancellationToken)
{
try
{
_logger.Verbose("Start sending keep alive packets.");
var keepAlivePeriod = Options.KeepAlivePeriod;
while (!cancellationToken.IsCancellationRequested)
{
// Values described here: [MQTT-3.1.2-24].
var timeWithoutPacketSent = DateTime.UtcNow - _lastPacketSentTimestamp;
if (timeWithoutPacketSent > keepAlivePeriod)
{
using (var timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
timeoutCancellationTokenSource.CancelAfter(Options.Timeout);
await PingAsync(timeoutCancellationTokenSource.Token).ConfigureAwait(false);
}
}
// Wait a fixed time in all cases. Calculation of the remaining time is complicated
// due to some edge cases and was buggy in the past. Now we wait several ms because the
// min keep alive value is one second so that the server will wait 1.5 seconds for a PING
// packet.
await Task.Delay(250, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception exception)
{
if (_cleanDisconnectInitiated)
{
return;
}
if (exception is OperationCanceledException)
{
return;
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Communication error while sending/receiving keep alive packets.");
}
else
{
_logger.Error(exception, "Error exception while sending/receiving keep alive packets.");
}
await DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
}
finally
{
_logger.Verbose("Stopped sending keep alive packets.");
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
}
}
}
+7 -10
View File
@@ -14,6 +14,8 @@ namespace MQTTnet.Client
{
public static class MqttClientExtensions
{
static readonly MqttReAuthenticationOptions EmptyReAuthenticationOptions = new MqttReAuthenticationOptions();
public static Task DisconnectAsync(
this IMqttClient client,
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
@@ -77,6 +79,11 @@ namespace MQTTnet.Client
return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken);
}
public static Task ReAuthenticateAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{
return client.ReAuthenticateAsync(EmptyReAuthenticationOptions, cancellationToken);
}
public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{
if (client.Options == null)
@@ -88,16 +95,6 @@ namespace MQTTnet.Client
return client.ConnectAsync(client.Options, cancellationToken);
}
public static Task SendExtendedAuthenticationExchangeDataAsync(this IMqttClient client, MqttExtendedAuthenticationExchangeData data)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}
return client.SendExtendedAuthenticationExchangeDataAsync(data, CancellationToken.None);
}
public static Task<MqttClientSubscribeResult> SubscribeAsync(this IMqttClient mqttClient, MqttTopicFilter topicFilter, CancellationToken cancellationToken = default)
{
if (mqttClient == null)
@@ -12,6 +12,14 @@ namespace MQTTnet.Client
{
public sealed class MqttClientOptions
{
/// <summary>
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
/// will close the connection when receiving such packets. If such a service is used this flag must
/// be set to _false_.
/// </summary>
public bool AllowPacketFragmentation { get; set; } = true;
/// <summary>
/// Gets or sets the authentication data.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
@@ -24,14 +32,6 @@ namespace MQTTnet.Client
/// </summary>
public string AuthenticationMethod { 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 brokers (like Amazon Web Services (AWS)) do support this feature and
/// will close the connection when receiving such packets. If such a service is used this flag must
/// be set to _false_.
/// </summary>
public bool AllowPacketFragmentation { get; set; } = true;
public IMqttClientChannelOptions ChannelOptions { get; set; }
/// <summary>
@@ -53,9 +53,7 @@ namespace MQTTnet.Client
public string ClientId { get; set; } = Guid.NewGuid().ToString("N");
public IMqttClientCredentialsProvider Credentials { get; set; }
public IMqttExtendedAuthenticationExchangeHandler ExtendedAuthenticationExchangeHandler { get; set; }
/// <summary>
/// Gets or sets the keep alive period.
/// The connection is normally left open by the client so that is can send and receive data at any time.
@@ -100,6 +98,11 @@ namespace MQTTnet.Client
/// </summary>
public uint SessionExpiryInterval { get; set; }
/// <summary>
/// Gets or sets whether an exception should be thrown when the server has sent a non success ACK packet.
/// </summary>
public bool ThrowOnNonSuccessfulResponseFromServer { get; set; } = true;
/// <summary>
/// Gets or sets the timeout which will be applied at socket level and internal operations.
/// The default value is the same as for sockets in .NET in general.
@@ -222,4 +225,4 @@ namespace MQTTnet.Client
/// </summary>
public int WriterBufferSizeMax { get; set; } = 65535;
}
}
}
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
@@ -79,7 +79,7 @@ namespace MQTTnet.Client
return _options;
}
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data = null)
{
_options.AuthenticationMethod = method;
_options.AuthenticationData = data;
@@ -177,12 +177,6 @@ namespace MQTTnet.Client
return this;
}
public MqttClientOptionsBuilder WithExtendedAuthenticationExchangeHandler(IMqttExtendedAuthenticationExchangeHandler handler)
{
_options.ExtendedAuthenticationExchangeHandler = handler;
return this;
}
public MqttClientOptionsBuilder WithKeepAlivePeriod(TimeSpan value)
{
_options.KeepAlivePeriod = value;
@@ -421,6 +415,18 @@ namespace MQTTnet.Client
return this;
}
public MqttClientOptionsBuilder WithWillPayload(ArraySegment<byte> willPayload)
{
if (willPayload.Count == 0)
{
_options.WillPayload = null;
return this;
}
_options.WillPayload = willPayload.ToArray();
return this;
}
public MqttClientOptionsBuilder WithWillPayload(string willPayload)
{
if (string.IsNullOrEmpty(willPayload))
@@ -34,7 +34,7 @@ namespace MQTTnet.Client
/// Gets or sets the reason code.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttClientPublishReasonCode ReasonCode { get; } = MqttClientPublishReasonCode.Success;
public MqttClientPublishReasonCode ReasonCode { get; }
/// <summary>
/// Gets or sets the reason string.
@@ -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
};
}
}
}
+31 -22
View File
@@ -19,7 +19,8 @@ namespace MQTTnet.Formatter
/// </summary>
public sealed class MqttBufferWriter
{
public const uint VariableByteIntegerMaxValue = 268435455;
const uint VariableByteIntegerMaxValue = 268435455;
const int EncodedStringMaxLength = 65535;
readonly int _maxBufferSize;
@@ -112,25 +113,7 @@ namespace MQTTnet.Formatter
WriteBinary(propertyWriter._buffer, 0, propertyWriter.Length);
}
public void WriteBinary(byte[] buffer, int offset, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (count == 0)
{
return;
}
EnsureAdditionalCapacity(count);
MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count);
IncreasePosition(count);
}
public void WriteBinaryData(byte[] value)
public void WriteBinary(byte[] value)
{
if (value == null || value.Length == 0)
{
@@ -155,6 +138,24 @@ namespace MQTTnet.Formatter
}
}
public void WriteBinary(byte[] buffer, int offset, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (count == 0)
{
return;
}
EnsureAdditionalCapacity(count);
MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count);
IncreasePosition(count);
}
public void WriteByte(byte @byte)
{
EnsureAdditionalCapacity(1);
@@ -186,6 +187,13 @@ namespace MQTTnet.Formatter
var writtenBytes = Encoding.UTF8.GetBytes(value, 0, value.Length, _buffer, _position + 2);
// From RFC: 1.5.4 UTF-8 Encoded String
// Unless stated otherwise all UTF-8 encoded strings can have any length in the range 0 to 65,535 bytes.
if (writtenBytes > EncodedStringMaxLength)
{
throw new MqttProtocolViolationException($"The maximum string length is 65535. The current string has a length of {writtenBytes}.");
}
_buffer[_position] = (byte)(writtenBytes >> 8);
_buffer[_position + 1] = (byte)writtenBytes;
@@ -273,8 +281,7 @@ namespace MQTTnet.Formatter
newBufferLength *= 2;
}
// Array.Resize will create a new array and copy the existing
// data to the new one.
// Array.Resize will create a new array and copy the existing data to the new one.
Array.Resize(ref _buffer, newBufferLength);
}
@@ -285,6 +292,8 @@ namespace MQTTnet.Formatter
if (_position > Length)
{
// Also extend the position because we reached the end of the
// pre allocated buffer.
Length = _position;
}
}
@@ -59,6 +59,8 @@ namespace MQTTnet.Formatter
return MqttConnectReturnCode.ConnectionAccepted;
}
case MqttConnectReasonCode.BadAuthenticationMethod:
case MqttConnectReasonCode.Banned:
case MqttConnectReasonCode.NotAuthorized:
{
return MqttConnectReturnCode.ConnectionRefusedNotAuthorized;
@@ -73,12 +75,13 @@ namespace MQTTnet.Formatter
{
return MqttConnectReturnCode.ConnectionRefusedIdentifierRejected;
}
case MqttConnectReasonCode.UnsupportedProtocolVersion:
{
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
}
case MqttConnectReasonCode.UseAnotherServer:
case MqttConnectReasonCode.ServerUnavailable:
case MqttConnectReasonCode.ServerBusy:
case MqttConnectReasonCode.ServerMoved:
@@ -88,7 +91,8 @@ namespace MQTTnet.Formatter
default:
{
throw new MqttProtocolViolationException("Unable to convert connect reason code (MQTTv5) to return code (MQTTv3).");
// This is the most best matching value.
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
}
}
}
@@ -6,6 +6,8 @@ namespace MQTTnet.Formatter
{
public static class MqttPacketFactories
{
public static MqttAuthPacketFactory Auth { get; } = new MqttAuthPacketFactory();
public static MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
public static MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
@@ -31,10 +31,14 @@ namespace MQTTnet.Formatter
public MqttProtocolVersion ProtocolVersion { get; private set; } = MqttProtocolVersion.Unknown;
public void Cleanup()
{
_bufferWriter.Cleanup();
}
public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket)
{
ThrowIfFormatterNotSet();
return _formatter.Decode(receivedMqttPacket);
}
@@ -50,11 +54,6 @@ namespace MQTTnet.Formatter
return _formatter.Encode(packet);
}
public void Cleanup()
{
_bufferWriter.Cleanup();
}
public static IMqttPacketFormatter GetMqttPacketFormatter(MqttProtocolVersion protocolVersion, MqttBufferWriter bufferWriter)
{
if (protocolVersion == MqttProtocolVersion.Unknown)
@@ -98,7 +97,7 @@ namespace MQTTnet.Formatter
// Remove the mosquitto try_private flag (MQTT 3.1.1 Bridge).
// This flag is accepted but not yet used.
protocolLevel &= 0x7F;
if (protocolName == "MQTT")
{
if (protocolLevel == 5)
@@ -459,7 +459,7 @@ namespace MQTTnet.Formatter.V3
if (packet.WillFlag)
{
bufferWriter.WriteString(packet.WillTopic);
bufferWriter.WriteBinaryData(packet.WillMessage);
bufferWriter.WriteBinary(packet.WillMessage);
}
if (packet.Username != null)
@@ -469,7 +469,7 @@ namespace MQTTnet.Formatter.V3
if (packet.Password != null)
{
bufferWriter.WriteBinaryData(packet.Password);
bufferWriter.WriteBinary(packet.Password);
}
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
@@ -521,7 +521,7 @@ namespace MQTTnet.Formatter.V3
if (packet.WillFlag)
{
bufferWriter.WriteString(packet.WillTopic);
bufferWriter.WriteBinaryData(packet.WillMessage);
bufferWriter.WriteBinary(packet.WillMessage);
}
if (packet.Username != null)
@@ -531,7 +531,7 @@ namespace MQTTnet.Formatter.V3
if (packet.Password != null)
{
bufferWriter.WriteBinaryData(packet.Password);
bufferWriter.WriteBinary(packet.Password);
}
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
@@ -186,7 +186,7 @@ namespace MQTTnet.Formatter.V5
_propertiesWriter.Reset();
_bufferWriter.WriteString(packet.WillTopic);
_bufferWriter.WriteBinaryData(packet.WillMessage);
_bufferWriter.WriteBinary(packet.WillMessage);
}
if (packet.Username != null)
@@ -196,7 +196,7 @@ namespace MQTTnet.Formatter.V5
if (packet.Password != null)
{
_bufferWriter.WriteBinaryData(packet.Password);
_bufferWriter.WriteBinary(packet.Password);
}
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Connect);
@@ -335,7 +335,7 @@ namespace MQTTnet.Formatter.V5
}
_bufferWriter.WriteByte((byte)id);
_bufferWriter.WriteBinaryData(value);
_bufferWriter.WriteBinary(value);
}
void WriteAsFourByteInteger(MqttPropertyId id, uint value)
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cauthentication/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cconnecting/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdiagnostics/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdisconnecting/@EntryIndexedValue">True</s:Boolean>
+22 -22
View File
@@ -2,16 +2,16 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet
{
@@ -20,9 +20,9 @@ namespace MQTTnet
string _contentType;
byte[] _correlationData;
uint _messageExpiryInterval;
ArraySegment<byte> _payloadSegment;
MqttPayloadFormatIndicator _payloadFormatIndicator;
ArraySegment<byte> _payloadSegment;
MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce;
string _responseTopic;
bool _retain;
@@ -89,13 +89,11 @@ namespace MQTTnet
public MqttApplicationMessageBuilder WithPayload(byte[] payload)
{
_payloadSegment = payload == null || payload.Length == 0
? EmptyBuffer.ArraySegment
: new ArraySegment<byte>(payload);
_payloadSegment = payload == null || payload.Length == 0 ? EmptyBuffer.ArraySegment : new ArraySegment<byte>(payload);
return this;
}
public MqttApplicationMessageBuilder WithPayloadSegment(ArraySegment<byte> payloadSegment)
public MqttApplicationMessageBuilder WithPayload(ArraySegment<byte> payloadSegment)
{
_payloadSegment = payloadSegment;
return this;
@@ -123,9 +121,7 @@ namespace MQTTnet
public MqttApplicationMessageBuilder WithPayload(Stream payload)
{
return payload == null
? WithPayload(default(byte[]))
: WithPayload(payload, payload.Length - payload.Position);
return payload == null ? WithPayload(default(byte[])) : WithPayload(payload, payload.Length - payload.Position);
}
public MqttApplicationMessageBuilder WithPayload(Stream payload, long length)
@@ -162,16 +158,6 @@ namespace MQTTnet
return WithPayload(payloadBuffer);
}
#if NETCOREAPP3_1_OR_GREATER || NETSTANDARD2_1
public MqttApplicationMessageBuilder WithPayloadSegment(ReadOnlyMemory<byte> payloadSegment)
{
return MemoryMarshal.TryGetArray(payloadSegment, out var segment)
? WithPayloadSegment(segment)
: WithPayload(payloadSegment.ToArray());
}
#endif
/// <summary>
/// Adds the payload format indicator to the message.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
@@ -182,6 +168,20 @@ namespace MQTTnet
return this;
}
public MqttApplicationMessageBuilder WithPayloadSegment(ArraySegment<byte> payloadSegment)
{
_payloadSegment = payloadSegment;
return this;
}
#if NETCOREAPP3_1_OR_GREATER || NETSTANDARD2_1
public MqttApplicationMessageBuilder WithPayloadSegment(ReadOnlyMemory<byte> payloadSegment)
{
return MemoryMarshal.TryGetArray(payloadSegment, out var segment) ? WithPayloadSegment(segment) : WithPayload(payloadSegment.ToArray());
}
#endif
/// <summary>
/// The quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
@@ -9,7 +9,7 @@ namespace MQTTnet.PacketDispatcher
{
public interface IMqttPacketAwaitable : IDisposable
{
MqttPacketAwaitableFilter Filter { get; }
int PacketIdentifier { get; }
void Complete(MqttPacket packet);
@@ -16,18 +16,13 @@ namespace MQTTnet.PacketDispatcher
readonly AsyncTaskCompletionSource<MqttPacket> _promise = new AsyncTaskCompletionSource<MqttPacket>();
readonly MqttPacketDispatcher _owningPacketDispatcher;
public MqttPacketAwaitable(ushort packetIdentifier, MqttPacketDispatcher owningPacketDispatcher)
public MqttPacketAwaitable(int packetPacketIdentifier, MqttPacketDispatcher owningPacketDispatcher)
{
Filter = new MqttPacketAwaitableFilter
{
Type = typeof(TPacket),
Identifier = packetIdentifier
};
PacketIdentifier = packetPacketIdentifier;
_owningPacketDispatcher = owningPacketDispatcher ?? throw new ArgumentNullException(nameof(owningPacketDispatcher));
}
public MqttPacketAwaitableFilter Filter { get; }
public int PacketIdentifier { get; }
public async Task<TPacket> WaitOneAsync(CancellationToken cancellationToken)
{
@@ -10,11 +10,15 @@ namespace MQTTnet.PacketDispatcher
{
public sealed class MqttPacketDispatcher : IDisposable
{
public const int PingRespPacketFakeIdentifier = 2147483647;
public const int AuthPacketFakeIdentifier = 2147483646;
public const int SuccessAuthPacketFakeIdentifier = 2147483647;
readonly List<IMqttPacketAwaitable> _waiters = new List<IMqttPacketAwaitable>();
bool _isDisposed;
public MqttPacketAwaitable<TResponsePacket> AddAwaitable<TResponsePacket>(ushort packetIdentifier) where TResponsePacket : MqttPacket
public MqttPacketAwaitable<TResponsePacket> AddAwaitable<TResponsePacket>(int packetIdentifier) where TResponsePacket : MqttPacket
{
var awaitable = new MqttPacketAwaitable<TResponsePacket>(packetIdentifier, this);
@@ -99,15 +103,21 @@ namespace MQTTnet.PacketDispatcher
throw new ArgumentNullException(nameof(packet));
}
ushort identifier = 0;
var identifier = 0;
if (packet is MqttPacketWithIdentifier packetWithIdentifier)
{
identifier = packetWithIdentifier.PacketIdentifier;
}
var packetType = packet.GetType();
else if (packet is MqttPingRespPacket)
{
identifier = PingRespPacketFakeIdentifier;
}
else
{
throw new InvalidOperationException($"Cannot dispatch {packet.GetRfcName()} packet.");
}
var waiters = new List<IMqttPacketAwaitable>();
lock (_waiters)
{
ThrowIfDisposed();
@@ -115,11 +125,8 @@ namespace MQTTnet.PacketDispatcher
for (var i = _waiters.Count - 1; i >= 0; i--)
{
var entry = _waiters[i];
// Note: The PingRespPacket will also arrive here and has NO identifier but there
// is code which waits for it. So the code must be able to deal with filters which
// are referring to the type only (identifier is 0)!
if (entry.Filter.Type != packetType || entry.Filter.Identifier != identifier)
if (entry.PacketIdentifier != identifier)
{
continue;
}
+6 -3
View File
@@ -28,7 +28,7 @@ namespace MQTTnet.Packets
public MqttQualityOfServiceLevel MaximumQoS { get; set; }
/// <summary>
/// Added in MQTTv5.
/// The return code is only used in MQTTv 5.0.0 and higher.
/// </summary>
public MqttConnectReasonCode ReasonCode { get; set; }
@@ -39,13 +39,16 @@ namespace MQTTnet.Packets
public string ResponseInformation { get; set; }
public bool RetainAvailable { get; set; }
/// <summary>
/// The return code is only used in MQTTv 3.1.1 and lower.
/// </summary>
public MqttConnectReturnCode ReturnCode { get; set; }
public ushort ServerKeepAlive { get; set; }
public string ServerReference { get; set; }
public uint SessionExpiryInterval { get; set; }
public bool SharedSubscriptionAvailable { get; set; }
@@ -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,12 +4,10 @@
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.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
@@ -19,11 +22,12 @@ namespace MQTTnet.Server
{
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));
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
CancellationToken = cancellationToken;
}
/// <summary>
@@ -44,6 +48,11 @@ namespace MQTTnet.Server
/// </summary>
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>
/// Gets the channel adapter. This can be a _MqttConnectionContext_ (used in ASP.NET), a _MqttChannelAdapter_ (used for
/// 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.
/// </summary>
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);
}
}
}
+24 -3
View File
@@ -95,8 +95,9 @@ namespace MQTTnet.Server
{
var cancellationToken = _cancellationToken.Token;
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);
}
@@ -176,6 +177,22 @@ namespace MQTTnet.Server
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()
{
// See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
@@ -436,9 +453,13 @@ namespace MQTTnet.Server
DisconnectPacket = disconnectPacket;
return;
}
else if (currentPacket is MqttAuthPacket authPacket)
{
await HandleIncomingAuthPacket(authPacket);
}
else
{
throw new MqttProtocolViolationException("Packet not allowed");
throw new MqttProtocolViolationException("Packet not allowed.");
}
}
}
@@ -36,7 +36,7 @@ namespace MQTTnet.Server
// See the MqttSubscription object for a detailed explanation.
readonly Dictionary<string, MqttSession> _sessions = new Dictionary<string, MqttSession>(4096);
readonly object _sessionsManagementLock = new object();
readonly ReaderWriterLockSlim _sessionsManagementLock = new ReaderWriterLockSlim();
readonly HashSet<MqttSession> _subscriberSessions = new HashSet<MqttSession>();
public MqttClientSessionsManager(
@@ -86,7 +86,8 @@ namespace MQTTnet.Server
MqttSession session;
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterWriteLock();
try
{
_sessions.TryGetValue(clientId, out session);
_sessions.Remove(clientId);
@@ -96,6 +97,10 @@ namespace MQTTnet.Server
_subscriberSessions.Remove(session);
}
}
finally
{
_sessionsManagementLock.ExitWriteLock();
}
try
{
@@ -173,10 +178,15 @@ namespace MQTTnet.Server
}
List<MqttSession> subscriberSessions;
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterReadLock();
try
{
subscriberSessions = _subscriberSessions.ToList();
}
finally
{
_sessionsManagementLock.ExitReadLock();
}
// Calculate application message topic hash once for subscription checks
MqttTopicHash.Calculate(applicationMessage.Topic, out var topicHash, out _, out _);
@@ -256,13 +266,23 @@ namespace MQTTnet.Server
{
_createConnectionSyncRoot.Dispose();
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterWriteLock();
try
{
foreach (var sessionItem in _sessions)
{
sessionItem.Value.Dispose();
}
}
finally
{
_sessionsManagementLock.ExitWriteLock();
}
if (_sessionsManagementLock != null)
{
_sessionsManagementLock.Dispose();
}
}
public MqttClient GetClient(string id)
@@ -310,7 +330,8 @@ namespace MQTTnet.Server
{
var result = new List<MqttSessionStatus>();
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterReadLock();
try
{
foreach (var sessionItem in _sessions)
{
@@ -318,6 +339,10 @@ namespace MQTTnet.Server
result.Add(sessionStatus);
}
}
finally
{
_sessionsManagementLock.ExitReadLock();
}
return Task.FromResult((IList<MqttSessionStatus>)result);
}
@@ -335,7 +360,7 @@ namespace MQTTnet.Server
return;
}
var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter, cancellationToken).ConfigureAwait(false);
var connAckPacket = MqttPacketFactories.ConnAck.Create(validatingConnectionEventArgs);
if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success)
@@ -372,7 +397,7 @@ namespace MQTTnet.Server
}
catch (Exception exception)
{
_logger.Error(exception, exception.Message);
_logger.Error(exception, $"Error while validating client {channelAdapter.Endpoint}. {exception.Message}");
}
finally
{
@@ -415,7 +440,8 @@ namespace MQTTnet.Server
public void OnSubscriptionsAdded(MqttSession clientSession, List<string> topics)
{
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterWriteLock();
try
{
if (!clientSession.HasSubscribedTopics)
{
@@ -428,11 +454,16 @@ namespace MQTTnet.Server
clientSession.AddSubscribedTopic(topic);
}
}
finally
{
_sessionsManagementLock.ExitWriteLock();
}
}
public void OnSubscriptionsRemoved(MqttSession clientSession, List<string> subscriptionTopics)
{
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterWriteLock();
try
{
foreach (var subscriptionTopic in subscriptionTopics)
{
@@ -445,6 +476,10 @@ namespace MQTTnet.Server
_subscriberSessions.Remove(clientSession);
}
}
finally
{
_sessionsManagementLock.ExitWriteLock();
}
}
public void Start()
@@ -545,7 +580,8 @@ namespace MQTTnet.Server
MqttSession oldSession;
MqttClient oldClient;
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterWriteLock();
try
{
MqttSession session;
@@ -590,6 +626,10 @@ namespace MQTTnet.Server
_clients[connectPacket.ClientId] = client;
}
finally
{
_sessionsManagementLock.ExitWriteLock();
}
if (!connAckPacket.IsSessionPresent)
{
@@ -636,7 +676,8 @@ namespace MQTTnet.Server
MqttSession GetClientSession(string clientId)
{
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterReadLock();
try
{
if (!_sessions.TryGetValue(clientId, out var session))
{
@@ -645,6 +686,10 @@ namespace MQTTnet.Server
return session;
}
finally
{
_sessionsManagementLock.ExitReadLock();
}
}
async Task<MqttConnectPacket> ReceiveConnectPacket(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
@@ -674,11 +719,11 @@ namespace MQTTnet.Server
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.
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);
// Check the client ID and set a random one if supported.
@@ -695,4 +740,4 @@ namespace MQTTnet.Server
return eventArgs;
}
}
}
}
@@ -31,7 +31,7 @@ namespace MQTTnet.Server
readonly Dictionary<string, MqttSubscription> _subscriptions = new Dictionary<string, MqttSubscription>();
// Use subscription lock to maintain consistency across subscriptions and topic hash dictionaries
readonly AsyncLock _subscriptionsLock = new AsyncLock();
readonly ReaderWriterLockSlim _subscriptionsLock = new ReaderWriterLockSlim();
readonly Dictionary<ulong, TopicHashMaskSubscriptions> _wildcardSubscriptionsByTopicHash = new Dictionary<ulong, TopicHashMaskSubscriptions>();
public MqttClientSubscriptionsManager(
@@ -51,7 +51,8 @@ namespace MQTTnet.Server
var possibleSubscriptions = new List<MqttSubscription>();
// Check for possible subscriptions. They might have collisions but this is fine.
using (_subscriptionsLock.EnterAsync(CancellationToken.None).GetAwaiter().GetResult())
_subscriptionsLock.EnterReadLock();
try
{
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions))
{
@@ -73,6 +74,10 @@ namespace MQTTnet.Server
}
}
}
finally
{
_subscriptionsLock.ExitReadLock();
}
// The pre check has evaluated that nothing is subscribed.
// If there were some possible candidates they get checked below
@@ -157,7 +162,10 @@ namespace MQTTnet.Server
public void Dispose()
{
_subscriptionsLock.Dispose();
if (_subscriptionsLock != null)
{
_subscriptionsLock.Dispose();
}
}
public async Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
@@ -232,7 +240,8 @@ namespace MQTTnet.Server
var removedSubscriptions = new List<string>();
using (await _subscriptionsLock.EnterAsync(cancellationToken).ConfigureAwait(false))
_subscriptionsLock.EnterWriteLock();
try
{
foreach (var topicFilter in unsubscribePacket.TopicFilters)
{
@@ -293,8 +302,11 @@ namespace MQTTnet.Server
}
}
}
_subscriptionChangedNotification?.OnSubscriptionsRemoved(_session, removedSubscriptions);
finally
{
_subscriptionsLock.ExitWriteLock();
_subscriptionChangedNotification?.OnSubscriptionsRemoved(_session, removedSubscriptions);
}
if (_eventContainer.ClientUnsubscribedTopicEvent.HasHandlers)
{
@@ -341,7 +353,8 @@ namespace MQTTnet.Server
// Add to subscriptions and maintain topic hash dictionaries
using (_subscriptionsLock.EnterAsync(CancellationToken.None).GetAwaiter().GetResult())
_subscriptionsLock.EnterWriteLock();
try
{
MqttTopicHash.Calculate(topicFilter.Topic, out var topicHash, out _, out var hasWildcard);
@@ -391,6 +404,10 @@ namespace MQTTnet.Server
subscriptions.Add(subscription);
}
}
finally
{
_subscriptionsLock.ExitWriteLock();
}
return new CreateSubscriptionResult
{
@@ -511,4 +528,4 @@ namespace MQTTnet.Server
public MqttSubscription Subscription { get; set; }
}
}
}
}
@@ -17,11 +17,14 @@ namespace MQTTnet.Server
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<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>();
+13 -7
View File
@@ -73,6 +73,12 @@ namespace MQTTnet.Server
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
{
add => _eventContainer.ClientSubscribedTopicEvent.AddHandler(value);
@@ -203,13 +209,6 @@ namespace MQTTnet.Server
return _clientSessionsManager.GetClientStatusesAsync();
}
public Task<IList<MqttApplicationMessage>> GetRetainedMessagesAsync()
{
ThrowIfNotStarted();
return _retainedMessagesManager.GetMessages();
}
public Task<MqttApplicationMessage> GetRetainedMessageAsync(string topic)
{
if (topic == null)
@@ -222,6 +221,13 @@ namespace MQTTnet.Server
return _retainedMessagesManager.GetMessage(topic);
}
public Task<IList<MqttApplicationMessage>> GetRetainedMessagesAsync()
{
ThrowIfNotStarted();
return _retainedMessagesManager.GetMessages();
}
public Task<IList<MqttSessionStatus>> GetSessionsAsync()
{
ThrowIfNotStarted();