Compare commits

...
Author SHA1 Message Date
Christian be0c75439c Add Unit Tests 2023-07-29 09:48:07 +02:00
Christian 9295626503 [Server] Add initial support for session expiration (#1776) 2023-06-27 19:32:08 +02:00
Christian 18614d4e81 Expose configuration of packet fragmentation in server options. (#1769) 2023-06-15 18:51:43 +02:00
Christian 560e80455f Update samples (#1768) 2023-06-15 18:32:28 +02:00
HansM a97e88be29 Update Client_Subscribe_Samples.cs (#1766)
Fixes https://github.com/dotnet/MQTTnet/issues/1765.
2023-06-15 17:31:01 +02:00
logicaloudandChristian 3b5667c882 Fix #1761: MQTTRetainedMessageManager, return task with NULL result instead of NULL (#1762)
* Return task with NULL result instead of NULL

* move Task.FromResult call out of lock

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-06-12 21:52:54 +02:00
Nicke BroströmandChristian 0790e54794 Websockets no longer working in Blazor WebAssembly (#1755)
* Fix for websockets not applying properties unless explicitly set

* Added comment indicating why properties can't be set

* Update ReleaseNotes.md

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2023-06-12 21:48:27 +02:00
Christian 94f9fac2bd Update ReleaseNotes.md 2023-05-29 16:48:50 +02:00
Christian 8ad86928f1 1681 misleading signature of mqttclientconnectasync mqttclientconnectresultcode expected but throws exception (#1749) 2023-05-29 16:29:16 +02:00
Christian d8e5936d45 1724 nullreferenceexception on mqttclientconnectasync (#1748)
Update MqttClient.cs
2023-05-29 13:55:36 +02:00
Christian caa717544b 1742 some bug in mqttclientconnectionvalidatorhandle (#1746) 2023-05-29 13:53:42 +02:00
Christian 66b77f8d6d Merge branch 'master' of https://github.com/dotnet/MQTTnet 2023-05-29 13:47:09 +02:00
Christian ab5264c8ca 1738 userproperties in clientconnectedeventargs (#1747) 2023-05-29 13:45:44 +02:00
Christian db12309bae Update ReleaseNotes.md 2023-05-29 12:41:28 +02:00
Christian 2953e0dcc5 Improve payload to string conversion 2023-05-29 12:41:24 +02:00
Ramon Smits 1b18ac9fe3 Resolve ConvertPayloadToString resulting in ArgumentNullException (#1745)
Resolve breaking change where previously ConvertPayloadToString returned null if the payload was null
2023-05-29 12:38:33 +02:00
Christian 2cdad6622a Fix build 2023-05-14 09:14:37 +02:00
Andrei Kurosh 7601d11a7a UseDefaultCredentials options for WebSockets proxy configuration. (#1734) 2023-05-14 08:58:57 +02:00
Christian 0bb4c6ef09 Expose more TLS options (#1737) 2023-05-13 12:08:52 +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 4b7d035e9e Reset release notes 2023-04-29 12:37:36 +02:00
63 changed files with 1159 additions and 643 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).
* [Client] Fixed _PlatformNotSupportedException_ when using Blazor (#1755, thanks to @Nickztar).
* [Server] Fixed _NullReferenceException_ in retained messages management (#1762, thanks to @logicaloud).
* [Server] Exposed new option which allows disabling packet fragmentation (#1753).
* [Server] Expired sessions will no longer be used when a client connects (#1756).
+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:
+2 -1
View File
@@ -6,6 +6,7 @@
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Packets;
using MQTTnet.Protocol;
@@ -176,4 +177,4 @@ public static class Client_Subscribe_Samples
response.DumpToConsole();
}
}
}
}
@@ -7,6 +7,8 @@
// ReSharper disable InconsistentNaming
using System.Text.Json;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Samples.Server;
@@ -32,7 +34,10 @@ public static class Server_Retained_Messages_Samples
{
try
{
eventArgs.LoadedRetainedMessages = await JsonSerializer.DeserializeAsync<List<MqttApplicationMessage>>(File.OpenRead(storePath));
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
eventArgs.LoadedRetainedMessages = retainedMessages;
Console.WriteLine("Retained messages loaded.");
}
catch (FileNotFoundException)
@@ -56,7 +61,9 @@ public static class Server_Retained_Messages_Samples
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
// of retained messages must be provided in the loaded event.
var buffer = JsonSerializer.SerializeToUtf8Bytes(eventArgs.StoredRetainedMessages);
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
await File.WriteAllBytesAsync(storePath, buffer);
Console.WriteLine("Retained messages saved.");
}
@@ -79,4 +86,59 @@ public static class Server_Retained_Messages_Samples
Console.ReadLine();
}
}
sealed class MqttRetainedMessageModel
{
public string? ContentType { get; set; }
public byte[]? CorrelationData { get; set; }
public byte[]? Payload { get; set; }
public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public string? ResponseTopic { get; set; }
public string? Topic { get; set; }
public List<MqttUserProperty>? UserProperties { get; set; }
public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
{
if (message == null)
{
throw new ArgumentNullException(nameof(message));
}
return new MqttRetainedMessageModel
{
Topic = message.Topic,
// Create a copy of the buffer from the payload segment because
// it cannot be serialized and deserialized with the JSON serializer.
Payload = message.PayloadSegment.ToArray(),
UserProperties = message.UserProperties,
ResponseTopic = message.ResponseTopic,
CorrelationData = message.CorrelationData,
ContentType = message.ContentType,
PayloadFormatIndicator = message.PayloadFormatIndicator,
QualityOfServiceLevel = message.QualityOfServiceLevel
// Other properties like "Retain" are not if interest in the storage.
// That's why a custom model makes sense.
};
}
public MqttApplicationMessage ToApplicationMessage()
{
return new MqttApplicationMessage
{
Topic = Topic,
PayloadSegment = new ArraySegment<byte>(Payload ?? Array.Empty<byte>()),
PayloadFormatIndicator = PayloadFormatIndicator,
ResponseTopic = ResponseTopic,
CorrelationData = CorrelationData,
ContentType = ContentType,
UserProperties = UserProperties,
QualityOfServiceLevel = QualityOfServiceLevel,
Dup = false,
Retain = true
};
}
}
}
@@ -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);
}
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
@@ -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();
}
}
}
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
@@ -69,6 +69,11 @@ namespace MQTTnet.Extensions.Rpc
throw new ArgumentNullException(nameof(methodName));
}
if (!_mqttClient.IsConnected)
{
throw new MqttClientDisconnectedException(null);
}
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
@@ -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()
{
@@ -3,13 +3,16 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Extensions.Rpc;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
@@ -18,6 +21,38 @@ namespace MQTTnet.Tests.Clients.MqttClient
[TestClass]
public sealed class MqttClient_Connection_Tests : BaseTestClass
{
[TestMethod]
public async Task Connect_Disconnect_Connect()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var clientOptions = testEnvironment.CreateDefaultClientOptions();
var client = testEnvironment.CreateClient();
await client.ConnectAsync(clientOptions);
await client.DisconnectAsync();
await client.ConnectAsync(clientOptions);
}
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task Connect_Multiple_Times_Should_Fail()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var clientOptions = testEnvironment.CreateDefaultClientOptions();
var client = testEnvironment.CreateClient();
await client.ConnectAsync(clientOptions);
await client.ConnectAsync(clientOptions);
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
@@ -127,6 +162,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);
}
@@ -154,7 +191,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
await client.DisconnectAsync(disconnectOptions);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.IsNotNull(eventArgs.UserProperties);
Assert.AreEqual(1, eventArgs.UserProperties.Count);
@@ -162,5 +199,47 @@ namespace MQTTnet.Tests.Clients.MqttClient
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
}
}
[TestMethod]
public async Task Return_Non_Success()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += args =>
{
args.ResponseUserProperties = new List<MqttUserProperty>
{
new MqttUserProperty("Property", "Value")
};
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
return CompletedTask.Instance;
};
var client = testEnvironment.CreateClient();
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().WithoutThrowOnNonSuccessfulConnectResponse().Build());
Assert.IsNotNull(response);
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
Assert.AreEqual(response.UserProperties[0].Name, "Property");
Assert.AreEqual(response.UserProperties[0].Value, "Value");
}
}
[TestMethod]
[ExpectedException(typeof(MqttClientDisconnectedException))]
public async Task Rpc_Client_Not_Connected()
{
var factory = new MqttFactory();
using (var client = factory.CreateMqttClient())
{
var rpcClient = factory.CreateMqttRpcClient(client);
await rpcClient.ExecuteAsync("aaa", new byte[] { 1, 2, 3 }, MqttQualityOfServiceLevel.AtLeastOnce);
}
}
}
}
@@ -0,0 +1,59 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Clients.MqttClient
{
[TestClass]
public sealed class MqttClient_Publish_Tests : BaseTestClass
{
[TestMethod]
public async Task Acknowledge_After_Disconnect_Should_Fail()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
await client.SubscribeAsync("TEST", MqttQualityOfServiceLevel.AtLeastOnce);
var flag = false;
client.ApplicationMessageReceivedAsync += async args =>
{
try
{
args.AutoAcknowledge = false;
await client.DisconnectAsync();
await args.AcknowledgeAsync();
flag = true;
}
catch (Exception exception)
{
}
};
await server.InjectApplicationMessageAsync(
new MqttApplicationMessage
{
Topic = "TEST"
});
SpinWait.SpinUntil(() => flag, TimeSpan.FromSeconds(10));
Assert.IsTrue(flag);
}
}
}
}
@@ -69,38 +69,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
Assert.IsTrue(success);
}
}
[TestMethod]
public async Task Connect_Disconnect_Connect()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var clientOptions = testEnvironment.CreateDefaultClientOptions();
var client = testEnvironment.CreateClient();
await client.ConnectAsync(clientOptions);
await client.DisconnectAsync();
await client.ConnectAsync(clientOptions);
}
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public async Task Connect_Multiple_Times_Should_Fail()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var clientOptions = testEnvironment.CreateDefaultClientOptions();
var client = testEnvironment.CreateClient();
await client.ConnectAsync(clientOptions);
await client.ConnectAsync(clientOptions);
}
}
[TestMethod]
public async Task Disconnect_Event_Contains_Exception()
@@ -10,7 +10,7 @@ using MQTTnet.Client;
namespace MQTTnet.Tests.Clients
{
[TestClass]
public class MqttClientOptionsBuilder_Tests
public sealed class MqttClientOptionsBuilder_Tests
{
[TestMethod]
public void WithConnectionUri_Credential_Test()
-98
View File
@@ -1,98 +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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Exceptions;
using MQTTnet.Internal;
namespace MQTTnet.Tests
{
[TestClass]
public class Extension_Tests
{
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
[TestMethod]
public async Task TimeoutAfter()
{
await MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct),
TimeSpan.FromMilliseconds(100), CancellationToken.None);
}
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
[TestMethod]
public async Task TimeoutAfterWithResult()
{
await MqttTaskTimeout.WaitAsync(
ct => Task.Delay(TimeSpan.FromMilliseconds(500), ct).ContinueWith(t => 5, ct),
TimeSpan.FromMilliseconds(100), CancellationToken.None);
}
[TestMethod]
public async Task TimeoutAfterWithInnerException()
{
try
{
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
{
var iis = new int[0];
iis[1] = 0;
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
Assert.Fail();
}
catch (Exception e)
{
Assert.IsTrue(e is IndexOutOfRangeException);
}
}
[TestMethod]
public async Task TimeoutAfterWithInnerExceptionWithResult()
{
try
{
await MqttTaskTimeout.WaitAsync(ct => Task.Run(() =>
{
var iis = new int[0];
iis[1] = 0;
return iis[0];
}, ct), TimeSpan.FromSeconds(1), CancellationToken.None);
Assert.Fail();
}
catch (Exception e)
{
Assert.IsTrue(e is IndexOutOfRangeException);
}
}
[TestMethod]
public async Task TimeoutAfterMemoryUsage()
{
var initialMemory = GC.GetTotalMemory(true);
var tasks = Enumerable.Range(0, 100000)
.Select(i =>
{
return MqttTaskTimeout.WaitAsync(ct => Task.Delay(TimeSpan.FromMilliseconds(1), ct),
TimeSpan.FromMinutes(1), CancellationToken.None);
});
await Task.WhenAll(tasks);
AssertIsLess(initialMemory + 2_000_000, GC.GetTotalMemory(true));
}
static void AssertIsLess(long bound, long actual)
{
if (bound < actual)
{
Assert.Fail($"value must be less than {bound:N0} but is {actual:N0}");
}
}
}
}
@@ -78,7 +78,7 @@ namespace MQTTnet.Tests.Formatter
Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1
}
[TestMethod]
[TestMethod]
public void Serialize_Full_MqttConnAckPacket_V310()
{
var connAckPacket = new MqttConnAckPacket
@@ -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();
+36 -36
View File
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -24,10 +25,9 @@ namespace MQTTnet.Tests.Mockups
public sealed class TestEnvironment : IDisposable
{
readonly List<string> _clientErrors = new List<string>();
readonly List<IMqttClient> _clients = new List<IMqttClient>();
readonly ConcurrentBag<IMqttClient> _clients = new ConcurrentBag<IMqttClient>();
readonly List<Exception> _exceptions = new List<Exception>();
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
readonly ConcurrentBag<ILowLevelMqttClient> _lowLevelClients = new ConcurrentBag<ILowLevelMqttClient>();
readonly MqttProtocolVersion _protocolVersion;
readonly List<string> _serverErrors = new List<string>();
@@ -65,9 +65,12 @@ namespace MQTTnet.Tests.Mockups
if (e.LogMessage.Level == MqttNetLogLevel.Error)
{
lock (_clientErrors)
if (!IgnoreClientLogErrors)
{
_clientErrors.Add(e.LogMessage.ToString());
lock (_clientErrors)
{
_clientErrors.Add(e.LogMessage.ToString());
}
}
}
};
@@ -212,30 +215,27 @@ namespace MQTTnet.Tests.Mockups
public IMqttClient CreateClient()
{
lock (_clients)
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : MqttNetNullLogger.Instance;
var client = Factory.CreateMqttClient(logger);
client.ConnectingAsync += e =>
{
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : new MqttNetNullLogger();
var client = Factory.CreateMqttClient(logger);
_clients.Add(client);
client.ConnectingAsync += e =>
if (TestContext != null)
{
if (TestContext != null)
var clientOptions = e.ClientOptions;
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{
var clientOptions = e.ClientOptions;
var existingClientId = clientOptions.ClientId;
if (existingClientId != null && !existingClientId.StartsWith(TestContext.TestName))
{
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
}
clientOptions.ClientId = TestContext.TestName + "_" + existingClientId;
}
}
return CompletedTask.Instance;
};
return client;
}
return CompletedTask.Instance;
};
_clients.Add(client);
return client;
}
public MqttClientOptions CreateDefaultClientOptions()
@@ -245,18 +245,15 @@ 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()
{
lock (_clients)
{
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
_lowLevelClients.Add(client);
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
_lowLevelClients.Add(client);
return client;
}
return client;
}
public MqttServer CreateServer(MqttServerOptions options)
@@ -383,13 +380,16 @@ namespace MQTTnet.Tests.Mockups
}
}
lock (_clientErrors)
if (!IgnoreClientLogErrors)
{
if (!IgnoreClientLogErrors && _clientErrors.Count > 0)
lock (_clientErrors)
{
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
Console.WriteLine(message);
throw new Exception(message);
if (_clientErrors.Count > 0)
{
var message = $"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)})";
Console.WriteLine(message);
throw new Exception(message);
}
}
}
}
@@ -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);
}
}
+9 -12
View File
@@ -598,8 +598,7 @@ namespace MQTTnet.Tests.Server
// c
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
await Task.Delay(500);
await LongTestDelay();
var flow = string.Join(string.Empty, events);
Assert.AreEqual("c", flow);
@@ -607,10 +606,8 @@ namespace MQTTnet.Tests.Server
// Connect client with same client ID. Should disconnect existing client.
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
await Task.Delay(500);
await LongTestDelay();
flow = string.Join(string.Empty, events);
Assert.AreEqual("cdc", flow);
c2.ApplicationMessageReceivedAsync += e =>
@@ -628,8 +625,7 @@ namespace MQTTnet.Tests.Server
// r
await c2.PublishStringAsync("topic");
await Task.Delay(500);
await LongTestDelay();
flow = string.Join(string.Empty, events);
Assert.AreEqual("cdcr", flow);
@@ -639,16 +635,17 @@ namespace MQTTnet.Tests.Server
await c1.TryDisconnectAsync();
Assert.AreEqual(false, c1.IsConnected);
await Task.Delay(500);
await LongTestDelay();
// d
Assert.AreEqual(true, c2.IsConnected);
await c2.DisconnectAsync();
await Task.Delay(500);
await LongTestDelay();
await server.StopAsync();
await LongTestDelay();
flow = string.Join(string.Empty, events);
Assert.AreEqual("cdcrd", flow);
}
@@ -852,11 +849,11 @@ namespace MQTTnet.Tests.Server
return CompletedTask.Instance;
};
await Task.Delay(100);
// Stopping the server should disconnect the connection with the client
// which, it turn, will fire the disconnected event at the client.
await server.StopAsync();
await Task.Delay(100);
await LongTestDelay();
Assert.AreEqual(1, disconnectCalled);
}
@@ -0,0 +1,25 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class MqttRetainedMessageManager_Tests
{
[TestMethod]
public async Task MqttRetainedMessageManager_GetUndefinedTopic()
{
var logger = new Mockups.TestLogger();
var eventContainer = new MQTTnet.Server.MqttServerEventContainer();
var retainedMessagesManager = new MQTTnet.Server.MqttRetainedMessagesManager(eventContainer, logger);
var task = retainedMessagesManager.GetMessage("undefined");
Assert.IsNotNull(task, "Task should not be null");
var result = await task;
Assert.IsNull(result, "Null result expected");
}
}
}
@@ -221,7 +221,10 @@ namespace MQTTnet.Tests.Server
var eventContainer = new MqttServerEventContainer();
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
var session = new MqttSession("", false, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
var session = new MqttSession(new MqttConnectPacket
{
ClientId = ""
}, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
}
+29 -11
View File
@@ -70,6 +70,28 @@ namespace MQTTnet.Tests.Server
}
}
[TestMethod]
public async Task Do_Not_Use_Expired_Session()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer(o => o.WithPersistentSessions());
var c1 = await testEnvironment.ConnectClient(o => o.WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3));
// Kill the client connection and ensure that the session will stay there but is expired after 3 seconds!
c1.Dispose();
await Task.Delay(TimeSpan.FromSeconds(6));
c1 = testEnvironment.CreateClient();
var options = testEnvironment.CreateDefaultClientOptionsBuilder().WithClientId("Client1").WithCleanSession(false).WithSessionExpiryInterval(3).Build();
var connectResult = await c1.ConnectAsync(options);
Assert.AreEqual(false, connectResult.IsSessionPresent);
}
}
[TestMethod]
public async Task Fire_Deleted_Event()
{
@@ -121,7 +143,7 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
}
}
[TestMethod]
[DataRow(MqttProtocolVersion.V310)]
[DataRow(MqttProtocolVersion.V311)]
@@ -134,16 +156,12 @@ namespace MQTTnet.Tests.Server
await testEnvironment.StartServer();
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion);
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(10)).WithProtocolVersion(protocolVersion);
var hasReceive = false;
void OnReceive()
{
if (!hasReceive)
{
hasReceive = true;
}
hasReceive = true;
}
// Try to connect 50 clients at the same time.
@@ -152,7 +170,7 @@ namespace MQTTnet.Tests.Server
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
await LongTestDelay();
Assert.AreEqual(1, connectedClients.Count);
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
@@ -164,7 +182,7 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, hasReceive);
}
}
[DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
@@ -355,8 +373,8 @@ namespace MQTTnet.Tests.Server
onReceive();
return CompletedTask.Instance;
};
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{
await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
}
@@ -557,7 +557,7 @@ namespace MQTTnet.Tests.Server
var eventContainer = new MqttServerEventContainer();
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
_clientSession = new MqttSession(clientId, false, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
_clientSession = new MqttSession(new MqttConnectPacket{ ClientId = clientId }, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
List<string> topicsToSubscribe;
@@ -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,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();
}
}
+204 -157
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,17 +21,9 @@ 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 object _disconnectLock = new object();
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
readonly MqttClientEvents _events = new MqttClientEvents();
readonly MqttNetSourceLogger _logger;
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
@@ -65,32 +58,32 @@ namespace MQTTnet.Client
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<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,9 +107,9 @@ 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();
@@ -127,12 +120,12 @@ namespace MQTTnet.Client
_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);
_adapter = adapter ?? throw new InvalidOperationException("The adapter factory did not provide an adapter.");
if (cancellationToken.CanBeCanceled)
{
connectResult = await ConnectInternal(cancellationToken).ConfigureAwait(false);
connectResult = await ConnectInternal(adapter, cancellationToken).ConfigureAwait(false);
}
else
{
@@ -140,43 +133,50 @@ namespace MQTTnet.Client
// CancellationToken.None or similar.
using (var timeout = new CancellationTokenSource(Options.Timeout))
{
connectResult = await ConnectInternal(timeout.Token).ConfigureAwait(false);
connectResult = await ConnectInternal(adapter, timeout.Token).ConfigureAwait(false);
}
}
if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
{
_logger.Warning("Connecting failed: {0}", connectResult.ResultCode);
return connectResult;
}
_lastPacketSentTimestamp = DateTime.UtcNow;
var keepAliveInterval = Options.KeepAlivePeriod;
if (connectResult.ServerKeepAlive > 0)
{
_logger.Info($"Using keep alive value ({connectResult.ServerKeepAlive}) sent from the server.");
_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);
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessages(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;
}
@@ -219,19 +219,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 +239,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);
}
}
}
@@ -274,11 +274,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:
{
@@ -306,7 +306,7 @@ namespace MQTTnet.Client
UserProperties = data.UserProperties
};
return SendAsync(authPacket, cancellationToken);
return Send(authPacket, cancellationToken);
}
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default)
@@ -335,17 +335,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)
@@ -374,17 +374,17 @@ 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);
}
protected override void Dispose(bool disposing)
@@ -408,7 +408,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 +416,7 @@ namespace MQTTnet.Client
if (!eventArgs.ProcessingFailed)
{
var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs);
return SendAsync(pubRecPacket, cancellationToken);
return Send(pubRecPacket, cancellationToken);
}
}
else
@@ -427,27 +427,51 @@ namespace MQTTnet.Client
return CompletedTask.Instance;
}
async Task<MqttClientConnectResult> AuthenticateAsync(MqttClientOptions options, CancellationToken cancellationToken)
async Task<MqttClientConnectResult> Authenticate(IMqttChannelAdapter channelAdapter, MqttClientOptions options, CancellationToken cancellationToken)
{
MqttClientConnectResult result;
try
{
var connectPacket = MqttPacketFactories.Connect.Create(options);
await Send(connectPacket, cancellationToken).ConfigureAwait(false);
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
var receivedPacket = await Receive(cancellationToken).ConfigureAwait(false);
var clientConnectResultFactory = new MqttClientConnectResultFactory();
result = clientConnectResultFactory.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion);
if (receivedPacket is MqttConnAckPacket connAckPacket)
{
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, _adapter.PacketFormatterAdapter.ProtocolVersion);
}
else if (receivedPacket is MqttAuthPacket)
{
throw new NotSupportedException("Extended authentication handler is not yet supported");
}
else if (receivedPacket == null)
{
throw new MqttCommunicationException("Connection closed.");
}
else
{
throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket}).");
}
}
catch (Exception exception)
{
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
}
if (result.ResultCode != MqttClientConnectResultCode.Success)
// This is no feature. It is basically a backward compatibility option and should be removed in the future.
// The client should not throw any exception if the transport layer connection was successful and the server
// did send a proper ACK packet with a non success response.
if (options.ThrowOnNonSuccessfulConnectResponse)
{
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
_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.");
@@ -482,7 +506,7 @@ namespace MQTTnet.Client
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand);
}
async Task<MqttClientConnectResult> ConnectInternal(CancellationToken cancellationToken)
async Task<MqttClientConnectResult> ConnectInternal(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
{
var backgroundCancellationToken = _mqttClientAlive.Token;
@@ -495,14 +519,18 @@ namespace MQTTnet.Client
_publishPacketReceiverQueue?.Dispose();
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
var connectResult = await Authenticate(channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false);
if (connectResult.ResultCode == MqttClientConnectResultCode.Success)
{
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(backgroundCancellationToken), backgroundCancellationToken);
}
return await AuthenticateAsync(Options, effectiveCancellationToken.Token).ConfigureAwait(false);
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();
@@ -527,7 +555,7 @@ namespace MQTTnet.Client
try
{
_packetDispatcher.Dispose(new MqttClientDisconnectedException(exception));
_packetDispatcher?.Dispose(new MqttClientDisconnectedException(exception));
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
@@ -557,17 +585,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,15 +640,26 @@ 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 OnConnected(MqttClientConnectResult connectResult)
{
if (_events.ConnectedEvent.HasHandlers)
{
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
return _events.ConnectedEvent.InvokeAsync(eventArgs);
}
return CompletedTask.Instance;
}
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket)
{
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
@@ -641,7 +680,7 @@ 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)
@@ -657,7 +696,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 +720,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,39 +729,115 @@ 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 = _adapter.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.");
}
}
async Task<TResponsePacket> Request<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
{
cancellationToken.ThrowIfCancellationRequested();
@@ -736,7 +851,7 @@ namespace MQTTnet.Client
{
try
{
await SendAsync(requestPacket, cancellationToken).ConfigureAwait(false);
await Send(requestPacket, cancellationToken).ConfigureAwait(false);
}
catch (Exception exception)
{
@@ -760,12 +875,13 @@ namespace MQTTnet.Client
}
}
Task SendAsync(MqttPacket packet, CancellationToken cancellationToken)
Task Send(MqttPacket packet, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
_lastPacketSentTimestamp = DateTime.UtcNow;
//ThrowIfNotConnected();
return _adapter.SendPacketAsync(packet, cancellationToken);
}
@@ -823,7 +939,7 @@ namespace MQTTnet.Client
}
}
async Task TryProcessReceivedPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
async Task TryProcessReceivedPacket(MqttPacket packet, CancellationToken cancellationToken)
{
try
{
@@ -884,80 +1000,11 @@ namespace MQTTnet.Client
_packetDispatcher.FailAll(exception);
await DisconnectInternalAsync(_packetReceiverTask, exception, null).ConfigureAwait(false);
await DisconnectInternal(_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)
async Task TrySendKeepAliveMessages(CancellationToken cancellationToken)
{
try
{
@@ -1006,7 +1053,7 @@ namespace MQTTnet.Client
_logger.Error(exception, "Error exception while sending/receiving keep alive packets.");
}
await DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
await DisconnectInternal(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
}
finally
{
@@ -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>
@@ -100,6 +100,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 ThrowOnNonSuccessfulConnectResponse { 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 +227,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.
@@ -78,7 +78,17 @@ namespace MQTTnet.Client
return _options;
}
/// <summary>
/// The client will not throw an exception when the MQTT server responses with a non success ACK packet.
/// This will become the default behavior in future versions of the library.
/// </summary>
public MqttClientOptionsBuilder WithoutThrowOnNonSuccessfulConnectResponse()
{
_options.ThrowOnNonSuccessfulConnectResponse = false;
return this;
}
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
{
_options.AuthenticationMethod = method;
@@ -421,6 +431,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))
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
@@ -30,11 +31,21 @@ namespace MQTTnet.Client
#endif
#if NETCOREAPP3_1_OR_GREATER
public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; }
public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; }
public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
public CipherSuitesPolicy CipherSuitesPolicy { get; set; }
public EncryptionPolicy EncryptionPolicy { get; set; } = EncryptionPolicy.RequireEncryption;
public bool AllowRenegotiation { get; set; } = true;
#endif
/// <summary>
/// Gets or sets the target host.
/// If the value is null or empty the same host as the TCP socket host will be used.
/// </summary>
public string TargetHost { get; set; }
#if NET48 || NETCOREAPP3_1_OR_GREATER
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
#else
@@ -2,15 +2,19 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.WebSockets;
namespace MQTTnet.Client
{
public sealed class MqttClientWebSocketOptions : IMqttClientChannelOptions
{
public CookieContainer CookieContainer { get; set; }
public ICredentials Credentials { get; set; }
public MqttClientWebSocketProxyOptions ProxyOptions { get; set; }
public IDictionary<string, string> RequestHeaders { get; set; }
@@ -18,12 +22,34 @@ namespace MQTTnet.Client
public ICollection<string> SubProtocols { get; set; } = new List<string> { "mqtt" };
public MqttClientTlsOptions TlsOptions { get; set; } = new MqttClientTlsOptions();
public string Uri { get; set; }
public override string ToString()
{
return Uri;
}
#if !NETSTANDARD1_3
#if !WINDOWS_UWP
/// <summary>
/// Gets or sets the keep alive interval for the Web Socket connection.
/// This is not related to the keep alive interval for the MQTT protocol.
/// </summary>
public TimeSpan KeepAliveInterval { get; set; } = WebSocket.DefaultKeepAliveInterval;
/// <summary>
/// Gets or sets whether the default (system) credentials should be used when connecting via Web Socket connection.
/// This is not related to the credentials which are used for the MQTT protocol.
/// </summary>
public bool UseDefaultCredentials { get; set; }
#else
/// <summary>
/// Gets or sets the keep alive interval for the Web Socket connection.
/// This is not related to the keep alive interval for the MQTT protocol.
/// </summary>
public TimeSpan KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(30);
#endif
#endif
}
}
@@ -16,6 +16,8 @@ namespace MQTTnet.Client
public bool BypassOnLocal { get; set; }
public bool UseDefaultCredentials { get; set; }
public string[] BypassList { get; set; }
}
}
@@ -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.
@@ -77,7 +77,7 @@ namespace MQTTnet.Client
internal MqttPublishPacket PublishPacket { get; set; }
public Task AcknowledgeAsync(CancellationToken cancellationToken)
public Task AcknowledgeAsync(CancellationToken cancellationToken = default)
{
if (_acknowledgeHandler == null)
{
@@ -16,6 +16,8 @@ namespace MQTTnet.Diagnostics
return "net461";
#elif NET472
return "net472";
#elif NET48
return "net48";
#elif NETSTANDARD1_3
return "netstandard1.3";
#elif NETSTANDARD2_0
+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;
}
}
@@ -2,54 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Exceptions;
using MQTTnet.Protocol;
namespace MQTTnet.Formatter
{
public static class MqttConnectReasonCodeConverter
{
public static MqttConnectReasonCode ToConnectReasonCode(MqttConnectReturnCode returnCode)
{
switch (returnCode)
{
case MqttConnectReturnCode.ConnectionAccepted:
{
return MqttConnectReasonCode.Success;
}
case MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion:
{
return MqttConnectReasonCode.UnsupportedProtocolVersion;
}
case MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword:
{
return MqttConnectReasonCode.BadUserNameOrPassword;
}
case MqttConnectReturnCode.ConnectionRefusedIdentifierRejected:
{
return MqttConnectReasonCode.ClientIdentifierNotValid;
}
case MqttConnectReturnCode.ConnectionRefusedServerUnavailable:
{
return MqttConnectReasonCode.ServerUnavailable;
}
case MqttConnectReturnCode.ConnectionRefusedNotAuthorized:
{
return MqttConnectReasonCode.NotAuthorized;
}
default:
{
throw new MqttProtocolViolationException("Unable to convert connect reason code (MQTTv5) to return code (MQTTv3).");
}
}
}
public static MqttConnectReturnCode ToConnectReturnCode(MqttConnectReasonCode reasonCode)
{
switch (reasonCode)
@@ -59,11 +17,13 @@ namespace MQTTnet.Formatter
return MqttConnectReturnCode.ConnectionAccepted;
}
case MqttConnectReasonCode.Banned:
case MqttConnectReasonCode.NotAuthorized:
{
return MqttConnectReturnCode.ConnectionRefusedNotAuthorized;
}
case MqttConnectReasonCode.BadAuthenticationMethod:
case MqttConnectReasonCode.BadUserNameOrPassword:
{
return MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
@@ -79,6 +39,7 @@ namespace MQTTnet.Formatter
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
}
case MqttConnectReasonCode.UseAnotherServer:
case MqttConnectReasonCode.ServerUnavailable:
case MqttConnectReasonCode.ServerBusy:
case MqttConnectReasonCode.ServerMoved:
@@ -87,9 +48,7 @@ namespace MQTTnet.Formatter
}
default:
{
throw new MqttProtocolViolationException("Unable to convert connect reason code (MQTTv5) to return code (MQTTv3).");
}
return MqttConnectReturnCode.ConnectionRefusedUnacceptableProtocolVersion;
}
}
}
@@ -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);
@@ -130,6 +130,9 @@ namespace MQTTnet.Formatter.V5
MaximumQoS = MqttQualityOfServiceLevel.ExactlyOnce
};
// Also set the return code of MQTT 3.1.1 for backward compatibility and debugging purposes.
packet.ReturnCode = MqttConnectReasonCodeConverter.ToConnectReturnCode(packet.ReasonCode);
var propertiesReader = new MqttV5PropertiesReader(_bufferReader);
while (propertiesReader.MoveNext())
{
@@ -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)
@@ -100,6 +100,12 @@ namespace MQTTnet.Implementations
if (_tcpOptions.TlsOptions?.UseTls == true)
{
var targetHost = _tcpOptions.TlsOptions.TargetHost;
if (string.IsNullOrEmpty(targetHost))
{
targetHost = _tcpOptions.Server;
}
var sslStream = new SslStream(networkStream, false, InternalUserCertificateValidationCallback);
try
{
@@ -111,14 +117,16 @@ namespace MQTTnet.Implementations
EnabledSslProtocols = _tcpOptions.TlsOptions.SslProtocol,
CertificateRevocationCheckMode =
_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors ? X509RevocationMode.NoCheck : _tcpOptions.TlsOptions.RevocationMode,
TargetHost = _tcpOptions.Server,
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy
TargetHost = targetHost,
CipherSuitesPolicy = _tcpOptions.TlsOptions.CipherSuitesPolicy,
EncryptionPolicy = _tcpOptions.TlsOptions.EncryptionPolicy,
AllowRenegotiation = _tcpOptions.TlsOptions.AllowRenegotiation
};
await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false);
#else
await sslStream.AuthenticateAsClientAsync(
_tcpOptions.Server,
targetHost,
LoadCertificates(),
_tcpOptions.TlsOptions.SslProtocol,
!_tcpOptions.TlsOptions.IgnoreCertificateRevocationErrors)
@@ -290,12 +298,12 @@ namespace MQTTnet.Implementations
X509CertificateCollection LoadCertificates()
{
var certificates = new X509CertificateCollection();
if (_tcpOptions.TlsOptions.Certificates == null)
{
return certificates;
return null;
}
var certificates = new X509CertificateCollection();
foreach (var certificate in _tcpOptions.TlsOptions.Certificates)
{
certificates.Add(certificate);
@@ -231,6 +231,7 @@ namespace MQTTnet.Implementations
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger))
{
clientAdapter.AllowPacketFragmentation = _options.AllowPacketFragmentation;
await clientHandler(clientAdapter).ConfigureAwait(false);
}
}
@@ -255,6 +256,7 @@ namespace MQTTnet.Implementations
{
try
{
// ReSharper disable once MethodHasAsyncOverload
stream?.Dispose();
clientSocket?.Dispose();
}
@@ -155,14 +155,26 @@ namespace MQTTnet.Implementations
throw new NotSupportedException("Proxies are not supported when using 'netstandard 1.3'.");
#else
var proxyUri = new Uri(_options.ProxyOptions.Address);
WebProxy webProxy;
if (!string.IsNullOrEmpty(_options.ProxyOptions.Username) && !string.IsNullOrEmpty(_options.ProxyOptions.Password))
{
var credentials = new NetworkCredential(_options.ProxyOptions.Username, _options.ProxyOptions.Password, _options.ProxyOptions.Domain);
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList, credentials);
webProxy = new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList, credentials);
}
else
{
webProxy = new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
}
if (_options.ProxyOptions.UseDefaultCredentials)
{
// Only update the property if required because setting it to false will alter
// the used credentials internally!
webProxy.UseDefaultCredentials = true;
}
return new WebProxy(proxyUri, _options.ProxyOptions.BypassOnLocal, _options.ProxyOptions.BypassList);
return webProxy;
#endif
}
@@ -207,6 +219,26 @@ namespace MQTTnet.Implementations
}
}
#if !NETSTANDARD1_3
#if !WINDOWS_UWP
// Only set the value if it is actually true. This property is not supported on all platforms
// and will throw a _PlatformNotSupported_ (i.e. WASM) exception when being used regardless of the actual value.
if (_options.UseDefaultCredentials)
{
clientWebSocket.Options.UseDefaultCredentials = _options.UseDefaultCredentials;
}
if (_options.KeepAliveInterval != WebSocket.DefaultKeepAliveInterval)
{
clientWebSocket.Options.KeepAliveInterval = _options.KeepAliveInterval;
}
#endif
#endif
if (_options.Credentials != null)
{
clientWebSocket.Options.Credentials = _options.Credentials;
}
var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
if (certificateValidationHandler != null)
{
@@ -220,6 +252,8 @@ namespace MQTTnet.Implementations
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net452'.");
#elif NET461
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net461'.");
#elif NET48
throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net48'.");
#else
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
@@ -1,38 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Exceptions;
namespace MQTTnet.Internal
{
public static class MqttTaskTimeout
{
public static async Task WaitAsync(Func<CancellationToken, Task> action, TimeSpan timeout, CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException(nameof(action));
using (var timeoutCts = new CancellationTokenSource(timeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
{
try
{
await action(linkedCts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException exception)
{
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
if (timeoutReached)
{
throw new MqttCommunicationTimedOutException(exception);
}
throw;
}
}
}
}
}
+1 -1
View File
@@ -11,7 +11,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<LangVersion>7.3</LangVersion>
<AssemblyName>MQTTnet</AssemblyName>
+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
@@ -4,6 +4,7 @@
using System;
using System.Text;
using MQTTnet.Internal;
namespace MQTTnet
{
@@ -16,6 +17,16 @@ namespace MQTTnet
throw new ArgumentNullException(nameof(applicationMessage));
}
if(applicationMessage.PayloadSegment == EmptyBuffer.ArraySegment)
{
return null;
}
if (applicationMessage.PayloadSegment.Array == null)
{
return null;
}
var payloadSegment = applicationMessage.PayloadSegment;
return Encoding.UTF8.GetString(payloadSegment.Array, payloadSegment.Offset, payloadSegment.Count);
}
@@ -36,7 +36,7 @@ namespace MQTTnet.Packets
public override string ToString()
{
return $"Disconnect: [ReasonCode={ReasonCode}]";
return $"Disconnect: [ReasonCode={ReasonCode}] [ReasonString={ReasonString}] [ServerReference={ServerReference}] [SessionExpiryInterval={SessionExpiryInterval}]";
}
}
}
@@ -4,26 +4,33 @@
using System;
using System.Collections;
using System.Collections.Generic;
using MQTTnet.Formatter;
using MQTTnet.Packets;
namespace MQTTnet.Server
{
public sealed class ClientConnectedEventArgs : EventArgs
{
public ClientConnectedEventArgs(string clientId, string userName, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems)
readonly MqttConnectPacket _connectPacket;
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
ProtocolVersion = protocolVersion;
Endpoint = endpoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
/// <summary>
/// Gets the client identifier of the connected client.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
public string ClientId => _connectPacket.ClientId;
/// <summary>
/// Gets the endpoint of the connected client.
@@ -43,6 +50,12 @@ namespace MQTTnet.Server
/// <summary>
/// Gets the user name of the connected client.
/// </summary>
public string UserName { get; }
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the user properties sent by the client.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _connectPacket?.UserProperties;
}
}
@@ -103,6 +103,8 @@ namespace MQTTnet.Server
finally
{
IsRunning = false;
Session.DisconnectedTimestamp = DateTime.UtcNow;
_cancellationToken?.TryCancel();
_cancellationToken?.Dispose();
@@ -34,9 +34,9 @@ namespace MQTTnet.Server
// The _sessions dictionary contains all session, the _subscriberSessions hash set contains subscriber sessions only.
// See the MqttSubscription object for a detailed explanation.
readonly Dictionary<string, MqttSession> _sessions = new Dictionary<string, MqttSession>(4096);
readonly MqttSessionsStorage _sessionsStorage = new MqttSessionsStorage();
readonly object _sessionsManagementLock = new object();
readonly ReaderWriterLockSlim _sessionsManagementLock = new ReaderWriterLockSlim();
readonly HashSet<MqttSession> _subscriberSessions = new HashSet<MqttSession>();
public MqttClientSessionsManager(
@@ -58,7 +58,7 @@ namespace MQTTnet.Server
_eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer));
}
public async Task CloseAllConnectionsAsync()
public async Task CloseAllConnections()
{
List<MqttClient> connections;
lock (_clients)
@@ -78,24 +78,24 @@ namespace MQTTnet.Server
_logger.Verbose("Deleting session for client '{0}'.", clientId);
MqttClient connection;
lock (_clients)
{
_clients.TryGetValue(clientId, out connection);
}
MqttSession session;
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterWriteLock();
try
{
_sessions.TryGetValue(clientId, out session);
_sessions.Remove(clientId);
if (session != null)
if (_sessionsStorage.TryRemoveSession(clientId, out session))
{
_subscriberSessions.Remove(session);
}
}
finally
{
_sessionsManagementLock.ExitWriteLock();
}
try
{
@@ -106,7 +106,7 @@ namespace MQTTnet.Server
}
catch (Exception exception)
{
_logger.Error(exception, $"Error while deleting session '{clientId}'.");
_logger.Error(exception, "Error while deleting session '{0}'", clientId);
}
try
@@ -119,12 +119,12 @@ namespace MQTTnet.Server
}
catch (Exception exception)
{
_logger.Error(exception, $"Error while executing session deleted event for session '{clientId}'.");
_logger.Error(exception, "Error while executing session deleted event for session '{0}'", clientId);
}
session?.Dispose();
_logger.Verbose("Session for client '{0}' deleted.", clientId);
_logger.Verbose("Session of client '{0}' deleted", clientId);
}
public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage(
@@ -173,10 +173,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 +261,17 @@ namespace MQTTnet.Server
{
_createConnectionSyncRoot.Dispose();
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterWriteLock();
try
{
foreach (var sessionItem in _sessions)
{
sessionItem.Value.Dispose();
}
_sessionsStorage.Dispose();
}
finally
{
_sessionsManagementLock.ExitWriteLock();
}
_sessionsManagementLock?.Dispose();
}
public MqttClient GetClient(string id)
@@ -286,17 +295,17 @@ namespace MQTTnet.Server
}
}
public Task<IList<MqttClientStatus>> GetClientStatusesAsync()
public Task<IList<MqttClientStatus>> GetClientsStatus()
{
var result = new List<MqttClientStatus>();
lock (_clients)
{
foreach (var connection in _clients.Values)
foreach (var client in _clients.Values)
{
var clientStatus = new MqttClientStatus(connection)
var clientStatus = new MqttClientStatus(client)
{
Session = new MqttSessionStatus(connection.Session)
Session = new MqttSessionStatus(client.Session)
};
result.Add(clientStatus);
@@ -306,18 +315,23 @@ namespace MQTTnet.Server
return Task.FromResult((IList<MqttClientStatus>)result);
}
public Task<IList<MqttSessionStatus>> GetSessionStatusAsync()
public Task<IList<MqttSessionStatus>> GetSessionsStatus()
{
var result = new List<MqttSessionStatus>();
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterReadLock();
try
{
foreach (var sessionItem in _sessions)
foreach (var session in _sessionsStorage.ReadAllSessions())
{
var sessionStatus = new MqttSessionStatus(sessionItem.Value);
var sessionStatus = new MqttSessionStatus(session);
result.Add(sessionStatus);
}
}
finally
{
_sessionsManagementLock.ExitReadLock();
}
return Task.FromResult((IList<MqttSessionStatus>)result);
}
@@ -352,9 +366,7 @@ namespace MQTTnet.Server
if (_eventContainer.ClientConnectedEvent.HasHandlers)
{
var eventArgs = new ClientConnectedEventArgs(
connectPacket.ClientId,
connectPacket.Username,
var eventArgs = new ClientConnectedEventArgs(connectPacket,
channelAdapter.PacketFormatterAdapter.ProtocolVersion,
channelAdapter.Endpoint,
client.Session.Items);
@@ -415,7 +427,8 @@ namespace MQTTnet.Server
public void OnSubscriptionsAdded(MqttSession clientSession, List<string> topics)
{
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterWriteLock();
try
{
if (!clientSession.HasSubscribedTopics)
{
@@ -428,11 +441,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,13 +463,17 @@ namespace MQTTnet.Server
_subscriberSessions.Remove(clientSession);
}
}
finally
{
_sessionsManagementLock.ExitWriteLock();
}
}
public void Start()
{
if (!_options.EnablePersistentSessions)
{
_sessions.Clear();
_sessionsStorage.Clear();
}
}
@@ -545,50 +567,59 @@ namespace MQTTnet.Server
MqttSession oldSession;
MqttClient oldClient;
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterWriteLock();
try
{
MqttSession session;
// Create a new session (if required).
if (!_sessions.TryGetValue(connectPacket.ClientId, out oldSession))
if (!_sessionsStorage.TryGetSession(connectPacket.ClientId, out oldSession))
{
session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist);
session = CreateSession(connectPacket, validatingConnectionEventArgs, sessionShouldPersist);
}
else
{
if (connectPacket.CleanSession)
{
_logger.Verbose("Deleting existing session of client '{0}' due to clean start.", connectPacket.ClientId);
_logger.Verbose("Deleting existing session of client '{0}' due to clean start", connectPacket.ClientId);
_subscriberSessions.Remove(oldSession);
session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist);
session = CreateSession(connectPacket, validatingConnectionEventArgs, sessionShouldPersist);
}
else
{
_logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
_logger.Verbose("Reusing existing session of client '{0}'", connectPacket.ClientId);
session = oldSession;
oldSession = null;
// Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval
session.IsPersistent = sessionShouldPersist;
connAckPacket.IsSessionPresent = true;
session.DisconnectedTimestamp = null;
session.Recover();
connAckPacket.IsSessionPresent = true;
}
}
_sessions[connectPacket.ClientId] = session;
_sessionsStorage.UpdateSession(connectPacket.ClientId, session);
// Create a new client (always required).
_clients.TryGetValue(connectPacket.ClientId, out oldClient);
if (oldClient != null)
lock (_clients)
{
// This will stop the current client from sending and receiving but remains the connection active
// for a later DISCONNECT packet.
oldClient.IsTakenOver = true;
_clients.TryGetValue(connectPacket.ClientId, out oldClient);
if (oldClient != null)
{
// This will stop the current client from sending and receiving but remains the connection active
// for a later DISCONNECT packet.
oldClient.IsTakenOver = true;
}
client = CreateClient(connectPacket, channelAdapter, session);
_clients[connectPacket.ClientId] = client;
}
client = CreateClient(connectPacket, channelAdapter, session);
_clients[connectPacket.ClientId] = client;
}
finally
{
_sessionsManagementLock.ExitWriteLock();
}
if (!connAckPacket.IsSessionPresent)
@@ -616,11 +647,14 @@ namespace MQTTnet.Server
return client;
}
MqttSession CreateSession(string clientId, IDictionary sessionItems, bool isPersistent)
MqttSession CreateSession(MqttConnectPacket connectPacket, ValidatingConnectionEventArgs validatingConnectionEventArgs, bool isPersistent)
{
_logger.Verbose("Created new session for client '{0}'.", clientId);
_logger.Verbose("Created new session for client '{0}'", connectPacket.ClientId);
return new MqttSession(clientId, isPersistent, sessionItems, _options, _eventContainer, _retainedMessagesManager, this);
return new MqttSession(connectPacket, validatingConnectionEventArgs.SessionItems, _options, _eventContainer, _retainedMessagesManager, this)
{
IsPersistent = isPersistent
};
}
async Task FireApplicationMessageNotConsumedEvent(MqttApplicationMessage applicationMessage, string senderId)
@@ -636,15 +670,20 @@ namespace MQTTnet.Server
MqttSession GetClientSession(string clientId)
{
lock (_sessionsManagementLock)
_sessionsManagementLock.EnterReadLock();
try
{
if (!_sessions.TryGetValue(clientId, out var session))
if (!_sessionsStorage.TryGetSession(clientId, out var session))
{
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
}
return session;
}
finally
{
_sessionsManagementLock.ExitReadLock();
}
}
async Task<MqttConnectPacket> ReceiveConnectPacket(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
@@ -695,4 +734,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,7 @@ namespace MQTTnet.Server
public void Dispose()
{
_subscriptionsLock.Dispose();
_subscriptionsLock?.Dispose();
}
public async Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
@@ -232,7 +237,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 +299,11 @@ namespace MQTTnet.Server
}
}
}
_subscriptionChangedNotification?.OnSubscriptionsRemoved(_session, removedSubscriptions);
finally
{
_subscriptionsLock.ExitWriteLock();
_subscriptionChangedNotification?.OnSubscriptionsRemoved(_session, removedSubscriptions);
}
if (_eventContainer.ClientUnsubscribedTopicEvent.HasHandlers)
{
@@ -341,7 +350,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 +401,10 @@ namespace MQTTnet.Server
subscriptions.Add(subscription);
}
}
finally
{
_subscriptionsLock.ExitWriteLock();
}
return new CreateSubscriptionResult
{
@@ -511,4 +525,4 @@ namespace MQTTnet.Server
public MqttSubscription Subscription { get; set; }
}
}
}
}
@@ -134,9 +134,9 @@ namespace MQTTnet.Server
{
return Task.FromResult(message);
}
return null;
}
return Task.FromResult<MqttApplicationMessage>(null);
}
public async Task ClearMessages()
@@ -21,6 +21,7 @@ namespace MQTTnet.Server
readonly MqttPacketBus _packetBus = new MqttPacketBus();
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
readonly MqttConnectPacket _connectPacket;
readonly MqttServerOptions _serverOptions;
readonly MqttClientSubscriptionsManager _subscriptionsManager;
@@ -31,18 +32,16 @@ namespace MQTTnet.Server
HashSet<string> _subscribedTopics;
public MqttSession(
string clientId,
bool isPersistent,
MqttConnectPacket connectPacket,
IDictionary items,
MqttServerOptions serverOptions,
MqttServerEventContainer eventContainer,
MqttRetainedMessagesManager retainedMessagesManager,
MqttClientSessionsManager clientSessionsManager)
{
Id = clientId ?? throw new ArgumentNullException(nameof(clientId));
IsPersistent = isPersistent;
Items = items ?? throw new ArgumentNullException(nameof(items));
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager));
@@ -50,10 +49,14 @@ namespace MQTTnet.Server
}
public DateTime CreatedTimestamp { get; } = DateTime.UtcNow;
public DateTime? DisconnectedTimestamp { get; set; }
public bool HasSubscribedTopics => _subscribedTopics != null && _subscribedTopics.Count > 0;
public string Id { get; }
public uint ExpiryInterval => _connectPacket.SessionExpiryInterval;
public string Id => _connectPacket.ClientId;
/// <summary>
/// Session should persist if CleanSession was set to false (Mqtt3) or if SessionExpiryInterval != 0 (Mqtt5)
@@ -0,0 +1,93 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace MQTTnet.Server
{
public sealed class MqttSessionsStorage
{
readonly Dictionary<string, MqttSession> _sessions = new Dictionary<string, MqttSession>(4096);
public void Clear()
{
// Make sure that the sessions are also getting disposed!
// This object can be reused after disposing.
Dispose();
}
public void Dispose()
{
foreach (var sessionItem in _sessions)
{
sessionItem.Value.Dispose();
}
_sessions.Clear();
}
public IReadOnlyCollection<MqttSession> ReadAllSessions()
{
return new ReadOnlyCollection<MqttSession>(_sessions.Values.ToList());
}
public bool TryGetSession(string id, out MqttSession session)
{
if (id == null)
{
throw new ArgumentNullException(nameof(id));
}
if (!_sessions.TryGetValue(id, out session))
{
return false;
}
// If the Session Expiry Interval is 0xFFFFFFFF (UINT_MAX), the Session does not expire.
if (session.ExpiryInterval > 0 && session.ExpiryInterval != uint.MaxValue)
{
if (session.DisconnectedTimestamp.HasValue)
{
if (DateTime.UtcNow > session.DisconnectedTimestamp.Value.AddSeconds(session.ExpiryInterval))
{
TryRemoveSession(id, out var oldSession);
oldSession?.Dispose();
return false;
}
}
}
return true;
}
public bool TryRemoveSession(string id, out MqttSession session)
{
if (id == null)
{
throw new ArgumentNullException(nameof(id));
}
if (_sessions.TryGetValue(id, out session))
{
_sessions.Remove(id);
return true;
}
return false;
}
public void UpdateSession(string id, MqttSession session)
{
if (id == null)
{
throw new ArgumentNullException(nameof(id));
}
_sessions[id] = session;
}
}
}
+9 -3
View File
@@ -200,7 +200,7 @@ namespace MQTTnet.Server
{
ThrowIfNotStarted();
return _clientSessionsManager.GetClientStatusesAsync();
return _clientSessionsManager.GetClientsStatus();
}
public Task<IList<MqttApplicationMessage>> GetRetainedMessagesAsync()
@@ -226,10 +226,16 @@ namespace MQTTnet.Server
{
ThrowIfNotStarted();
return _clientSessionsManager.GetSessionStatusAsync();
return _clientSessionsManager.GetSessionsStatus();
}
[Obsolete("Use InjectApplicationMessageAsync instead.")]
public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage, CancellationToken cancellationToken = default)
{
return InjectApplicationMessageAsync(injectedApplicationMessage, cancellationToken);
}
public Task InjectApplicationMessageAsync(InjectedMqttApplicationMessage injectedApplicationMessage, CancellationToken cancellationToken = default)
{
if (injectedApplicationMessage == null)
{
@@ -292,7 +298,7 @@ namespace MQTTnet.Server
_cancellationTokenSource.Cancel(false);
await _clientSessionsManager.CloseAllConnectionsAsync().ConfigureAwait(false);
await _clientSessionsManager.CloseAllConnections().ConfigureAwait(false);
foreach (var adapter in _adapters)
{
+27 -1
View File
@@ -13,12 +13,38 @@ namespace MQTTnet.Server
{
public static class MqttServerExtensions
{
[Obsolete("Use InjectApplicationMessageAsync instead.")]
public static Task InjectApplicationMessage(
this MqttServer server,
string topic,
string payload = null,
MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
bool retain = false)
{
return InjectApplicationMessageAsync(server, topic, payload, qualityOfServiceLevel, retain);
}
public static Task InjectApplicationMessageAsync(this MqttServer server, MqttApplicationMessage applicationMessage)
{
if (server == null)
{
throw new ArgumentNullException(nameof(server));
}
if (applicationMessage == null)
{
throw new ArgumentNullException(nameof(applicationMessage));
}
return server.InjectApplicationMessageAsync(new InjectedMqttApplicationMessage(applicationMessage));
}
public static Task InjectApplicationMessageAsync(
this MqttServer server,
string topic,
string payload = null,
MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
bool retain = false)
{
if (server == null)
{
@@ -36,7 +62,7 @@ namespace MQTTnet.Server
payloadBuffer = Encoding.UTF8.GetBytes(stringPayload);
}
return server.InjectApplicationMessage(
return server.InjectApplicationMessageAsync(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
@@ -131,6 +131,18 @@ namespace MQTTnet.Server
return this;
}
/// <summary>
/// Usually the MQTT packets can be send partially to improve memory allocations.
/// This is done by using multiple TCP packets or WebSocket frames etc.
/// Unfortunately not all clients do support this and will close the connection when receiving partial packets.
/// </summary>
public MqttServerOptionsBuilder WithoutPacketFragmentation()
{
_options.DefaultEndpointOptions.AllowPacketFragmentation = false;
_options.TlsEndpointOptions.AllowPacketFragmentation = false;
return this;
}
public MqttServerOptionsBuilder WithPersistentSessions(bool value = true)
{
_options.EnablePersistentSessions = value;
@@ -23,6 +23,14 @@ namespace MQTTnet.Server
/// </summary>
public bool? KeepAlive { get; set; }
/// <summary>
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
/// or WebSocket frames etc. Unfortunately not all clients do support this feature and
/// will close the connection when receiving such packets. If such clients are connecting to this
/// server the flag must be set to _false_.
/// </summary>
public bool AllowPacketFragmentation { get; set; } = true;
/// <summary>
/// Gets or sets the TCP keep alive interval.
/// The value _null_ indicates that the OS and framework defaults should be used.
@@ -21,6 +21,10 @@ namespace MQTTnet.Server
public DateTime CreatedTimestamp => _session.CreatedTimestamp;
public DateTime? DisconnectedTimestamp => _session.DisconnectedTimestamp;
public uint ExpiryInterval => _session.ExpiryInterval;
public string Id => _session.Id;
public IDictionary Items => _session.Items;