From ee4adf93fcaf95ceccc7b331b6c899fa23236599 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Wed, 5 Jun 2024 14:30:43 +0200 Subject: [PATCH 01/19] use ReadOnlySequence instead of ArraySegment --- .../Server_Retained_Messages_Samples.cs | 5 +- .../MqttConnectionContext.cs | 10 +++- .../ReaderExtensionsBenchmark.cs | 5 +- .../SendPacketAsyncBenchmark.cs | 10 +++- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 6 ++ .../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 3 +- .../Formatter/MqttPublishPacketFactory.cs | 5 +- .../Internal/MqttRetainedMessagesManager.cs | 14 ++++- Source/MQTTnet.Server/MqttServerExtensions.cs | 3 +- Source/MQTTnet.TestApp/ClientTest.cs | 7 +-- Source/MQTTnet.TestApp/PerformanceTest.cs | 9 +-- Source/MQTTnet.TestApp/ServerTest.cs | 10 ++-- .../ASP/MqttConnectionContextTest.cs | 3 +- .../MQTTnet.Tests/ASP/ReaderExtensionsTest.cs | 2 +- .../ManagedMqttClient_Tests.cs | 7 ++- .../Clients/MqttClient/MqttClient_Tests.cs | 5 +- ...MqttPacketSerialization_V3_Binary_Tests.cs | 13 +++-- .../MqttPacketSerialization_V3_Tests.cs | 5 +- .../MqttPacketSerialization_V5_Tests.cs | 5 +- Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs | 3 +- .../Mockups/MemoryMqttChannel.cs | 9 +++ .../MqttApplicationMessageBuilder_Tests.cs | 5 +- Source/MQTTnet.Tests/Server/General.cs | 9 +-- Source/MQTTnet.Tests/Server/Session_Tests.cs | 3 +- Source/MQTTnet.Tests/Server/Tls_Tests.cs | 13 +++-- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 2 +- Source/MQTTnet/Channel/IMqttChannel.cs | 3 + .../MqttApplicationMessageFactory.cs | 2 +- Source/MQTTnet/Formatter/MqttPacketBuffer.cs | 56 ++++++++++++------- .../Formatter/MqttPublishPacketFactory.cs | 2 +- .../Formatter/V3/MqttV3PacketFormatter.cs | 21 +++---- .../Formatter/V5/MqttV5PacketDecoder.cs | 3 +- .../Formatter/V5/MqttV5PacketEncoder.cs | 17 +++--- .../MQTTnet/Implementations/MqttTcpChannel.cs | 34 +++++++++++ .../Implementations/MqttWebSocketChannel.cs | 33 +++++++++++ Source/MQTTnet/Internal/EmptyBuffer.cs | 2 + Source/MQTTnet/MqttApplicationMessage.cs | 5 +- .../MQTTnet/MqttApplicationMessageBuilder.cs | 26 +++++++-- .../MqttApplicationMessageExtensions.cs | 11 +--- Source/MQTTnet/Packets/MqttPublishPacket.cs | 7 ++- 40 files changed, 272 insertions(+), 121 deletions(-) diff --git a/Samples/Server/Server_Retained_Messages_Samples.cs b/Samples/Server/Server_Retained_Messages_Samples.cs index c563aec63..e78dac37b 100644 --- a/Samples/Server/Server_Retained_Messages_Samples.cs +++ b/Samples/Server/Server_Retained_Messages_Samples.cs @@ -6,6 +6,7 @@ // ReSharper disable UnusedMember.Global // ReSharper disable InconsistentNaming +using System.Buffers; using System.Text.Json; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -112,7 +113,7 @@ public static MqttRetainedMessageModel Create(MqttApplicationMessage message) // 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(), + Payload = message.PayloadSequence.ToArray(), UserProperties = message.UserProperties, ResponseTopic = message.ResponseTopic, CorrelationData = message.CorrelationData, @@ -130,7 +131,7 @@ public MqttApplicationMessage ToApplicationMessage() return new MqttApplicationMessage { Topic = Topic, - PayloadSegment = new ArraySegment(Payload ?? Array.Empty()), + PayloadSequence = new ReadOnlySequence(Payload ?? Array.Empty()), PayloadFormatIndicator = PayloadFormatIndicator, ResponseTopic = ResponseTopic, CorrelationData = CorrelationData, diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs index eb801cd5c..d118e71a8 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -203,7 +203,7 @@ public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellat { var buffer = PacketFormatterAdapter.Encode(packet); - if (buffer.Payload.Count == 0) + if (buffer.Payload.Length == 0) { // zero copy // https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279 @@ -232,7 +232,13 @@ static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) var span = output.GetSpan(buffer.Length); buffer.Packet.AsSpan().CopyTo(span); - buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count)); + + int offset = 0; + foreach (var segment in buffer.Payload) + { + segment.Span.CopyTo(span.Slice(offset, buffer.Packet.Count)); + offset += segment.Length; + } output.Advance(buffer.Length); } diff --git a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs index 41ba0a821..1dc2f9c8e 100644 --- a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs @@ -36,7 +36,10 @@ public void GlobalSetup() var buffer = mqttPacketFormatter.Encode(packet); stream = new MemoryStream(); stream.Write(buffer.Packet); - stream.Write(buffer.Payload); + foreach (var segment in buffer.Payload) + { + stream.Write(segment.Span); + } mqttPacketFormatter.Cleanup(); } diff --git a/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs index aa5cd0383..ebfb7db15 100644 --- a/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs @@ -40,7 +40,7 @@ public async ValueTask After() stream.Position = 0; var output = PipeWriter.Create(stream); - if (buffer.Payload.Count == 0) + if (buffer.Payload.Length == 0) { await output.WriteAsync(buffer.Packet).ConfigureAwait(false); } @@ -60,7 +60,13 @@ static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) var span = output.GetSpan(buffer.Length); buffer.Packet.AsSpan().CopyTo(span); - buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count)); + + int offset = 0; + foreach(var segment in buffer.Payload) + { + segment.Span.CopyTo(span.Slice(offset, buffer.Packet.Count)); + offset += segment.Length; + } output.Advance(buffer.Length); } diff --git a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs index de6ed3dc9..77f9eec0b 100644 --- a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -14,6 +14,7 @@ using MQTTnet.Formatter.V3; using BenchmarkDotNet.Jobs; using MQTTnet.Diagnostics; +using System.Buffers; namespace MQTTnet.Benchmarks { @@ -109,6 +110,11 @@ public Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, Cancellati throw new NotSupportedException(); } + public Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) + { + throw new NotSupportedException(); + } + public void Dispose() { } diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index 663e23846..0c20fbdd2 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -132,7 +133,7 @@ Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventAr return CompletedTask.Instance; } - var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray(); + var payloadBuffer = eventArgs.ApplicationMessage.PayloadSequence.ToArray(); awaitable.TrySetResult(payloadBuffer); // Set this message to handled to that other code can avoid execution etc. diff --git a/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs index 6e747f4d0..53fa396e0 100644 --- a/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs @@ -4,6 +4,7 @@ using MQTTnet.Exceptions; using MQTTnet.Packets; +using System.Buffers; namespace MQTTnet.Server.Internal.Formatter; @@ -30,7 +31,7 @@ public static MqttPublishPacket Create(MqttConnectPacket connectPacket) var packet = new MqttPublishPacket { Topic = connectPacket.WillTopic, - PayloadSegment = willMessageBuffer, + PayloadSequence = new ReadOnlySequence(willMessageBuffer), QualityOfServiceLevel = connectPacket.WillQoS, Retain = connectPacket.WillRetain, ContentType = connectPacket.WillContentType, @@ -56,7 +57,7 @@ public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage var packet = new MqttPublishPacket { Topic = applicationMessage.Topic, - PayloadSegment = applicationMessage.PayloadSegment, + PayloadSequence = applicationMessage.PayloadSequence, QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, Retain = applicationMessage.Retain, Dup = applicationMessage.Dup, diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index fb411eca7..c0dc38377 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -4,6 +4,7 @@ using MQTTnet.Diagnostics; using MQTTnet.Internal; +using System.Buffers; namespace MQTTnet.Server.Internal { @@ -65,8 +66,8 @@ public async Task UpdateMessage(string clientId, MqttApplicationMessage applicat lock (_messages) { - var payloadSegment = applicationMessage.PayloadSegment; - var hasPayload = payloadSegment.Count > 0; + var payloadSequence = applicationMessage.PayloadSequence; + var hasPayload = payloadSequence.Length > 0; if (!hasPayload) { @@ -82,7 +83,7 @@ public async Task UpdateMessage(string clientId, MqttApplicationMessage applicat } else { - if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !SequenceEqual(existingMessage.PayloadSegment, payloadSegment)) + if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !SequenceEqual(existingMessage.PayloadSequence, payloadSequence)) { _messages[applicationMessage.Topic] = applicationMessage; saveIsRequired = true; @@ -152,5 +153,12 @@ static bool SequenceEqual(ArraySegment source, ArraySegment target) { return source.AsSpan().SequenceEqual(target); } + + static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence target) + { + int offset = 0; + // TODO + return true; + } } } \ No newline at end of file diff --git a/Source/MQTTnet.Server/MqttServerExtensions.cs b/Source/MQTTnet.Server/MqttServerExtensions.cs index 59aecebbf..580e9559f 100644 --- a/Source/MQTTnet.Server/MqttServerExtensions.cs +++ b/Source/MQTTnet.Server/MqttServerExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Buffers; using System.Text; using MQTTnet.Internal; using MQTTnet.Packets; @@ -49,7 +50,7 @@ public static Task InjectApplicationMessage( new MqttApplicationMessage { Topic = topic, - PayloadSegment = new ArraySegment(payloadBuffer), + PayloadSequence = new ReadOnlySequence(payloadBuffer), QualityOfServiceLevel = qualityOfServiceLevel, Retain = retain })); diff --git a/Source/MQTTnet.TestApp/ClientTest.cs b/Source/MQTTnet.TestApp/ClientTest.cs index 775281037..db893f3eb 100644 --- a/Source/MQTTnet.TestApp/ClientTest.cs +++ b/Source/MQTTnet.TestApp/ClientTest.cs @@ -35,12 +35,9 @@ public static async Task RunAsync() client.ApplicationMessageReceivedAsync += e => { var payloadText = string.Empty; - if (e.ApplicationMessage.PayloadSegment.Count > 0) + if (e.ApplicationMessage.PayloadSequence.Length > 0) { - payloadText = Encoding.UTF8.GetString( - e.ApplicationMessage.PayloadSegment.Array, - e.ApplicationMessage.PayloadSegment.Offset, - e.ApplicationMessage.PayloadSegment.Count); + payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence); } Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); diff --git a/Source/MQTTnet.TestApp/PerformanceTest.cs b/Source/MQTTnet.TestApp/PerformanceTest.cs index cf715e07d..634700d94 100644 --- a/Source/MQTTnet.TestApp/PerformanceTest.cs +++ b/Source/MQTTnet.TestApp/PerformanceTest.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Diagnostics; using System.Linq; using System.Net; @@ -200,7 +201,7 @@ static MqttApplicationMessage CreateMessage() return new MqttApplicationMessage { Topic = "A/B/C", - PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes(Payload)), + PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes(Payload)), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }; } @@ -233,7 +234,7 @@ public static async Task RunQoS2Test() var message = new MqttApplicationMessage { Topic = "A/B/C", - PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("Hello World")), + PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes("Hello World")), QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }; @@ -284,7 +285,7 @@ public static async Task RunQoS1Test() var message = new MqttApplicationMessage { Topic = "A/B/C", - PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("Hello World")), + PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes("Hello World")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }; @@ -335,7 +336,7 @@ public static async Task RunQoS0Test() var message = new MqttApplicationMessage { Topic = "A/B/C", - PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("Hello World")), + PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes("Hello World")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }; diff --git a/Source/MQTTnet.TestApp/ServerTest.cs b/Source/MQTTnet.TestApp/ServerTest.cs index 0076381e0..c840834b4 100644 --- a/Source/MQTTnet.TestApp/ServerTest.cs +++ b/Source/MQTTnet.TestApp/ServerTest.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -101,7 +102,7 @@ public static async Task RunAsync() { // Replace the payload with the timestamp. But also extending a JSON // based payload with the timestamp is a suitable use case. - e.ApplicationMessage.PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"))); + e.ApplicationMessage.PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"))); } if (e.ApplicationMessage.Topic == "not_allowed_topic") @@ -145,12 +146,9 @@ public static async Task RunAsync() mqttServer.InterceptingPublishAsync += e => { var payloadText = string.Empty; - if (e.ApplicationMessage.PayloadSegment.Count > 0) + if (e.ApplicationMessage.PayloadSequence.Length > 0) { - payloadText = Encoding.UTF8.GetString( - e.ApplicationMessage.PayloadSegment.Array, - e.ApplicationMessage.PayloadSegment.Offset, - e.ApplicationMessage.PayloadSegment.Count); + payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence); } MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta); diff --git a/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs b/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs index cf8075728..385bfa63a 100644 --- a/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs +++ b/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; @@ -99,7 +100,7 @@ public async Task TestLargePacket() connection.Transport = pipe; var ctx = new MqttConnectionContext(serializer, connection); - await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new ArraySegment(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false); + await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSequence = new ReadOnlySequence(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false); var readResult = await pipe.Send.Reader.ReadAsync(); Assert.IsTrue(readResult.Buffer.Length > 20000); diff --git a/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs b/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs index 6c9cac8f8..91f4b0119 100644 --- a/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs +++ b/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs @@ -18,7 +18,7 @@ public void TestTryDeserialize() { var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); - var buffer = serializer.Encode(new MqttPublishPacket { Topic = "a", PayloadSegment = new byte[5] }).Join(); + var buffer = serializer.Encode(new MqttPublishPacket { Topic = "a", PayloadSequence = new ReadOnlySequence(new byte[5]) }).Join(); var sequence = new ReadOnlySequence(buffer.Array, buffer.Offset, buffer.Count); diff --git a/Source/MQTTnet.Tests/Clients/ManagedMqttClient/ManagedMqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/ManagedMqttClient/ManagedMqttClient_Tests.cs index 83143dfef..367f13f7a 100644 --- a/Source/MQTTnet.Tests/Clients/ManagedMqttClient/ManagedMqttClient_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/ManagedMqttClient/ManagedMqttClient_Tests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -298,7 +299,7 @@ public async Task Publish_Does_Not_Hang_On_Server_Error() await managedClient.EnqueueAsync( new MqttApplicationMessage - { Topic = topic, PayloadSegment = new ArraySegment(new byte[] { 1 }), Retain = true, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + { Topic = topic, PayloadSequence = new ReadOnlySequence(new byte[] { 1 }), Retain = true, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); var timeoutTask = Task.Delay(testTimeout); @@ -608,7 +609,7 @@ public async Task Subscriptions_Are_Published_Immediately() var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval); var sendingClient = await testEnvironment.ConnectClient(); - await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment(new byte[] { 1 }), Retain = true }); + await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSequence = new ReadOnlySequence(new byte[] { 1 }), Retain = true }); var subscribeTime = DateTime.UtcNow; @@ -641,7 +642,7 @@ public async Task Subscriptions_Subscribe_Only_New_Subscriptions() //wait a bit for the subscription to become established await Task.Delay(500); - await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSegment = new ArraySegment(new byte[] { 1 }), Retain = true }); + await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSequence = new ReadOnlySequence(new byte[] { 1 }), Retain = true }); var messages = await SetupReceivingOfMessages(managedClient, 1); diff --git a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs index 3eb0ff763..5fdf185cd 100644 --- a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -297,7 +298,7 @@ await receiver.SubscribeAsync( Assert.IsNotNull(receivedMessage); Assert.AreEqual("A", receivedMessage.Topic); - Assert.AreEqual(null, receivedMessage.PayloadSegment.Array); + Assert.AreEqual(0, receivedMessage.PayloadSequence.Length); } } @@ -508,7 +509,7 @@ public async Task Publish_QoS_1_In_ApplicationMessageReceiveHandler() client2.ApplicationMessageReceivedAsync += e => { - client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray())); + client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence.ToArray())); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs index b92608489..d7d74c7fd 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -111,7 +112,7 @@ public void DeserializeV311_MqttPublishPacket() PacketIdentifier = 123, Dup = true, Retain = true, - PayloadSegment = new ArraySegment(Encoding.ASCII.GetBytes("HELLO")), + PayloadSequence = new ReadOnlySequence(Encoding.ASCII.GetBytes("HELLO")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, Topic = "A/B/C" }; @@ -318,7 +319,7 @@ public void Serialize_LargePacket() var publishPacket = new MqttPublishPacket { Topic = "abcdefghijklmnopqrstuvwxyz0123456789", - PayloadSegment = new ArraySegment(payload) + PayloadSequence = new ReadOnlySequence(payload) }; var serializationHelper = new MqttPacketSerializationHelper(); @@ -328,17 +329,17 @@ public void Serialize_LargePacket() Assert.IsNotNull(publishPacketCopy); Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic); - CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy.PayloadSegment.ToArray()); + CollectionAssert.AreEqual(publishPacket.PayloadSequence.ToArray(), publishPacketCopy.PayloadSequence.ToArray()); // Now modify the payload and test again. - publishPacket.PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("MQTT")); + publishPacket.PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes("MQTT")); buffer = serializationHelper.Encode(publishPacket); var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket; Assert.IsNotNull(publishPacketCopy2); Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic); - CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy2.PayloadSegment.ToArray()); + CollectionAssert.AreEqual(publishPacket.PayloadSequence.ToArray(), publishPacketCopy2.PayloadSequence.ToArray()); } [TestMethod] @@ -462,7 +463,7 @@ public void SerializeV311_MqttPublishPacket() PacketIdentifier = 123, Dup = true, Retain = true, - PayloadSegment = new ArraySegment(Encoding.ASCII.GetBytes("HELLO")), + PayloadSequence = new ReadOnlySequence(Encoding.ASCII.GetBytes("HELLO")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, Topic = "A/B/C" }; diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs index 3a99ff5fb..a44b599e1 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Text; @@ -298,7 +299,7 @@ public void Serialize_Full_MqttPublishPacket_V311() PacketIdentifier = 123, Dup = true, Retain = true, - PayloadSegment = new ArraySegment(Encoding.ASCII.GetBytes("Payload")), + PayloadSequence = new ReadOnlySequence(Encoding.ASCII.GetBytes("Payload")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, Topic = "Topic", ResponseTopic = "/Response", @@ -322,7 +323,7 @@ public void Serialize_Full_MqttPublishPacket_V311() Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Retain, deserialized.Retain); - CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray()); + CollectionAssert.AreEqual(publishPacket.PayloadSequence.ToArray(), deserialized.PayloadSequence.ToArray()); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1. diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs index 1f9634379..ae17c78f6 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Text; @@ -259,7 +260,7 @@ public void Serialize_Full_MqttPublishPacket_V500() PacketIdentifier = 123, Dup = true, Retain = true, - PayloadSegment = new ArraySegment(Encoding.ASCII.GetBytes("Payload")), + PayloadSequence = new ReadOnlySequence(Encoding.ASCII.GetBytes("Payload")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, Topic = "Topic", ResponseTopic = "/Response", @@ -283,7 +284,7 @@ public void Serialize_Full_MqttPublishPacket_V500() Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Retain, deserialized.Retain); - CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray()); + CollectionAssert.AreEqual(publishPacket.PayloadSequence.ToArray(), deserialized.PayloadSequence.ToArray()); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic); diff --git a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs index 40def6acd..f7c99560f 100644 --- a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs +++ b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -273,7 +274,7 @@ public async Task Publish_And_Receive_New_Properties() Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic); Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval); CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData); - CollectionAssert.AreEqual(applicationMessage.PayloadSegment.ToArray(), receivedMessage.PayloadSegment.ToArray()); + CollectionAssert.AreEqual(applicationMessage.PayloadSequence.ToArray(), receivedMessage.PayloadSequence.ToArray()); CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties); } } diff --git a/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs b/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs index 5cb8ead61..1b5f426f0 100644 --- a/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs +++ b/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -52,6 +53,14 @@ public Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, Cancellati return _stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken); } + public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) + { + foreach (var segment in buffer) + { + await _stream.WriteAsync(segment, cancellationToken).ConfigureAwait(false); + } + } + public void Dispose() { } diff --git a/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs b/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs index f4f28f0bc..74d34f1ca 100644 --- a/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs +++ b/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.IO; using System.Linq; using System.Text; @@ -30,7 +31,7 @@ public void CreateApplicationMessage_TimeStampPayload() Assert.AreEqual("xyz", message.Topic); Assert.IsFalse(message.Retain); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "00:06:00"); + Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSequence.ToArray()), "00:06:00"); } [TestMethod] @@ -42,7 +43,7 @@ public void CreateApplicationMessage_StreamPayload() Assert.AreEqual("123", message.Topic); Assert.IsFalse(message.Retain); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "Hello"); + Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSequence.ToArray()), "Hello"); } [TestMethod] diff --git a/Source/MQTTnet.Tests/Server/General.cs b/Source/MQTTnet.Tests/Server/General.cs index 0cf7c43f6..80e406da2 100644 --- a/Source/MQTTnet.Tests/Server/General.cs +++ b/Source/MQTTnet.Tests/Server/General.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Text; @@ -306,7 +307,7 @@ public async Task Intercept_Message() var server = await testEnvironment.StartServer(); server.InterceptingPublishAsync += e => { - e.ApplicationMessage.PayloadSegment = new ArraySegment(Encoding.ASCII.GetBytes("extended")); + e.ApplicationMessage.PayloadSequence = new ReadOnlySequence(Encoding.ASCII.GetBytes("extended")); return CompletedTask.Instance; }; @@ -317,7 +318,7 @@ public async Task Intercept_Message() var isIntercepted = false; c2.ApplicationMessageReceivedAsync += e => { - isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()), StringComparison.Ordinal) == 0; + isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence.ToArray()), StringComparison.Ordinal) == 0; return CompletedTask.Instance; }; @@ -428,7 +429,7 @@ await server.InjectApplicationMessage( new MqttApplicationMessage { Topic = "/test/1", - PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes("true")), + PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes("true")), QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }) { @@ -783,7 +784,7 @@ public async Task Send_Long_Body() var client1 = await testEnvironment.ConnectClient(); client1.ApplicationMessageReceivedAsync += e => { - receivedBody = e.ApplicationMessage.PayloadSegment.ToArray(); + receivedBody = e.ApplicationMessage.PayloadSequence.ToArray(); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/Server/Session_Tests.cs b/Source/MQTTnet.Tests/Server/Session_Tests.cs index 0aed57aba..c274c0e09 100644 --- a/Source/MQTTnet.Tests/Server/Session_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Session_Tests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Linq; using System.Text; using System.Threading; @@ -283,7 +284,7 @@ public async Task Set_Session_Item() server.InterceptingPublishAsync += e => { - e.ApplicationMessage.PayloadSegment = new ArraySegment(Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty)); + e.ApplicationMessage.PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty)); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/Server/Tls_Tests.cs b/Source/MQTTnet.Tests/Server/Tls_Tests.cs index 325463bfb..9e47ef26b 100644 --- a/Source/MQTTnet.Tests/Server/Tls_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Tls_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Linq; using System.Net; using System.Security.Authentication; @@ -100,7 +101,7 @@ await firstClient.PublishAsync( new MqttApplicationMessage { Topic = "TestTopic1", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) + PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) }); await testEnvironment.Server.InjectApplicationMessage( @@ -108,7 +109,7 @@ await testEnvironment.Server.InjectApplicationMessage( new MqttApplicationMessage { Topic = "TestTopic1", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) + PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) })); certificateProvider.CurrentCertificate = CreateCertificate(secondOid); @@ -136,7 +137,7 @@ await firstClient.PublishAsync( new MqttApplicationMessage { Topic = "TestTopic2", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) + PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) }); await testEnvironment.Server.InjectApplicationMessage( @@ -144,7 +145,7 @@ await testEnvironment.Server.InjectApplicationMessage( new MqttApplicationMessage { Topic = "TestTopic2", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) + PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) })); // Ensure first client still works @@ -152,7 +153,7 @@ await firstClient.PublishAsync( new MqttApplicationMessage { Topic = "TestTopic1", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) + PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) }); await testEnvironment.Server.InjectApplicationMessage( @@ -160,7 +161,7 @@ await testEnvironment.Server.InjectApplicationMessage( new MqttApplicationMessage { Topic = "TestTopic1", - PayloadSegment = new ArraySegment(new byte[] { 1, 2, 3, 4 }) + PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) })); await Task.Delay(1000); diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 85e5b14b3..0de5d8ce1 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -228,7 +228,7 @@ public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellat _logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet); - if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation) + if (packetBuffer.Payload.Length == 0 || !AllowPacketFragmentation) { await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false); } diff --git a/Source/MQTTnet/Channel/IMqttChannel.cs b/Source/MQTTnet/Channel/IMqttChannel.cs index 2f0a6be51..d3295e042 100644 --- a/Source/MQTTnet/Channel/IMqttChannel.cs +++ b/Source/MQTTnet/Channel/IMqttChannel.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -23,4 +24,6 @@ public interface IMqttChannel : IDisposable Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, CancellationToken cancellationToken); + + Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs index 4564ddf84..5e252cef1 100644 --- a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs +++ b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs @@ -19,7 +19,7 @@ public static MqttApplicationMessage Create(MqttPublishPacket publishPacket) return new MqttApplicationMessage { Topic = publishPacket.Topic, - PayloadSegment = publishPacket.PayloadSegment, + PayloadSequence = publishPacket.PayloadSequence, QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, Retain = publishPacket.Retain, Dup = publishPacket.Dup, diff --git a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs index 4df92b36e..271b90214 100644 --- a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs +++ b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs @@ -2,63 +2,81 @@ // 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 MQTTnet.Implementations; using MQTTnet.Internal; +using System; +using System.Buffers; namespace MQTTnet.Formatter { public readonly struct MqttPacketBuffer { - static readonly ArraySegment EmptyPayload = EmptyBuffer.ArraySegment; - - public MqttPacketBuffer(ArraySegment packet, ArraySegment payload) + static readonly ReadOnlySequence EmptySequence = EmptyBuffer.ArraySequence; + + public MqttPacketBuffer(ArraySegment packet, ReadOnlySequence payload) { Packet = packet; Payload = payload; - Length = Packet.Count + Payload.Count; + if (Packet.Count + (int)Payload.Length > int.MaxValue) + { + throw new InvalidOperationException("The packet is too large."); + } + + Length = Packet.Count + (int)Payload.Length; } - + public MqttPacketBuffer(ArraySegment packet) { Packet = packet; - Payload = EmptyPayload; + Payload = EmptySequence; Length = Packet.Count; } public int Length { get; } - + public ArraySegment Packet { get; } - - public ArraySegment Payload { get; } + + public ReadOnlySequence Payload { get; } public byte[] ToArray() { - if (Payload.Count == 0) + if (Payload.Length == 0) { return Packet.ToArray(); } - var buffer = new byte[Length]; + var buffer = GC.AllocateUninitializedArray(Length); + MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count); - MqttMemoryHelper.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count); + + int offset = Packet.Count; + foreach (ReadOnlyMemory segment in Payload) + { + segment.CopyTo(buffer.AsMemory(offset)); + offset += segment.Length; + } return buffer; } - + public ArraySegment Join() { - if (Payload.Count == 0) + if (Payload.Length == 0) { return Packet; } - var buffer = new byte[Length]; + var buffer = GC.AllocateUninitializedArray(Length); + MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count); - MqttMemoryHelper.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count); + + int offset = Packet.Count; + foreach (ReadOnlyMemory segment in Payload) + { + segment.CopyTo(buffer.AsMemory(offset)); + offset += segment.Length; + } return new ArraySegment(buffer); } diff --git a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs index 9ec30fe59..94141d8a6 100644 --- a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs @@ -22,7 +22,7 @@ public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage var packet = new MqttPublishPacket { Topic = applicationMessage.Topic, - PayloadSegment = applicationMessage.PayloadSegment, + PayloadSequence = applicationMessage.PayloadSequence, QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, Retain = applicationMessage.Retain, Dup = applicationMessage.Dup, diff --git a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs index 808140b42..56549b9f5 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs @@ -2,14 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; using MQTTnet.Adapter; using MQTTnet.Exceptions; using MQTTnet.Packets; using MQTTnet.Protocol; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; namespace MQTTnet.Formatter.V3 { @@ -103,10 +104,10 @@ public MqttPacketBuffer Encode(MqttPacket packet) var remainingLength = (uint)(_bufferWriter.Length - 5); var publishPacket = packet as MqttPublishPacket; - var payloadSegment = publishPacket?.PayloadSegment; - if (payloadSegment != null) + var payloadSequence = publishPacket?.PayloadSequence; + if (payloadSequence != null) { - remainingLength += (uint)payloadSegment.Value.Count; + remainingLength += (uint)payloadSequence.Value.Length; } var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); @@ -122,9 +123,9 @@ public MqttPacketBuffer Encode(MqttPacket packet) var buffer = _bufferWriter.GetBuffer(); var firstSegment = new ArraySegment(buffer, headerOffset, _bufferWriter.Length - headerOffset); - return payloadSegment == null + return payloadSequence == null ? new MqttPacketBuffer(firstSegment) - : new MqttPacketBuffer(firstSegment, payloadSegment.Value); + : new MqttPacketBuffer(firstSegment, payloadSequence.Value); } MqttPacket DecodeConnAckPacket(ArraySegment body) @@ -278,7 +279,7 @@ MqttPacket DecodePublishPacket(ReceivedMqttPacket receivedMqttPacket) if (!_bufferReader.EndOfStream) { - packet.PayloadSegment = new ArraySegment(_bufferReader.ReadRemainingData()); + packet.PayloadSequence = new ReadOnlySequence(_bufferReader.ReadRemainingData()); } return packet; diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs index c9010cabe..68495ca2a 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using MQTTnet.Adapter; using MQTTnet.Exceptions; @@ -540,7 +541,7 @@ MqttPacket DecodePublishPacket(byte header, ArraySegment body) if (!_bufferReader.EndOfStream) { - packet.PayloadSegment = new ArraySegment(_bufferReader.ReadRemainingData()); + packet.PayloadSequence = new ReadOnlySequence(_bufferReader.ReadRemainingData()); } return packet; diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs index 8e6804f3c..750b9dcd4 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs @@ -30,18 +30,19 @@ public MqttPacketBuffer Encode(MqttPacket packet) } // Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes) - _bufferWriter.Reset(5); - _bufferWriter.Seek(5); + const int ReservedHeaderSize = 5; + _bufferWriter.Reset(ReservedHeaderSize); + _bufferWriter.Seek(ReservedHeaderSize); var fixedHeader = EncodePacket(packet); - var remainingLength = (uint)_bufferWriter.Length - 5; + var remainingLength = (uint)_bufferWriter.Length - ReservedHeaderSize; var publishPacket = packet as MqttPublishPacket; - var payloadSegment = publishPacket?.PayloadSegment; + var payloadSequence = publishPacket?.PayloadSequence; - if (payloadSegment != null) + if (payloadSequence != null) { - remainingLength += (uint)payloadSegment.Value.Count; + remainingLength += (uint)payloadSequence.Value.Length; } var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); @@ -57,9 +58,9 @@ public MqttPacketBuffer Encode(MqttPacket packet) var buffer = _bufferWriter.GetBuffer(); var firstSegment = new ArraySegment(buffer, headerOffset, _bufferWriter.Length - headerOffset); - return payloadSegment == null + return payloadSequence == null ? new MqttPacketBuffer(firstSegment) - : new MqttPacketBuffer(firstSegment, payloadSegment.Value); + : new MqttPacketBuffer(firstSegment, payloadSequence.Value); } byte EncodeAuthPacket(MqttAuthPacket packet) diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 1f1bbf85f..32a6f0a3f 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.IO; using System.Net; using System.Net.Security; @@ -278,6 +279,39 @@ public async Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, Canc } } + public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + var stream = _stream; + + if (stream == null) + { + throw new MqttCommunicationException("The TCP connection is closed."); + } + + foreach (var segment in buffer) + { + await stream.WriteAsync(segment, cancellationToken).ConfigureAwait(false); + } + } + catch (ObjectDisposedException) + { + throw new MqttCommunicationException("The TCP connection is closed."); + } + catch (IOException exception) + { + if (exception.InnerException is SocketException socketException) + { + ExceptionDispatchInfo.Capture(socketException).Throw(); + } + + throw; + } + } + X509Certificate InternalUserCertificateSelectionCallback( object sender, string targetHost, diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index 341ea8afb..a40a1137d 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Net; using System.Net.WebSockets; using System.Security.Cryptography.X509Certificates; @@ -109,6 +110,38 @@ public async Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, Canc await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false); } + public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + +#if NET5_0_OR_GREATER + // MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is received the recipient MUST close the Network Connection [MQTT-6.0.0-1]. + // A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2]. + long length = buffer.Length; + foreach (var segment in buffer) + { + length -= segment.Length; + bool endOfPacket = isEndOfPacket && length == 0; + await _webSocket.SendAsync(segment, WebSocketMessageType.Binary, endOfPacket, cancellationToken).ConfigureAwait(false); + } +#else + // The lock is required because the client will throw an exception if _SendAsync_ is + // called from multiple threads at the same time. But this issue only happens with several + // framework versions. + if (_sendLock == null) + { + return; + } + + using (await _sendLock.EnterAsync(cancellationToken).ConfigureAwait(false)) + { + await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false); + } +#endif + } + + + void Cleanup() { _sendLock?.Dispose(); diff --git a/Source/MQTTnet/Internal/EmptyBuffer.cs b/Source/MQTTnet/Internal/EmptyBuffer.cs index 2351264dd..b2bb2e62e 100644 --- a/Source/MQTTnet/Internal/EmptyBuffer.cs +++ b/Source/MQTTnet/Internal/EmptyBuffer.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; namespace MQTTnet.Internal { @@ -11,5 +12,6 @@ public static class EmptyBuffer public static readonly byte[] Array = System.Array.Empty(); public static readonly ArraySegment ArraySegment = new ArraySegment(Array, 0, 0); + public static readonly ReadOnlySequence ArraySequence = new ReadOnlySequence(); } } \ No newline at end of file diff --git a/Source/MQTTnet/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs index 204a71576..102e6e53e 100644 --- a/Source/MQTTnet/MqttApplicationMessage.cs +++ b/Source/MQTTnet/MqttApplicationMessage.cs @@ -6,6 +6,7 @@ using MQTTnet.Packets; using MQTTnet.Protocol; using System; +using System.Buffers; using System.Collections.Generic; namespace MQTTnet @@ -50,9 +51,9 @@ public sealed class MqttApplicationMessage public uint MessageExpiryInterval { get; set; } /// - /// Get or set ArraySegment style of Payload. + /// Get or set ReadOnlySequence style of Payload. /// - public ArraySegment PayloadSegment { get; set; } = EmptyBuffer.ArraySegment; + public ReadOnlySequence PayloadSequence { get; set; } /// /// Gets or sets the payload format indicator. diff --git a/Source/MQTTnet/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs index 316ab6024..bc631a70a 100644 --- a/Source/MQTTnet/MqttApplicationMessageBuilder.cs +++ b/Source/MQTTnet/MqttApplicationMessageBuilder.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -22,7 +23,7 @@ public sealed class MqttApplicationMessageBuilder uint _messageExpiryInterval; MqttPayloadFormatIndicator _payloadFormatIndicator; - ArraySegment _payloadSegment; + ReadOnlySequence _payloadSequence; MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; string _responseTopic; bool _retain; @@ -41,7 +42,7 @@ public MqttApplicationMessage Build() var applicationMessage = new MqttApplicationMessage { Topic = _topic, - PayloadSegment = _payloadSegment, + PayloadSequence = _payloadSequence, QualityOfServiceLevel = _qualityOfServiceLevel, Retain = _retain, ContentType = _contentType, @@ -89,13 +90,19 @@ public MqttApplicationMessageBuilder WithMessageExpiryInterval(uint messageExpir public MqttApplicationMessageBuilder WithPayload(byte[] payload) { - _payloadSegment = payload == null || payload.Length == 0 ? EmptyBuffer.ArraySegment : new ArraySegment(payload); + _payloadSequence = payload == null || payload.Length == 0 ? EmptyBuffer.ArraySequence : new ReadOnlySequence(payload); return this; } public MqttApplicationMessageBuilder WithPayload(ArraySegment payloadSegment) { - _payloadSegment = payloadSegment; + _payloadSequence = new ReadOnlySequence(payloadSegment); + return this; + } + + public MqttApplicationMessageBuilder WithPayload(ReadOnlySequence payloadSequence) + { + _payloadSequence = payloadSequence; return this; } @@ -170,13 +177,20 @@ public MqttApplicationMessageBuilder WithPayloadFormatIndicator(MqttPayloadForma public MqttApplicationMessageBuilder WithPayloadSegment(ArraySegment payloadSegment) { - _payloadSegment = payloadSegment; + _payloadSequence = new ReadOnlySequence(payloadSegment); + return this; + } + + public MqttApplicationMessageBuilder WithPayloadSegment(ReadOnlySequence payloadSequence) + { + _payloadSequence = payloadSequence; return this; } public MqttApplicationMessageBuilder WithPayloadSegment(ReadOnlyMemory payloadSegment) { - return MemoryMarshal.TryGetArray(payloadSegment, out var segment) ? WithPayloadSegment(segment) : WithPayload(payloadSegment.ToArray()); + _payloadSequence = new ReadOnlySequence(payloadSegment); + return this; } /// diff --git a/Source/MQTTnet/MqttApplicationMessageExtensions.cs b/Source/MQTTnet/MqttApplicationMessageExtensions.cs index c2f82dd8d..e409d425a 100644 --- a/Source/MQTTnet/MqttApplicationMessageExtensions.cs +++ b/Source/MQTTnet/MqttApplicationMessageExtensions.cs @@ -4,7 +4,6 @@ using System; using System.Text; -using MQTTnet.Internal; namespace MQTTnet; @@ -17,17 +16,11 @@ public static string ConvertPayloadToString(this MqttApplicationMessage applicat throw new ArgumentNullException(nameof(applicationMessage)); } - if (applicationMessage.PayloadSegment == EmptyBuffer.ArraySegment) + if (applicationMessage.PayloadSequence.Length == 0) { return null; } - if (applicationMessage.PayloadSegment.Array == null) - { - return null; - } - - var payloadSegment = applicationMessage.PayloadSegment; - return Encoding.UTF8.GetString(payloadSegment.Array, payloadSegment.Offset, payloadSegment.Count); + return Encoding.UTF8.GetString(applicationMessage.PayloadSequence); } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPublishPacket.cs b/Source/MQTTnet/Packets/MqttPublishPacket.cs index c07dddda6..5cc5d5843 100644 --- a/Source/MQTTnet/Packets/MqttPublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttPublishPacket.cs @@ -2,9 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using MQTTnet.Protocol; using System; +using System.Buffers; using System.Collections.Generic; -using MQTTnet.Protocol; namespace MQTTnet.Packets; @@ -20,7 +21,7 @@ public sealed class MqttPublishPacket : MqttPacketWithIdentifier public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified; - public ArraySegment PayloadSegment { get; set; } + public ReadOnlySequence PayloadSequence { get; set; } public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } = MqttQualityOfServiceLevel.AtMostOnce; @@ -39,6 +40,6 @@ public sealed class MqttPublishPacket : MqttPacketWithIdentifier public override string ToString() { return - $"Publish: [Topic={Topic}] [PayloadLength={PayloadSegment.Count}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]"; + $"Publish: [Topic={Topic}] [PayloadLength={PayloadSequence.Length}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]"; } } \ No newline at end of file From d7a588b669f765917ecdddfaed5e824a64a487a2 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Wed, 5 Jun 2024 16:50:22 +0200 Subject: [PATCH 02/19] fix tests --- Source/MQTTnet.AspnetCore/MqttConnectionContext.cs | 2 +- Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs | 2 +- Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs index d118e71a8..b7217b2ee 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -236,7 +236,7 @@ static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) int offset = 0; foreach (var segment in buffer.Payload) { - segment.Span.CopyTo(span.Slice(offset, buffer.Packet.Count)); + segment.Span.CopyTo(span.Slice(offset)); offset += segment.Length; } diff --git a/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs index ebfb7db15..433138caf 100644 --- a/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs @@ -64,7 +64,7 @@ static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) int offset = 0; foreach(var segment in buffer.Payload) { - segment.Span.CopyTo(span.Slice(offset, buffer.Packet.Count)); + segment.Span.CopyTo(span.Slice(offset)); offset += segment.Length; } diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index c0dc38377..1f8acbb53 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -158,7 +158,7 @@ static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence { int offset = 0; // TODO - return true; + return false; } } } \ No newline at end of file From 698f5ac8649157bc7d8f92b9226225f6cb7b4412 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Thu, 6 Jun 2024 07:51:26 +0200 Subject: [PATCH 03/19] simnplify getstring --- Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs | 2 +- Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs | 4 ++-- Source/MQTTnet.Tests/Server/General.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs index 5fdf185cd..a46856e77 100644 --- a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs @@ -509,7 +509,7 @@ public async Task Publish_QoS_1_In_ApplicationMessageReceiveHandler() client2.ApplicationMessageReceivedAsync += e => { - client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence.ToArray())); + client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence)); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs b/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs index 74d34f1ca..3da3ec8db 100644 --- a/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs +++ b/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs @@ -31,7 +31,7 @@ public void CreateApplicationMessage_TimeStampPayload() Assert.AreEqual("xyz", message.Topic); Assert.IsFalse(message.Retain); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSequence.ToArray()), "00:06:00"); + Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSequence), "00:06:00"); } [TestMethod] @@ -43,7 +43,7 @@ public void CreateApplicationMessage_StreamPayload() Assert.AreEqual("123", message.Topic); Assert.IsFalse(message.Retain); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSequence.ToArray()), "Hello"); + Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSequence), "Hello"); } [TestMethod] diff --git a/Source/MQTTnet.Tests/Server/General.cs b/Source/MQTTnet.Tests/Server/General.cs index 80e406da2..3cceffecc 100644 --- a/Source/MQTTnet.Tests/Server/General.cs +++ b/Source/MQTTnet.Tests/Server/General.cs @@ -318,7 +318,7 @@ public async Task Intercept_Message() var isIntercepted = false; c2.ApplicationMessageReceivedAsync += e => { - isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence.ToArray()), StringComparison.Ordinal) == 0; + isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence), StringComparison.Ordinal) == 0; return CompletedTask.Instance; }; From 7e2ba96d793c5e2e27003528802883861b750ec4 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Thu, 6 Jun 2024 07:59:15 +0200 Subject: [PATCH 04/19] fix bug --- Source/MQTTnet/Formatter/MqttPacketBuffer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs index 271b90214..e1c87b75e 100644 --- a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs +++ b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs @@ -17,7 +17,7 @@ public MqttPacketBuffer(ArraySegment packet, ReadOnlySequence payloa Packet = packet; Payload = payload; - if (Packet.Count + (int)Payload.Length > int.MaxValue) + if (Packet.Count + Payload.Length > int.MaxValue) { throw new InvalidOperationException("The packet is too large."); } From 72431bfe2495a0b31f5bab1a6a162b562529ab14 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Thu, 6 Jun 2024 08:37:55 +0200 Subject: [PATCH 05/19] implement sequenceequal --- .../Internal/MqttRetainedMessagesManager.cs | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index 1f8acbb53..79f267cd8 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -156,9 +156,61 @@ static bool SequenceEqual(ArraySegment source, ArraySegment target) static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence target) { - int offset = 0; - // TODO - return false; + if (source.Length != target.Length) + { + return false; + } + + long comparedLength = 0; + long length = source.Length; + + int offsetSource = 0; + int offsetTarget = 0; + + var sourceEnumerator = source.GetEnumerator(); + var targetEnumerator = target.GetEnumerator(); + + ReadOnlyMemory sourceSegment = sourceEnumerator.Current; + ReadOnlyMemory targetSegment = targetEnumerator.Current; + + while (true) + { + int compareLength = Math.Min(sourceSegment.Length, targetSegment.Length); + + if (!sourceSegment.Span.Slice(offsetSource, compareLength).SequenceEqual(targetSegment.Span.Slice(offsetTarget, compareLength))) + { + return false; + } + + comparedLength += compareLength; + if (comparedLength >= length) + { + return true; + } + + offsetSource += compareLength; + offsetTarget += compareLength; + if (offsetSource >= sourceSegment.Length) + { + if (!sourceEnumerator.MoveNext()) + { + return false; + } + + sourceSegment = sourceEnumerator.Current; + offsetSource = 0; + } + if (offsetTarget >= targetSegment.Length) + { + if (!targetEnumerator.MoveNext()) + { + return false; + } + + targetSegment = targetEnumerator.Current; + offsetTarget = 0; + } + } } } } \ No newline at end of file From dc6b05fa6e5fa1d94f0b419721cd2786e43fca02 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Thu, 6 Jun 2024 08:43:21 +0200 Subject: [PATCH 06/19] fix sequenceeqal --- .../Internal/MqttRetainedMessagesManager.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index 79f267cd8..b679125cf 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -164,8 +164,8 @@ static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence long comparedLength = 0; long length = source.Length; - int offsetSource = 0; - int offsetTarget = 0; + int sourceOffset = 0; + int targetOffset = 0; var sourceEnumerator = source.GetEnumerator(); var targetEnumerator = target.GetEnumerator(); @@ -175,9 +175,10 @@ static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence while (true) { - int compareLength = Math.Min(sourceSegment.Length, targetSegment.Length); + int compareLength = Math.Min(sourceSegment.Length - sourceOffset, targetSegment.Length - targetOffset); - if (!sourceSegment.Span.Slice(offsetSource, compareLength).SequenceEqual(targetSegment.Span.Slice(offsetTarget, compareLength))) + if (compareLength > 0 && + !sourceSegment.Span.Slice(sourceOffset, compareLength).SequenceEqual(targetSegment.Span.Slice(targetOffset, compareLength))) { return false; } @@ -188,9 +189,8 @@ static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence return true; } - offsetSource += compareLength; - offsetTarget += compareLength; - if (offsetSource >= sourceSegment.Length) + sourceOffset += compareLength; + if (sourceOffset >= sourceSegment.Length) { if (!sourceEnumerator.MoveNext()) { @@ -198,9 +198,11 @@ static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence } sourceSegment = sourceEnumerator.Current; - offsetSource = 0; + sourceOffset = 0; } - if (offsetTarget >= targetSegment.Length) + + targetOffset += compareLength; + if (targetOffset >= targetSegment.Length) { if (!targetEnumerator.MoveNext()) { @@ -208,7 +210,7 @@ static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence } targetSegment = targetEnumerator.Current; - offsetTarget = 0; + targetOffset = 0; } } } From af37eb6d09d3b769da13293064a1aed7af160758 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Mon, 10 Jun 2024 14:26:28 +0200 Subject: [PATCH 07/19] simplify WriteAsync --- .../ChannelAdapterBenchmark.cs | 19 +++--------- .../MqttTcpChannelBenchmark.cs | 13 ++++---- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 7 +---- .../MqttPacketSerialization_V3_Tests.cs | 15 +++++----- .../Mockups/MemoryMqttChannel.cs | 5 ---- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 5 ++-- Source/MQTTnet/Channel/IMqttChannel.cs | 2 -- Source/MQTTnet/Formatter/MqttPacketBuffer.cs | 13 +------- .../MQTTnet/Implementations/MqttTcpChannel.cs | 30 ------------------- .../Implementations/MqttWebSocketChannel.cs | 26 ---------------- Source/MQTTnet/MqttApplicationMessage.cs | 2 -- 11 files changed, 23 insertions(+), 114 deletions(-) diff --git a/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs index 3b6a7d858..98caee7f7 100644 --- a/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -2,15 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.IO; -using System.Threading; using BenchmarkDotNet.Attributes; using MQTTnet.Adapter; using MQTTnet.Diagnostics; using MQTTnet.Formatter; using MQTTnet.Packets; using MQTTnet.Tests.Mockups; +using System.Buffers; +using System.IO; +using System.Threading; namespace MQTTnet.Benchmarks { @@ -58,7 +58,7 @@ public void Setup() var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); - var serializedPacket = Join(serializer.Encode(_packet).Join()); + var serializedPacket = serializer.Encode(_packet).ToArray(); _iterations = 10000; @@ -75,16 +75,5 @@ public void Setup() _channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger()); } - - static byte[] Join(params ArraySegment[] chunks) - { - var buffer = new MemoryStream(); - foreach (var chunk in chunks) - { - buffer.Write(chunk.Array, chunk.Offset, chunk.Count); - } - - return buffer.ToArray(); - } } } \ No newline at end of file diff --git a/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs index 1dc822c40..99dbaede6 100644 --- a/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs @@ -2,10 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using MQTTnet.Channel; @@ -14,6 +10,10 @@ using MQTTnet.Implementations; using MQTTnet.Server; using MQTTnet.Server.Internal.Adapter; +using System.Buffers; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; namespace MQTTnet.Benchmarks; @@ -64,12 +64,13 @@ async Task ReadAsync(int iterations, int size) { await Task.Yield(); + var buffer = new byte[size]; var expected = iterations * size; long read = 0; while (read < expected) { - var readResult = await _clientChannel.ReadAsync(new byte[size], 0, size, CancellationToken.None).ConfigureAwait(false); + var readResult = await _clientChannel.ReadAsync(buffer, 0, size, CancellationToken.None).ConfigureAwait(false); read += readResult; } } @@ -78,7 +79,7 @@ async Task WriteAsync(int iterations, int size) { await Task.Yield(); - var buffer = new ArraySegment(new byte[size]); + var buffer = new ReadOnlySequence(new byte[size]); for (var i = 0; i < iterations; i++) { diff --git a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs index 77f9eec0b..c430e38c7 100644 --- a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -38,7 +38,7 @@ public void GlobalSetup() _bufferWriter = new MqttBufferWriter(4096, 65535); _serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311); - _serializedPacket = _serializer.Encode(_packet).Join(); + _serializedPacket = _serializer.Encode(_packet).ToArray(); } [Benchmark] @@ -105,11 +105,6 @@ public Task ReadAsync(byte[] buffer, int offset, int count, CancellationTok return Task.FromResult(count); } - public Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, CancellationToken cancellationToken) - { - throw new NotSupportedException(); - } - public Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) { throw new NotSupportedException(); diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs index a44b599e1..506c54c91 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs @@ -2,16 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Linq; -using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Exceptions; using MQTTnet.Formatter; using MQTTnet.Packets; using MQTTnet.Protocol; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; namespace MQTTnet.Tests.Formatter { @@ -78,7 +77,7 @@ public void Serialize_Full_MqttConnAckPacket_V311() Assert.AreEqual(false, deserialized.WildcardSubscriptionAvailable); Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1 } - + [TestMethod] public void Serialize_Full_MqttConnAckPacket_V310() { @@ -179,7 +178,7 @@ public void Serialize_Full_MqttConnectPacket_V311() Assert.AreEqual(connectPacket.ClientId, deserialized.ClientId); CollectionAssert.AreEqual(null, deserialized.AuthenticationData); // Not supported in v3.1.1 Assert.AreEqual(null, deserialized.AuthenticationMethod); // Not supported in v3.1.1 - Assert.AreEqual(connectPacket.CleanSession, deserialized.CleanSession); + Assert.AreEqual(connectPacket.CleanSession, deserialized.CleanSession); Assert.AreEqual(0L, deserialized.ReceiveMaximum); // Not supported in v3.1.1 Assert.AreEqual(connectPacket.WillFlag, deserialized.WillFlag); Assert.AreEqual(connectPacket.WillTopic, deserialized.WillTopic); @@ -400,7 +399,7 @@ public void Serialize_Full_MqttSubAckPacket_V311() }; var deserialized = MqttPacketSerializationHelper.EncodeAndDecodePacket(subAckPacket, MqttProtocolVersion.V311); - + Assert.AreEqual(subAckPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(null, deserialized.ReasonString); // Not supported in v3.1.1 Assert.AreEqual(subAckPacket.ReasonCodes.Count, deserialized.ReasonCodes.Count); diff --git a/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs b/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs index 1b5f426f0..32f6f0c1b 100644 --- a/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs +++ b/Source/MQTTnet.Tests/Mockups/MemoryMqttChannel.cs @@ -48,11 +48,6 @@ public Task ReadAsync(byte[] buffer, int offset, int count, CancellationTok return _stream.ReadAsync(buffer, offset, count, cancellationToken); } - public Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, CancellationToken cancellationToken) - { - return _stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken); - } - public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) { foreach (var segment in buffer) diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 0de5d8ce1..4a2624ff7 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.IO; using System.Net.Sockets; using System.Runtime.InteropServices; @@ -230,11 +231,11 @@ public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellat if (packetBuffer.Payload.Length == 0 || !AllowPacketFragmentation) { - await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false); + await _channel.WriteAsync(new ReadOnlySequence(packetBuffer.ToArray()), true, cancellationToken).ConfigureAwait(false); } else { - await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false); + await _channel.WriteAsync(new ReadOnlySequence(packetBuffer.Packet), false, cancellationToken).ConfigureAwait(false); await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false); } diff --git a/Source/MQTTnet/Channel/IMqttChannel.cs b/Source/MQTTnet/Channel/IMqttChannel.cs index d3295e042..55cd48ba0 100644 --- a/Source/MQTTnet/Channel/IMqttChannel.cs +++ b/Source/MQTTnet/Channel/IMqttChannel.cs @@ -23,7 +23,5 @@ public interface IMqttChannel : IDisposable Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); - Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, CancellationToken cancellationToken); - Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs index e1c87b75e..d5cdfbfe5 100644 --- a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs +++ b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs @@ -67,18 +67,7 @@ public ArraySegment Join() return Packet; } - var buffer = GC.AllocateUninitializedArray(Length); - - MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count); - - int offset = Packet.Count; - foreach (ReadOnlyMemory segment in Payload) - { - segment.CopyTo(buffer.AsMemory(offset)); - offset += segment.Length; - } - - return new ArraySegment(buffer); + return new ArraySegment(ToArray()); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Implementations/MqttTcpChannel.cs b/Source/MQTTnet/Implementations/MqttTcpChannel.cs index 32a6f0a3f..5f8ac61bd 100644 --- a/Source/MQTTnet/Implementations/MqttTcpChannel.cs +++ b/Source/MQTTnet/Implementations/MqttTcpChannel.cs @@ -249,36 +249,6 @@ public async Task ReadAsync(byte[] buffer, int offset, int count, Cancellat } } - public async Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - var stream = _stream; - - if (stream == null) - { - throw new MqttCommunicationException("The TCP connection is closed."); - } - - await stream.WriteAsync(buffer.AsMemory(), cancellationToken).ConfigureAwait(false); - } - catch (ObjectDisposedException) - { - throw new MqttCommunicationException("The TCP connection is closed."); - } - catch (IOException exception) - { - if (exception.InnerException is SocketException socketException) - { - ExceptionDispatchInfo.Capture(socketException).Throw(); - } - - throw; - } - } - public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs index a40a1137d..a8c321734 100644 --- a/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs +++ b/Source/MQTTnet/Implementations/MqttWebSocketChannel.cs @@ -101,20 +101,10 @@ public async Task ReadAsync(byte[] buffer, int offset, int count, Cancellat return response.Count; } - public async Task WriteAsync(ArraySegment buffer, bool isEndOfPacket, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - // MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is received the recipient MUST close the Network Connection [MQTT-6.0.0-1]. - // A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2]. - await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false); - } - public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); -#if NET5_0_OR_GREATER // MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is received the recipient MUST close the Network Connection [MQTT-6.0.0-1]. // A single WebSocket data frame can contain multiple or partial MQTT Control Packets. The receiver MUST NOT assume that MQTT Control Packets are aligned on WebSocket frame boundaries [MQTT-6.0.0-2]. long length = buffer.Length; @@ -124,24 +114,8 @@ public async Task WriteAsync(ReadOnlySequence buffer, bool isEndOfPacket, bool endOfPacket = isEndOfPacket && length == 0; await _webSocket.SendAsync(segment, WebSocketMessageType.Binary, endOfPacket, cancellationToken).ConfigureAwait(false); } -#else - // The lock is required because the client will throw an exception if _SendAsync_ is - // called from multiple threads at the same time. But this issue only happens with several - // framework versions. - if (_sendLock == null) - { - return; - } - - using (await _sendLock.EnterAsync(cancellationToken).ConfigureAwait(false)) - { - await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false); - } -#endif } - - void Cleanup() { _sendLock?.Dispose(); diff --git a/Source/MQTTnet/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs index 102e6e53e..230152805 100644 --- a/Source/MQTTnet/MqttApplicationMessage.cs +++ b/Source/MQTTnet/MqttApplicationMessage.cs @@ -2,10 +2,8 @@ // 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.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; -using System; using System.Buffers; using System.Collections.Generic; From 67bf799a683cfcd187ea03e2c0f3835cd6858c3e Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Mon, 10 Jun 2024 19:53:15 +0200 Subject: [PATCH 08/19] rename payloadsequence to payload --- .../Server_Retained_Messages_Samples.cs | 4 ++-- .../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 2 +- .../Formatter/MqttPublishPacketFactory.cs | 4 ++-- .../Internal/MqttRetainedMessagesManager.cs | 6 ++--- Source/MQTTnet.Server/MqttServerExtensions.cs | 2 +- Source/MQTTnet.TestApp/ClientTest.cs | 4 ++-- Source/MQTTnet.TestApp/PerformanceTest.cs | 8 +++---- Source/MQTTnet.TestApp/ServerTest.cs | 6 ++--- .../ASP/MqttConnectionContextTest.cs | 2 +- .../MQTTnet.Tests/ASP/ReaderExtensionsTest.cs | 2 +- .../ManagedMqttClient_Tests.cs | 6 ++--- .../Clients/MqttClient/MqttClient_Tests.cs | 4 ++-- ...MqttPacketSerialization_V3_Binary_Tests.cs | 12 +++++----- .../MqttPacketSerialization_V3_Tests.cs | 4 ++-- .../MqttPacketSerialization_V5_Tests.cs | 4 ++-- Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs | 2 +- .../MqttApplicationMessageBuilder_Tests.cs | 4 ++-- Source/MQTTnet.Tests/Server/General.cs | 8 +++---- Source/MQTTnet.Tests/Server/Session_Tests.cs | 2 +- Source/MQTTnet.Tests/Server/Tls_Tests.cs | 12 +++++----- .../MqttApplicationMessageFactory.cs | 2 +- Source/MQTTnet/Formatter/MqttPacketBuffer.cs | 4 +--- .../Formatter/MqttPublishPacketFactory.cs | 2 +- .../Formatter/V3/MqttV3PacketFormatter.cs | 12 +++++----- .../Formatter/V5/MqttV5PacketDecoder.cs | 2 +- .../Formatter/V5/MqttV5PacketEncoder.cs | 10 ++++----- Source/MQTTnet/Internal/EmptyBuffer.cs | 1 - Source/MQTTnet/MqttApplicationMessage.cs | 2 +- .../MQTTnet/MqttApplicationMessageBuilder.cs | 22 +++++++++---------- .../MqttApplicationMessageExtensions.cs | 4 ++-- Source/MQTTnet/Packets/MqttPublishPacket.cs | 4 ++-- 31 files changed, 80 insertions(+), 83 deletions(-) diff --git a/Samples/Server/Server_Retained_Messages_Samples.cs b/Samples/Server/Server_Retained_Messages_Samples.cs index e78dac37b..c309ddfd8 100644 --- a/Samples/Server/Server_Retained_Messages_Samples.cs +++ b/Samples/Server/Server_Retained_Messages_Samples.cs @@ -113,7 +113,7 @@ public static MqttRetainedMessageModel Create(MqttApplicationMessage message) // Create a copy of the buffer from the payload segment because // it cannot be serialized and deserialized with the JSON serializer. - Payload = message.PayloadSequence.ToArray(), + Payload = message.Payload.ToArray(), UserProperties = message.UserProperties, ResponseTopic = message.ResponseTopic, CorrelationData = message.CorrelationData, @@ -131,7 +131,7 @@ public MqttApplicationMessage ToApplicationMessage() return new MqttApplicationMessage { Topic = Topic, - PayloadSequence = new ReadOnlySequence(Payload ?? Array.Empty()), + Payload = Payload != null ? new ReadOnlySequence(Payload) : ReadOnlySequence.Empty, PayloadFormatIndicator = PayloadFormatIndicator, ResponseTopic = ResponseTopic, CorrelationData = CorrelationData, diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index 0c20fbdd2..eaad21855 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -133,7 +133,7 @@ Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventAr return CompletedTask.Instance; } - var payloadBuffer = eventArgs.ApplicationMessage.PayloadSequence.ToArray(); + var payloadBuffer = eventArgs.ApplicationMessage.Payload.ToArray(); awaitable.TrySetResult(payloadBuffer); // Set this message to handled to that other code can avoid execution etc. diff --git a/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs index 53fa396e0..1e2db79e4 100644 --- a/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs @@ -31,7 +31,7 @@ public static MqttPublishPacket Create(MqttConnectPacket connectPacket) var packet = new MqttPublishPacket { Topic = connectPacket.WillTopic, - PayloadSequence = new ReadOnlySequence(willMessageBuffer), + Payload = new ReadOnlySequence(willMessageBuffer), QualityOfServiceLevel = connectPacket.WillQoS, Retain = connectPacket.WillRetain, ContentType = connectPacket.WillContentType, @@ -57,7 +57,7 @@ public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage var packet = new MqttPublishPacket { Topic = applicationMessage.Topic, - PayloadSequence = applicationMessage.PayloadSequence, + Payload = applicationMessage.Payload, QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, Retain = applicationMessage.Retain, Dup = applicationMessage.Dup, diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index b679125cf..a2588bfe8 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -66,8 +66,8 @@ public async Task UpdateMessage(string clientId, MqttApplicationMessage applicat lock (_messages) { - var payloadSequence = applicationMessage.PayloadSequence; - var hasPayload = payloadSequence.Length > 0; + var payload = applicationMessage.Payload; + var hasPayload = payload.Length > 0; if (!hasPayload) { @@ -83,7 +83,7 @@ public async Task UpdateMessage(string clientId, MqttApplicationMessage applicat } else { - if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !SequenceEqual(existingMessage.PayloadSequence, payloadSequence)) + if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !SequenceEqual(existingMessage.Payload, payload)) { _messages[applicationMessage.Topic] = applicationMessage; saveIsRequired = true; diff --git a/Source/MQTTnet.Server/MqttServerExtensions.cs b/Source/MQTTnet.Server/MqttServerExtensions.cs index 580e9559f..d7ef08a8a 100644 --- a/Source/MQTTnet.Server/MqttServerExtensions.cs +++ b/Source/MQTTnet.Server/MqttServerExtensions.cs @@ -50,7 +50,7 @@ public static Task InjectApplicationMessage( new MqttApplicationMessage { Topic = topic, - PayloadSequence = new ReadOnlySequence(payloadBuffer), + Payload = new ReadOnlySequence(payloadBuffer), QualityOfServiceLevel = qualityOfServiceLevel, Retain = retain })); diff --git a/Source/MQTTnet.TestApp/ClientTest.cs b/Source/MQTTnet.TestApp/ClientTest.cs index db893f3eb..3abc6230c 100644 --- a/Source/MQTTnet.TestApp/ClientTest.cs +++ b/Source/MQTTnet.TestApp/ClientTest.cs @@ -35,9 +35,9 @@ public static async Task RunAsync() client.ApplicationMessageReceivedAsync += e => { var payloadText = string.Empty; - if (e.ApplicationMessage.PayloadSequence.Length > 0) + if (e.ApplicationMessage.Payload.Length > 0) { - payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence); + payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); } Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); diff --git a/Source/MQTTnet.TestApp/PerformanceTest.cs b/Source/MQTTnet.TestApp/PerformanceTest.cs index 634700d94..9c27bdf33 100644 --- a/Source/MQTTnet.TestApp/PerformanceTest.cs +++ b/Source/MQTTnet.TestApp/PerformanceTest.cs @@ -201,7 +201,7 @@ static MqttApplicationMessage CreateMessage() return new MqttApplicationMessage { Topic = "A/B/C", - PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes(Payload)), + Payload = new ReadOnlySequence(Encoding.UTF8.GetBytes(Payload)), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }; } @@ -234,7 +234,7 @@ public static async Task RunQoS2Test() var message = new MqttApplicationMessage { Topic = "A/B/C", - PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes("Hello World")), + Payload = new ReadOnlySequence(Encoding.UTF8.GetBytes("Hello World")), QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }; @@ -285,7 +285,7 @@ public static async Task RunQoS1Test() var message = new MqttApplicationMessage { Topic = "A/B/C", - PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes("Hello World")), + Payload = new ReadOnlySequence(Encoding.UTF8.GetBytes("Hello World")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }; @@ -336,7 +336,7 @@ public static async Task RunQoS0Test() var message = new MqttApplicationMessage { Topic = "A/B/C", - PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes("Hello World")), + Payload = new ReadOnlySequence(Encoding.UTF8.GetBytes("Hello World")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }; diff --git a/Source/MQTTnet.TestApp/ServerTest.cs b/Source/MQTTnet.TestApp/ServerTest.cs index c840834b4..dbca99c81 100644 --- a/Source/MQTTnet.TestApp/ServerTest.cs +++ b/Source/MQTTnet.TestApp/ServerTest.cs @@ -102,7 +102,7 @@ public static async Task RunAsync() { // Replace the payload with the timestamp. But also extending a JSON // based payload with the timestamp is a suitable use case. - e.ApplicationMessage.PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"))); + e.ApplicationMessage.Payload = new ReadOnlySequence(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"))); } if (e.ApplicationMessage.Topic == "not_allowed_topic") @@ -146,9 +146,9 @@ public static async Task RunAsync() mqttServer.InterceptingPublishAsync += e => { var payloadText = string.Empty; - if (e.ApplicationMessage.PayloadSequence.Length > 0) + if (e.ApplicationMessage.Payload.Length > 0) { - payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence); + payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); } MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta); diff --git a/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs b/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs index 385bfa63a..d9400be1d 100644 --- a/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs +++ b/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs @@ -100,7 +100,7 @@ public async Task TestLargePacket() connection.Transport = pipe; var ctx = new MqttConnectionContext(serializer, connection); - await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSequence = new ReadOnlySequence(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false); + await ctx.SendPacketAsync(new MqttPublishPacket { Payload = new ReadOnlySequence(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false); var readResult = await pipe.Send.Reader.ReadAsync(); Assert.IsTrue(readResult.Buffer.Length > 20000); diff --git a/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs b/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs index 91f4b0119..3f798abfd 100644 --- a/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs +++ b/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs @@ -18,7 +18,7 @@ public void TestTryDeserialize() { var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); - var buffer = serializer.Encode(new MqttPublishPacket { Topic = "a", PayloadSequence = new ReadOnlySequence(new byte[5]) }).Join(); + var buffer = serializer.Encode(new MqttPublishPacket { Topic = "a", Payload = new ReadOnlySequence(new byte[5]) }).Join(); var sequence = new ReadOnlySequence(buffer.Array, buffer.Offset, buffer.Count); diff --git a/Source/MQTTnet.Tests/Clients/ManagedMqttClient/ManagedMqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/ManagedMqttClient/ManagedMqttClient_Tests.cs index 367f13f7a..c6b816191 100644 --- a/Source/MQTTnet.Tests/Clients/ManagedMqttClient/ManagedMqttClient_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/ManagedMqttClient/ManagedMqttClient_Tests.cs @@ -299,7 +299,7 @@ public async Task Publish_Does_Not_Hang_On_Server_Error() await managedClient.EnqueueAsync( new MqttApplicationMessage - { Topic = topic, PayloadSequence = new ReadOnlySequence(new byte[] { 1 }), Retain = true, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); + { Topic = topic, Payload = new ReadOnlySequence(new byte[] { 1 }), Retain = true, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); var timeoutTask = Task.Delay(testTimeout); @@ -609,7 +609,7 @@ public async Task Subscriptions_Are_Published_Immediately() var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval); var sendingClient = await testEnvironment.ConnectClient(); - await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSequence = new ReadOnlySequence(new byte[] { 1 }), Retain = true }); + await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new ReadOnlySequence(new byte[] { 1 }), Retain = true }); var subscribeTime = DateTime.UtcNow; @@ -642,7 +642,7 @@ public async Task Subscriptions_Subscribe_Only_New_Subscriptions() //wait a bit for the subscription to become established await Task.Delay(500); - await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", PayloadSequence = new ReadOnlySequence(new byte[] { 1 }), Retain = true }); + await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new ReadOnlySequence(new byte[] { 1 }), Retain = true }); var messages = await SetupReceivingOfMessages(managedClient, 1); diff --git a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs index a46856e77..798dce10a 100644 --- a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs @@ -298,7 +298,7 @@ await receiver.SubscribeAsync( Assert.IsNotNull(receivedMessage); Assert.AreEqual("A", receivedMessage.Topic); - Assert.AreEqual(0, receivedMessage.PayloadSequence.Length); + Assert.AreEqual(0, receivedMessage.Payload.Length); } } @@ -509,7 +509,7 @@ public async Task Publish_QoS_1_In_ApplicationMessageReceiveHandler() client2.ApplicationMessageReceivedAsync += e => { - client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence)); + client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload)); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs index d7d74c7fd..6d44431f1 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs @@ -112,7 +112,7 @@ public void DeserializeV311_MqttPublishPacket() PacketIdentifier = 123, Dup = true, Retain = true, - PayloadSequence = new ReadOnlySequence(Encoding.ASCII.GetBytes("HELLO")), + Payload = new ReadOnlySequence(Encoding.ASCII.GetBytes("HELLO")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, Topic = "A/B/C" }; @@ -319,7 +319,7 @@ public void Serialize_LargePacket() var publishPacket = new MqttPublishPacket { Topic = "abcdefghijklmnopqrstuvwxyz0123456789", - PayloadSequence = new ReadOnlySequence(payload) + Payload = new ReadOnlySequence(payload) }; var serializationHelper = new MqttPacketSerializationHelper(); @@ -329,17 +329,17 @@ public void Serialize_LargePacket() Assert.IsNotNull(publishPacketCopy); Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic); - CollectionAssert.AreEqual(publishPacket.PayloadSequence.ToArray(), publishPacketCopy.PayloadSequence.ToArray()); + CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), publishPacketCopy.Payload.ToArray()); // Now modify the payload and test again. - publishPacket.PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes("MQTT")); + publishPacket.Payload = new ReadOnlySequence(Encoding.UTF8.GetBytes("MQTT")); buffer = serializationHelper.Encode(publishPacket); var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket; Assert.IsNotNull(publishPacketCopy2); Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic); - CollectionAssert.AreEqual(publishPacket.PayloadSequence.ToArray(), publishPacketCopy2.PayloadSequence.ToArray()); + CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), publishPacketCopy2.Payload.ToArray()); } [TestMethod] @@ -463,7 +463,7 @@ public void SerializeV311_MqttPublishPacket() PacketIdentifier = 123, Dup = true, Retain = true, - PayloadSequence = new ReadOnlySequence(Encoding.ASCII.GetBytes("HELLO")), + Payload = new ReadOnlySequence(Encoding.ASCII.GetBytes("HELLO")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, Topic = "A/B/C" }; diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs index 506c54c91..1f3349d0e 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs @@ -298,7 +298,7 @@ public void Serialize_Full_MqttPublishPacket_V311() PacketIdentifier = 123, Dup = true, Retain = true, - PayloadSequence = new ReadOnlySequence(Encoding.ASCII.GetBytes("Payload")), + Payload = new ReadOnlySequence(Encoding.ASCII.GetBytes("Payload")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, Topic = "Topic", ResponseTopic = "/Response", @@ -322,7 +322,7 @@ public void Serialize_Full_MqttPublishPacket_V311() Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Retain, deserialized.Retain); - CollectionAssert.AreEqual(publishPacket.PayloadSequence.ToArray(), deserialized.PayloadSequence.ToArray()); + CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), deserialized.Payload.ToArray()); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1. diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs index ae17c78f6..69bd67a7c 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs @@ -260,7 +260,7 @@ public void Serialize_Full_MqttPublishPacket_V500() PacketIdentifier = 123, Dup = true, Retain = true, - PayloadSequence = new ReadOnlySequence(Encoding.ASCII.GetBytes("Payload")), + Payload = new ReadOnlySequence(Encoding.ASCII.GetBytes("Payload")), QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce, Topic = "Topic", ResponseTopic = "/Response", @@ -284,7 +284,7 @@ public void Serialize_Full_MqttPublishPacket_V500() Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Retain, deserialized.Retain); - CollectionAssert.AreEqual(publishPacket.PayloadSequence.ToArray(), deserialized.PayloadSequence.ToArray()); + CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), deserialized.Payload.ToArray()); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic); diff --git a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs index f7c99560f..e6d1c60c8 100644 --- a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs +++ b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs @@ -274,7 +274,7 @@ public async Task Publish_And_Receive_New_Properties() Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic); Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval); CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData); - CollectionAssert.AreEqual(applicationMessage.PayloadSequence.ToArray(), receivedMessage.PayloadSequence.ToArray()); + CollectionAssert.AreEqual(applicationMessage.Payload.ToArray(), receivedMessage.Payload.ToArray()); CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties); } } diff --git a/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs b/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs index 3da3ec8db..cf17e9fee 100644 --- a/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs +++ b/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs @@ -31,7 +31,7 @@ public void CreateApplicationMessage_TimeStampPayload() Assert.AreEqual("xyz", message.Topic); Assert.IsFalse(message.Retain); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSequence), "00:06:00"); + Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00"); } [TestMethod] @@ -43,7 +43,7 @@ public void CreateApplicationMessage_StreamPayload() Assert.AreEqual("123", message.Topic); Assert.IsFalse(message.Retain); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSequence), "Hello"); + Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello"); } [TestMethod] diff --git a/Source/MQTTnet.Tests/Server/General.cs b/Source/MQTTnet.Tests/Server/General.cs index 3cceffecc..4e31a64a8 100644 --- a/Source/MQTTnet.Tests/Server/General.cs +++ b/Source/MQTTnet.Tests/Server/General.cs @@ -307,7 +307,7 @@ public async Task Intercept_Message() var server = await testEnvironment.StartServer(); server.InterceptingPublishAsync += e => { - e.ApplicationMessage.PayloadSequence = new ReadOnlySequence(Encoding.ASCII.GetBytes("extended")); + e.ApplicationMessage.Payload = new ReadOnlySequence(Encoding.ASCII.GetBytes("extended")); return CompletedTask.Instance; }; @@ -318,7 +318,7 @@ public async Task Intercept_Message() var isIntercepted = false; c2.ApplicationMessageReceivedAsync += e => { - isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSequence), StringComparison.Ordinal) == 0; + isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; return CompletedTask.Instance; }; @@ -429,7 +429,7 @@ await server.InjectApplicationMessage( new MqttApplicationMessage { Topic = "/test/1", - PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes("true")), + Payload = new ReadOnlySequence(Encoding.UTF8.GetBytes("true")), QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }) { @@ -784,7 +784,7 @@ public async Task Send_Long_Body() var client1 = await testEnvironment.ConnectClient(); client1.ApplicationMessageReceivedAsync += e => { - receivedBody = e.ApplicationMessage.PayloadSequence.ToArray(); + receivedBody = e.ApplicationMessage.Payload.ToArray(); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/Server/Session_Tests.cs b/Source/MQTTnet.Tests/Server/Session_Tests.cs index c274c0e09..d1b4c6a1e 100644 --- a/Source/MQTTnet.Tests/Server/Session_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Session_Tests.cs @@ -284,7 +284,7 @@ public async Task Set_Session_Item() server.InterceptingPublishAsync += e => { - e.ApplicationMessage.PayloadSequence = new ReadOnlySequence(Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty)); + e.ApplicationMessage.Payload = new ReadOnlySequence(Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty)); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/Server/Tls_Tests.cs b/Source/MQTTnet.Tests/Server/Tls_Tests.cs index 9e47ef26b..9bc5b01f6 100644 --- a/Source/MQTTnet.Tests/Server/Tls_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Tls_Tests.cs @@ -101,7 +101,7 @@ await firstClient.PublishAsync( new MqttApplicationMessage { Topic = "TestTopic1", - PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) + Payload = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) }); await testEnvironment.Server.InjectApplicationMessage( @@ -109,7 +109,7 @@ await testEnvironment.Server.InjectApplicationMessage( new MqttApplicationMessage { Topic = "TestTopic1", - PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) + Payload = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) })); certificateProvider.CurrentCertificate = CreateCertificate(secondOid); @@ -137,7 +137,7 @@ await firstClient.PublishAsync( new MqttApplicationMessage { Topic = "TestTopic2", - PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) + Payload = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) }); await testEnvironment.Server.InjectApplicationMessage( @@ -145,7 +145,7 @@ await testEnvironment.Server.InjectApplicationMessage( new MqttApplicationMessage { Topic = "TestTopic2", - PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) + Payload = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) })); // Ensure first client still works @@ -153,7 +153,7 @@ await firstClient.PublishAsync( new MqttApplicationMessage { Topic = "TestTopic1", - PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) + Payload = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) }); await testEnvironment.Server.InjectApplicationMessage( @@ -161,7 +161,7 @@ await testEnvironment.Server.InjectApplicationMessage( new MqttApplicationMessage { Topic = "TestTopic1", - PayloadSequence = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) + Payload = new ReadOnlySequence(new byte[] { 1, 2, 3, 4 }) })); await Task.Delay(1000); diff --git a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs index 5e252cef1..1fd23bf70 100644 --- a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs +++ b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs @@ -19,7 +19,7 @@ public static MqttApplicationMessage Create(MqttPublishPacket publishPacket) return new MqttApplicationMessage { Topic = publishPacket.Topic, - PayloadSequence = publishPacket.PayloadSequence, + Payload = publishPacket.Payload, QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, Retain = publishPacket.Retain, Dup = publishPacket.Dup, diff --git a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs index d5cdfbfe5..28f28873d 100644 --- a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs +++ b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs @@ -10,8 +10,6 @@ namespace MQTTnet.Formatter { public readonly struct MqttPacketBuffer { - static readonly ReadOnlySequence EmptySequence = EmptyBuffer.ArraySequence; - public MqttPacketBuffer(ArraySegment packet, ReadOnlySequence payload) { Packet = packet; @@ -28,7 +26,7 @@ public MqttPacketBuffer(ArraySegment packet, ReadOnlySequence payloa public MqttPacketBuffer(ArraySegment packet) { Packet = packet; - Payload = EmptySequence; + Payload = ReadOnlySequence.Empty; Length = Packet.Count; } diff --git a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs index 94141d8a6..761e60a54 100644 --- a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs @@ -22,7 +22,7 @@ public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage var packet = new MqttPublishPacket { Topic = applicationMessage.Topic, - PayloadSequence = applicationMessage.PayloadSequence, + Payload = applicationMessage.Payload, QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, Retain = applicationMessage.Retain, Dup = applicationMessage.Dup, diff --git a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs index 56549b9f5..773985332 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs @@ -104,10 +104,10 @@ public MqttPacketBuffer Encode(MqttPacket packet) var remainingLength = (uint)(_bufferWriter.Length - 5); var publishPacket = packet as MqttPublishPacket; - var payloadSequence = publishPacket?.PayloadSequence; - if (payloadSequence != null) + var payload = publishPacket?.Payload; + if (payload != null) { - remainingLength += (uint)payloadSequence.Value.Length; + remainingLength += (uint)payload.Value.Length; } var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); @@ -123,9 +123,9 @@ public MqttPacketBuffer Encode(MqttPacket packet) var buffer = _bufferWriter.GetBuffer(); var firstSegment = new ArraySegment(buffer, headerOffset, _bufferWriter.Length - headerOffset); - return payloadSequence == null + return payload == null ? new MqttPacketBuffer(firstSegment) - : new MqttPacketBuffer(firstSegment, payloadSequence.Value); + : new MqttPacketBuffer(firstSegment, payload.Value); } MqttPacket DecodeConnAckPacket(ArraySegment body) @@ -279,7 +279,7 @@ MqttPacket DecodePublishPacket(ReceivedMqttPacket receivedMqttPacket) if (!_bufferReader.EndOfStream) { - packet.PayloadSequence = new ReadOnlySequence(_bufferReader.ReadRemainingData()); + packet.Payload = new ReadOnlySequence(_bufferReader.ReadRemainingData()); } return packet; diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs index 68495ca2a..ad1a654f6 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs @@ -541,7 +541,7 @@ MqttPacket DecodePublishPacket(byte header, ArraySegment body) if (!_bufferReader.EndOfStream) { - packet.PayloadSequence = new ReadOnlySequence(_bufferReader.ReadRemainingData()); + packet.Payload = new ReadOnlySequence(_bufferReader.ReadRemainingData()); } return packet; diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs index 750b9dcd4..86461a790 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs @@ -38,11 +38,11 @@ public MqttPacketBuffer Encode(MqttPacket packet) var remainingLength = (uint)_bufferWriter.Length - ReservedHeaderSize; var publishPacket = packet as MqttPublishPacket; - var payloadSequence = publishPacket?.PayloadSequence; + var payload = publishPacket?.Payload; - if (payloadSequence != null) + if (payload != null) { - remainingLength += (uint)payloadSequence.Value.Length; + remainingLength += (uint)payload.Value.Length; } var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); @@ -58,9 +58,9 @@ public MqttPacketBuffer Encode(MqttPacket packet) var buffer = _bufferWriter.GetBuffer(); var firstSegment = new ArraySegment(buffer, headerOffset, _bufferWriter.Length - headerOffset); - return payloadSequence == null + return payload == null ? new MqttPacketBuffer(firstSegment) - : new MqttPacketBuffer(firstSegment, payloadSequence.Value); + : new MqttPacketBuffer(firstSegment, payload.Value); } byte EncodeAuthPacket(MqttAuthPacket packet) diff --git a/Source/MQTTnet/Internal/EmptyBuffer.cs b/Source/MQTTnet/Internal/EmptyBuffer.cs index b2bb2e62e..e00f8d316 100644 --- a/Source/MQTTnet/Internal/EmptyBuffer.cs +++ b/Source/MQTTnet/Internal/EmptyBuffer.cs @@ -12,6 +12,5 @@ public static class EmptyBuffer public static readonly byte[] Array = System.Array.Empty(); public static readonly ArraySegment ArraySegment = new ArraySegment(Array, 0, 0); - public static readonly ReadOnlySequence ArraySequence = new ReadOnlySequence(); } } \ No newline at end of file diff --git a/Source/MQTTnet/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs index 230152805..42ef2f710 100644 --- a/Source/MQTTnet/MqttApplicationMessage.cs +++ b/Source/MQTTnet/MqttApplicationMessage.cs @@ -51,7 +51,7 @@ public sealed class MqttApplicationMessage /// /// Get or set ReadOnlySequence style of Payload. /// - public ReadOnlySequence PayloadSequence { get; set; } + public ReadOnlySequence Payload { get; set; } /// /// Gets or sets the payload format indicator. diff --git a/Source/MQTTnet/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs index bc631a70a..f25d6d7d7 100644 --- a/Source/MQTTnet/MqttApplicationMessageBuilder.cs +++ b/Source/MQTTnet/MqttApplicationMessageBuilder.cs @@ -23,7 +23,7 @@ public sealed class MqttApplicationMessageBuilder uint _messageExpiryInterval; MqttPayloadFormatIndicator _payloadFormatIndicator; - ReadOnlySequence _payloadSequence; + ReadOnlySequence _payload; MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; string _responseTopic; bool _retain; @@ -42,7 +42,7 @@ public MqttApplicationMessage Build() var applicationMessage = new MqttApplicationMessage { Topic = _topic, - PayloadSequence = _payloadSequence, + Payload = _payload, QualityOfServiceLevel = _qualityOfServiceLevel, Retain = _retain, ContentType = _contentType, @@ -90,19 +90,19 @@ public MqttApplicationMessageBuilder WithMessageExpiryInterval(uint messageExpir public MqttApplicationMessageBuilder WithPayload(byte[] payload) { - _payloadSequence = payload == null || payload.Length == 0 ? EmptyBuffer.ArraySequence : new ReadOnlySequence(payload); + _payload = payload == null || payload.Length == 0 ? ReadOnlySequence.Empty : new ReadOnlySequence(payload); return this; } public MqttApplicationMessageBuilder WithPayload(ArraySegment payloadSegment) { - _payloadSequence = new ReadOnlySequence(payloadSegment); + _payload = new ReadOnlySequence(payloadSegment); return this; } - public MqttApplicationMessageBuilder WithPayload(ReadOnlySequence payloadSequence) + public MqttApplicationMessageBuilder WithPayload(ReadOnlySequence payload) { - _payloadSequence = payloadSequence; + _payload = payload; return this; } @@ -177,19 +177,19 @@ public MqttApplicationMessageBuilder WithPayloadFormatIndicator(MqttPayloadForma public MqttApplicationMessageBuilder WithPayloadSegment(ArraySegment payloadSegment) { - _payloadSequence = new ReadOnlySequence(payloadSegment); + _payload = new ReadOnlySequence(payloadSegment); return this; } - public MqttApplicationMessageBuilder WithPayloadSegment(ReadOnlySequence payloadSequence) + public MqttApplicationMessageBuilder WithPayloadSegment(ReadOnlyMemory payloadSegment) { - _payloadSequence = payloadSequence; + _payload = new ReadOnlySequence(payloadSegment); return this; } - public MqttApplicationMessageBuilder WithPayloadSegment(ReadOnlyMemory payloadSegment) + public MqttApplicationMessageBuilder WithPayloadSequence(ReadOnlySequence payload) { - _payloadSequence = new ReadOnlySequence(payloadSegment); + _payload = payload; return this; } diff --git a/Source/MQTTnet/MqttApplicationMessageExtensions.cs b/Source/MQTTnet/MqttApplicationMessageExtensions.cs index e409d425a..f111611e0 100644 --- a/Source/MQTTnet/MqttApplicationMessageExtensions.cs +++ b/Source/MQTTnet/MqttApplicationMessageExtensions.cs @@ -16,11 +16,11 @@ public static string ConvertPayloadToString(this MqttApplicationMessage applicat throw new ArgumentNullException(nameof(applicationMessage)); } - if (applicationMessage.PayloadSequence.Length == 0) + if (applicationMessage.Payload.Length == 0) { return null; } - return Encoding.UTF8.GetString(applicationMessage.PayloadSequence); + return Encoding.UTF8.GetString(applicationMessage.Payload); } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPublishPacket.cs b/Source/MQTTnet/Packets/MqttPublishPacket.cs index 5cc5d5843..31dfc75de 100644 --- a/Source/MQTTnet/Packets/MqttPublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttPublishPacket.cs @@ -21,7 +21,7 @@ public sealed class MqttPublishPacket : MqttPacketWithIdentifier public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified; - public ReadOnlySequence PayloadSequence { get; set; } + public ReadOnlySequence Payload { get; set; } public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } = MqttQualityOfServiceLevel.AtMostOnce; @@ -40,6 +40,6 @@ public sealed class MqttPublishPacket : MqttPacketWithIdentifier public override string ToString() { return - $"Publish: [Topic={Topic}] [PayloadLength={PayloadSequence.Length}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]"; + $"Publish: [Topic={Topic}] [PayloadLength={Payload.Length}] [QoSLevel={QualityOfServiceLevel}] [Dup={Dup}] [Retain={Retain}] [PacketIdentifier={PacketIdentifier}]"; } } \ No newline at end of file From 977e50d859292ecbc99ec6123173d92dace41bc0 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Thu, 20 Jun 2024 15:20:18 +0200 Subject: [PATCH 09/19] move bufferwriter to Buffers namespace and add helpers for using ArrayPool --- .../Diagnostics/PackageInspection_Samples.cs | 5 +- .../MqttClientConnectionContextFactory.cs | 1 + .../MqttConnectionContext.cs | 21 +- .../MqttConnectionHandler.cs | 1 + .../MqttWebSocketServerAdapter.cs | 1 + Source/MQTTnet.AspnetCore/ReaderExtensions.cs | 2 +- .../ChannelAdapterBenchmark.cs | 1 + .../MQTTnet.Benchmarks/MemoryCopyBenchmark.cs | 2 +- .../MqttBufferReaderBenchmark.cs | 1 + .../MqttPacketReaderWriterBenchmark.cs | 1 + .../ReaderExtensionsBenchmark.cs | 8 +- .../SendPacketAsyncBenchmark.cs | 15 +- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 1 + .../Internal/Adapter/MqttTcpServerListener.cs | 1 + .../ASP/Mockups/ConnectionHandlerMockup.cs | 1 + .../ASP/MqttConnectionContextTest.cs | 1 + .../MQTTnet.Tests/ASP/ReaderExtensionsTest.cs | 1 + .../Diagnostics/PacketInspection_Tests.cs | 3 +- .../Formatter/MqttBufferReader_Tests.cs | 1 + ...MqttPacketSerialization_V3_Binary_Tests.cs | 1 + .../Formatter/MqttPacketWriter_Tests.cs | 1 + .../Helpers/MqttPacketWriterExtensions.cs | 1 + .../MqttPacketSerializationHelper.cs | 1 + .../MQTTnet.Tests/MqttPacketWriter_Tests.cs | 1 + Source/MQTTnet.Tests/Protocol_Tests.cs | 1 + Source/MQTTnet.Tests/Server/General.cs | 2 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 2 +- Source/MQTTnet/Adapter/MqttPacketInspector.cs | 30 +- .../MQTTnet/Buffers/ArrayPoolBufferSegment.cs | 64 ++ .../MQTTnet/Buffers/ArrayPoolMemoryStream.cs | 567 ++++++++++++++++++ .../MqttBufferReader.cs | 2 +- .../MqttBufferWriter.cs | 7 +- Source/MQTTnet/Buffers/MqttMemoryHelper.cs | 83 +++ .../InspectMqttPacketEventArgs.cs | 7 +- Source/MQTTnet/Formatter/MqttPacketBuffer.cs | 45 +- .../Formatter/MqttPacketFormatterAdapter.cs | 1 + .../Formatter/V3/MqttV3PacketFormatter.cs | 16 +- .../Formatter/V5/MqttV5PacketDecoder.cs | 1 + .../Formatter/V5/MqttV5PacketEncoder.cs | 20 +- .../Formatter/V5/MqttV5PacketFormatter.cs | 1 + .../Formatter/V5/MqttV5PropertiesReader.cs | 1 + .../Formatter/V5/MqttV5PropertiesWriter.cs | 1 + .../MqttClientAdapterFactory.cs | 1 + Source/MQTTnet/Internal/MqttMemoryHelper.cs | 14 - Source/MQTTnet/MQTTnet.csproj | 4 +- .../MQTTnet/MqttApplicationMessageBuilder.cs | 13 +- Source/MQTTnet/Packets/MqttPublishPacket.cs | 1 - 47 files changed, 846 insertions(+), 111 deletions(-) create mode 100644 Source/MQTTnet/Buffers/ArrayPoolBufferSegment.cs create mode 100644 Source/MQTTnet/Buffers/ArrayPoolMemoryStream.cs rename Source/MQTTnet/{Formatter => Buffers}/MqttBufferReader.cs (99%) rename Source/MQTTnet/{Formatter => Buffers}/MqttBufferWriter.cs (99%) create mode 100644 Source/MQTTnet/Buffers/MqttMemoryHelper.cs delete mode 100644 Source/MQTTnet/Internal/MqttMemoryHelper.cs diff --git a/Samples/Diagnostics/PackageInspection_Samples.cs b/Samples/Diagnostics/PackageInspection_Samples.cs index 29b634d17..e6fc25aa3 100644 --- a/Samples/Diagnostics/PackageInspection_Samples.cs +++ b/Samples/Diagnostics/PackageInspection_Samples.cs @@ -7,6 +7,7 @@ // ReSharper disable InconsistentNaming using MQTTnet.Diagnostics; +using System.Buffers; namespace MQTTnet.Samples.Diagnostics; @@ -43,11 +44,11 @@ static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs) { if (eventArgs.Direction == MqttPacketFlowDirection.Inbound) { - Console.WriteLine($"IN: {Convert.ToBase64String(eventArgs.Buffer)}"); + Console.WriteLine($"IN: {Convert.ToBase64String(eventArgs.Buffer.ToArray())}"); } else { - Console.WriteLine($"OUT: {Convert.ToBase64String(eventArgs.Buffer)}"); + Console.WriteLine($"OUT: {Convert.ToBase64String(eventArgs.Buffer.ToArray())}"); } return Task.CompletedTask; diff --git a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs index 7afd3e215..e60088c0e 100644 --- a/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs +++ b/Source/MQTTnet.AspnetCore/Client/MqttClientConnectionContextFactory.cs @@ -8,6 +8,7 @@ using System; using MQTTnet.Client; using MQTTnet.Diagnostics; +using MQTTnet.Buffers; namespace MQTTnet.AspNetCore.Client { diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs index b7217b2ee..6f0dc9e83 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -203,17 +203,8 @@ public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellat { var buffer = PacketFormatterAdapter.Encode(packet); - if (buffer.Payload.Length == 0) - { - // zero copy - // https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279 - await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false); - } - else - { - WritePacketBuffer(_output, buffer); - await _output.FlushAsync(cancellationToken).ConfigureAwait(false); - } + WritePacketBuffer(_output, buffer); + await _output.FlushAsync(cancellationToken).ConfigureAwait(false); BytesSent += buffer.Length; } @@ -231,9 +222,13 @@ static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) var span = output.GetSpan(buffer.Length); - buffer.Packet.AsSpan().CopyTo(span); - int offset = 0; + foreach (var segment in buffer.Packet) + { + segment.Span.CopyTo(span.Slice(offset)); + offset += segment.Length; + } + foreach (var segment in buffer.Payload) { segment.Span.CopyTo(span.Slice(offset)); diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs index 4e1138a40..f17faa4f4 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionHandler.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections.Features; using MQTTnet.Adapter; +using MQTTnet.Buffers; using MQTTnet.Diagnostics; using MQTTnet.Formatter; using MQTTnet.Server; diff --git a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs index 0d9522949..786ca25f9 100644 --- a/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs +++ b/Source/MQTTnet.AspnetCore/MqttWebSocketServerAdapter.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using MQTTnet.Adapter; +using MQTTnet.Buffers; using MQTTnet.Diagnostics; using MQTTnet.Formatter; using MQTTnet.Implementations; diff --git a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs index d120062ba..350249bad 100644 --- a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs @@ -43,7 +43,7 @@ public static bool TryDecode( return false; } - var fixedHeader = copy.First.Span[0]; + var fixedHeader = copy.FirstSpan[0]; copy = copy.Slice(headerLength); if (copy.Length < bodyLength) { diff --git a/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs b/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs index 98caee7f7..038427f1f 100644 --- a/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/ChannelAdapterBenchmark.cs @@ -4,6 +4,7 @@ using BenchmarkDotNet.Attributes; using MQTTnet.Adapter; +using MQTTnet.Buffers; using MQTTnet.Diagnostics; using MQTTnet.Formatter; using MQTTnet.Packets; diff --git a/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs b/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs index 0733e2bb9..feb08c8d3 100644 --- a/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs @@ -33,7 +33,7 @@ public void Array_Copy() [Benchmark] public void Memory_Copy() { - MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length); + MQTTnet.Buffers.MqttMemoryHelper.Copy(source, 0, target, 0, Length); } } diff --git a/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs index bfa3d209c..c3fb468ee 100644 --- a/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs @@ -6,6 +6,7 @@ using System.Text; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; +using MQTTnet.Buffers; using MQTTnet.Formatter; namespace MQTTnet.Benchmarks diff --git a/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs index 0efc7ffac..ccc2affe1 100644 --- a/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs @@ -5,6 +5,7 @@ using System; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; +using MQTTnet.Buffers; using MQTTnet.Formatter; using MQTTnet.Tests.Mockups; diff --git a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs index 1dc2f9c8e..c940a9f45 100644 --- a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs @@ -2,6 +2,7 @@ using BenchmarkDotNet.Jobs; using MQTTnet.Adapter; using MQTTnet.AspNetCore; +using MQTTnet.Buffers; using MQTTnet.Exceptions; using MQTTnet.Formatter; using MQTTnet.Packets; @@ -14,7 +15,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [RPlotExporter, RankColumn] [MemoryDiagnoser] public class ReaderExtensionsBenchmark @@ -35,7 +36,10 @@ public void GlobalSetup() var buffer = mqttPacketFormatter.Encode(packet); stream = new MemoryStream(); - stream.Write(buffer.Packet); + foreach (var segment in buffer.Packet) + { + stream.Write(segment.Span); + } foreach (var segment in buffer.Payload) { stream.Write(segment.Span); diff --git a/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs index 433138caf..e8785e2b7 100644 --- a/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs @@ -42,7 +42,10 @@ public async ValueTask After() if (buffer.Payload.Length == 0) { - await output.WriteAsync(buffer.Packet).ConfigureAwait(false); + foreach (var buffer in buffer.Packet) + { + await output.WriteAsync(buffer).ConfigureAwait(false); + } } else { @@ -59,10 +62,14 @@ static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) var span = output.GetSpan(buffer.Length); - buffer.Packet.AsSpan().CopyTo(span); - int offset = 0; - foreach(var segment in buffer.Payload) + foreach (var segment in buffer.Packet) + { + segment.Span.CopyTo(span.Slice(offset)); + offset += segment.Length; + } + + foreach (var segment in buffer.Payload) { segment.Span.CopyTo(span.Slice(offset)); offset += segment.Length; diff --git a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs index c430e38c7..e0f6ce8f8 100644 --- a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -15,6 +15,7 @@ using BenchmarkDotNet.Jobs; using MQTTnet.Diagnostics; using System.Buffers; +using MQTTnet.Buffers; namespace MQTTnet.Benchmarks { diff --git a/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerListener.cs b/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerListener.cs index b09a0e468..007a48da2 100644 --- a/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerListener.cs +++ b/Source/MQTTnet.Server/Internal/Adapter/MqttTcpServerListener.cs @@ -7,6 +7,7 @@ using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using MQTTnet.Adapter; +using MQTTnet.Buffers; using MQTTnet.Diagnostics; using MQTTnet.Formatter; using MQTTnet.Implementations; diff --git a/Source/MQTTnet.Tests/ASP/Mockups/ConnectionHandlerMockup.cs b/Source/MQTTnet.Tests/ASP/Mockups/ConnectionHandlerMockup.cs index b4ca73d1c..38d1f91e7 100644 --- a/Source/MQTTnet.Tests/ASP/Mockups/ConnectionHandlerMockup.cs +++ b/Source/MQTTnet.Tests/ASP/Mockups/ConnectionHandlerMockup.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Connections; using MQTTnet.Adapter; using MQTTnet.AspNetCore; +using MQTTnet.Buffers; using MQTTnet.Diagnostics; using MQTTnet.Formatter; using MQTTnet.Server; diff --git a/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs b/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs index d9400be1d..b78839ea7 100644 --- a/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs +++ b/Source/MQTTnet.Tests/ASP/MqttConnectionContextTest.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Connections; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.AspNetCore; +using MQTTnet.Buffers; using MQTTnet.Exceptions; using MQTTnet.Formatter; using MQTTnet.Packets; diff --git a/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs b/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs index 3f798abfd..ab5e8357f 100644 --- a/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs +++ b/Source/MQTTnet.Tests/ASP/ReaderExtensionsTest.cs @@ -5,6 +5,7 @@ using System.Buffers; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.AspNetCore; +using MQTTnet.Buffers; using MQTTnet.Formatter; using MQTTnet.Packets; diff --git a/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs b/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs index 7db513f58..0d5eb16ec 100644 --- a/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs +++ b/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -34,7 +35,7 @@ public async Task Inspect_Client_Packets() mqttClient.InspectPacketAsync += eventArgs => { - packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer)); + packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer.ToArray())); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs index cf709af80..386612188 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttBufferReader_Tests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Buffers; using MQTTnet.Exceptions; using MQTTnet.Formatter; diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs index 6d44431f1..8c079df3e 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs @@ -11,6 +11,7 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Adapter; +using MQTTnet.Buffers; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; using MQTTnet.Formatter; diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketWriter_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketWriter_Tests.cs index 7a16ff30a..37da37aa9 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketWriter_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketWriter_Tests.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Buffers; using MQTTnet.Exceptions; using MQTTnet.Formatter; diff --git a/Source/MQTTnet.Tests/Helpers/MqttPacketWriterExtensions.cs b/Source/MQTTnet.Tests/Helpers/MqttPacketWriterExtensions.cs index 1c6e1bf11..83020d5ad 100644 --- a/Source/MQTTnet.Tests/Helpers/MqttPacketWriterExtensions.cs +++ b/Source/MQTTnet.Tests/Helpers/MqttPacketWriterExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using MQTTnet.Buffers; using MQTTnet.Formatter; using MQTTnet.Protocol; diff --git a/Source/MQTTnet.Tests/MqttPacketSerializationHelper.cs b/Source/MQTTnet.Tests/MqttPacketSerializationHelper.cs index 43d1323d9..87e4fdcd9 100644 --- a/Source/MQTTnet.Tests/MqttPacketSerializationHelper.cs +++ b/Source/MQTTnet.Tests/MqttPacketSerializationHelper.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using MQTTnet.Adapter; +using MQTTnet.Buffers; using MQTTnet.Diagnostics; using MQTTnet.Formatter; using MQTTnet.Packets; diff --git a/Source/MQTTnet.Tests/MqttPacketWriter_Tests.cs b/Source/MQTTnet.Tests/MqttPacketWriter_Tests.cs index 0a3d3f5eb..ba90b1dd9 100644 --- a/Source/MQTTnet.Tests/MqttPacketWriter_Tests.cs +++ b/Source/MQTTnet.Tests/MqttPacketWriter_Tests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Buffers; using MQTTnet.Formatter; namespace MQTTnet.Tests diff --git a/Source/MQTTnet.Tests/Protocol_Tests.cs b/Source/MQTTnet.Tests/Protocol_Tests.cs index 6f5146c8d..c77ee02d1 100644 --- a/Source/MQTTnet.Tests/Protocol_Tests.cs +++ b/Source/MQTTnet.Tests/Protocol_Tests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Buffers; using MQTTnet.Formatter; namespace MQTTnet.Tests diff --git a/Source/MQTTnet.Tests/Server/General.cs b/Source/MQTTnet.Tests/Server/General.cs index 4e31a64a8..af4670c10 100644 --- a/Source/MQTTnet.Tests/Server/General.cs +++ b/Source/MQTTnet.Tests/Server/General.cs @@ -959,7 +959,7 @@ public async Task Disconnect_Client_with_Reason() { if (e.Buffer.Length > 0) { - if (e.Buffer[0] == (byte)MqttControlPacketType.Disconnect << 4) + if (e.Buffer.FirstSpan[0] == (byte)MqttControlPacketType.Disconnect << 4) { disconnectPacketReceived = true; } diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 4a2624ff7..61cee84b1 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -235,7 +235,7 @@ public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellat } else { - await _channel.WriteAsync(new ReadOnlySequence(packetBuffer.Packet), false, cancellationToken).ConfigureAwait(false); + await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false); await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false); } diff --git a/Source/MQTTnet/Adapter/MqttPacketInspector.cs b/Source/MQTTnet/Adapter/MqttPacketInspector.cs index 88cb1a305..5b2d28010 100644 --- a/Source/MQTTnet/Adapter/MqttPacketInspector.cs +++ b/Source/MQTTnet/Adapter/MqttPacketInspector.cs @@ -3,8 +3,10 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.IO; using System.Threading.Tasks; +using MQTTnet.Buffers; using MQTTnet.Diagnostics; using MQTTnet.Formatter; using MQTTnet.Internal; @@ -16,7 +18,7 @@ public sealed class MqttPacketInspector readonly AsyncEvent _asyncEvent; readonly MqttNetSourceLogger _logger; - MemoryStream _receivedPacketBuffer; + ArrayPoolMemoryStream _receivedPacketBuffer; public MqttPacketInspector(AsyncEvent asyncEvent, IMqttNetLogger logger) { @@ -39,10 +41,8 @@ public void BeginReceivePacket() if (_receivedPacketBuffer == null) { - _receivedPacketBuffer = new MemoryStream(); + _receivedPacketBuffer = new ArrayPoolMemoryStream(1024); } - - _receivedPacketBuffer?.SetLength(0); } public Task BeginSendPacket(MqttPacketBuffer buffer) @@ -55,9 +55,9 @@ public Task BeginSendPacket(MqttPacketBuffer buffer) // Create a copy of the actual packet so that the inspector gets no access // to the internal buffers. This is waste of memory but this feature is only // intended for debugging etc. so that this is OK. - var bufferCopy = buffer.ToArray(); + var bufferCopy = new ReadOnlySequence(buffer.ToArray()); - return InspectPacket(bufferCopy, MqttPacketFlowDirection.Outbound); + return InspectPacket(bufferCopy, null, MqttPacketFlowDirection.Outbound); } public Task EndReceivePacket() @@ -67,28 +67,32 @@ public Task EndReceivePacket() return CompletedTask.Instance; } - var buffer = _receivedPacketBuffer.ToArray(); - _receivedPacketBuffer.SetLength(0); + var sequence = _receivedPacketBuffer.GetReadOnlySequence(); + + // set sequence and transform ownership of stream + Task t = InspectPacket(sequence, _receivedPacketBuffer, MqttPacketFlowDirection.Inbound); + _receivedPacketBuffer = null; - return InspectPacket(buffer, MqttPacketFlowDirection.Inbound); + return t; } - public void FillReceiveBuffer(byte[] buffer) + public void FillReceiveBuffer(ReadOnlySpan buffer) { if (!_asyncEvent.HasHandlers) { return; } - _receivedPacketBuffer?.Write(buffer, 0, buffer.Length); + _receivedPacketBuffer?.Write(buffer); } - async Task InspectPacket(byte[] buffer, MqttPacketFlowDirection direction) + async Task InspectPacket(ReadOnlySequence sequence, IDisposable owner, MqttPacketFlowDirection direction) { try { - var eventArgs = new InspectMqttPacketEventArgs(direction, buffer); + var eventArgs = new InspectMqttPacketEventArgs(direction, sequence); await _asyncEvent.InvokeAsync(eventArgs).ConfigureAwait(false); + owner?.Dispose(); } catch (Exception exception) { diff --git a/Source/MQTTnet/Buffers/ArrayPoolBufferSegment.cs b/Source/MQTTnet/Buffers/ArrayPoolBufferSegment.cs new file mode 100644 index 000000000..041dfa16a --- /dev/null +++ b/Source/MQTTnet/Buffers/ArrayPoolBufferSegment.cs @@ -0,0 +1,64 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Buffers; + +namespace MQTTnet.Buffers +{ + /// + /// Helper to build a ReadOnlySequence from a set of allocated buffers. + /// + public sealed class ArrayPoolBufferSegment : ReadOnlySequenceSegment + { + private T[] _array; + + /// + /// Initializes a new instance of the class. + /// + public ArrayPoolBufferSegment(T[] array, int offset, int length) + { + Memory = new ReadOnlyMemory(array, offset, length); + _array = array; + } + + /// + /// Returns the base array of the buffer. + /// + public T[] Array() => _array; + + /// + /// Rents a buffer from the pool and returns a instance. + /// + /// The length of the segment. + public static ArrayPoolBufferSegment Rent(int length) + { + var array = ArrayPool.Shared.Rent(length); + return new ArrayPoolBufferSegment(array, 0, length); + } + + /// + /// Rents a new buffer and appends it to the sequence. + /// + public ArrayPoolBufferSegment RentAndAppend(int length) + { + var array = ArrayPool.Shared.Rent(length); + return Append(array, 0, length); + } + + /// + /// Appends a buffer to the sequence. + /// + public ArrayPoolBufferSegment Append(T[] array, int offset, int length) + { + var segment = new ArrayPoolBufferSegment(array, offset, length) + { + RunningIndex = RunningIndex + Memory.Length, + }; + Next = segment; + return segment; + } + } +} diff --git a/Source/MQTTnet/Buffers/ArrayPoolMemoryStream.cs b/Source/MQTTnet/Buffers/ArrayPoolMemoryStream.cs new file mode 100644 index 000000000..5ebcf7c74 --- /dev/null +++ b/Source/MQTTnet/Buffers/ArrayPoolMemoryStream.cs @@ -0,0 +1,567 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; + +namespace MQTTnet.Buffers +{ + /// + /// Class to create a MemoryStream which uses ArrayPool buffers. + /// + public sealed class ArrayPoolMemoryStream : MemoryStream + { + private const int DefaultBufferListSize = 8; + private int _bufferIndex; + private ArraySegment _currentBuffer; + private int _currentPosition; + private List> _buffers; + private int _start; + private int _count; + private int _bufferSize; + private int _endOfLastBuffer; + private bool _externalBuffersReadOnly; + + /// + /// Initializes a new instance of the class. + /// Creates a writeable stream that rents ArrayPool buffers as necessary. + /// + public ArrayPoolMemoryStream(int bufferSize, int start, int count) + { + _buffers = new List>(DefaultBufferListSize); + _bufferSize = bufferSize; + _start = start; + _count = count; + _endOfLastBuffer = 0; + _externalBuffersReadOnly = false; + + SetCurrentBuffer(0); + } + + /// + /// Initializes a new instance of the class. + /// Creates a writeable stream that creates buffers as necessary. + /// + public ArrayPoolMemoryStream(int bufferSize) + { + _buffers = new List>(DefaultBufferListSize); + _bufferSize = bufferSize; + _start = 0; + _count = bufferSize; + _endOfLastBuffer = 0; + _externalBuffersReadOnly = false; + } + + /// + public override bool CanRead + { + get { return _buffers != null; } + } + + /// + public override bool CanSeek + { + get { return _buffers != null; } + } + + /// + public override bool CanWrite + { + get { return _buffers != null && !_externalBuffersReadOnly; } + } + + /// + public override long Length + { + get { return GetAbsoluteLength(); } + } + + /// + public override long Position + { + get { return GetAbsolutePosition(); } + set { Seek(value, SeekOrigin.Begin); } + } + + /// + public override void Flush() + { + // nothing to do. + } + + /// + /// Returns ReadOnlySequence of the buffers stored in the stream. + /// ReadOnlySequence is only valid as long as the stream is not + /// disposed and no more data is written. + /// + public ReadOnlySequence GetReadOnlySequence() + { + if (_buffers.Count == 0 || _buffers[0].Array == null) + { + return ReadOnlySequence.Empty; + } + + int endIndex = GetBufferCount(0); + if (endIndex == 0) + { + return ReadOnlySequence.Empty; + } + + var firstSegment = new ArrayPoolBufferSegment(_buffers[0].Array!, _buffers[0].Offset, endIndex); + var nextSegment = firstSegment; + for (int ii = 1; ii < _buffers.Count; ii++) + { + var buffer = _buffers[ii]; + if (buffer.Array != null && endIndex > 0) + { + endIndex = GetBufferCount(ii); + nextSegment = nextSegment.Append(buffer.Array, buffer.Offset, endIndex); + } + } + + return new ReadOnlySequence(firstSegment, 0, nextSegment, endIndex); + } + + /// + public override int ReadByte() + { + do + { + // check for end of stream. + if (_currentBuffer.Array == null) + { + return -1; + } + + int bytesLeft = GetBufferCount(_bufferIndex) - _currentPosition; + + // copy the bytes requested. + if (bytesLeft > 0) + { + return _currentBuffer[_currentPosition++]; + } + + // move to next buffer. + SetCurrentBuffer(_bufferIndex + 1); + } while (true); + } + + /// + public override int Read(Span buffer) + { + int count = buffer.Length; + int offset = 0; + int bytesRead = 0; + + while (count > 0) + { + // check for end of stream. + if (_currentBuffer.Array == null) + { + return bytesRead; + } + + int bytesLeft = GetBufferCount(_bufferIndex) - _currentPosition; + + // copy the bytes requested. + if (bytesLeft > count) + { + _currentBuffer.AsSpan(_currentPosition, count).CopyTo(buffer.Slice(offset)); + bytesRead += count; + _currentPosition += count; + return bytesRead; + } + + // copy the bytes available and move to next buffer. + _currentBuffer.AsSpan(_currentPosition, bytesLeft).CopyTo(buffer.Slice(offset)); + bytesRead += bytesLeft; + + offset += bytesLeft; + count -= bytesLeft; + + // move to next buffer. + SetCurrentBuffer(_bufferIndex + 1); + } + + return bytesRead; + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + int bytesRead = 0; + + while (count > 0) + { + // check for end of stream. + if (_currentBuffer.Array == null) + { + return bytesRead; + } + + int bytesLeft = GetBufferCount(_bufferIndex) - _currentPosition; + + // copy the bytes requested. + if (bytesLeft > count) + { + Array.Copy(_currentBuffer.Array, _currentPosition + _currentBuffer.Offset, buffer, offset, count); + bytesRead += count; + _currentPosition += count; + return bytesRead; + } + + // copy the bytes available and move to next buffer. + Array.Copy(_currentBuffer.Array, _currentPosition + _currentBuffer.Offset, buffer, offset, bytesLeft); + bytesRead += bytesLeft; + + offset += bytesLeft; + count -= bytesLeft; + + // move to next buffer. + SetCurrentBuffer(_bufferIndex + 1); + } + + return bytesRead; + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + case SeekOrigin.Begin: + { + break; + } + + case SeekOrigin.Current: + { + offset += GetAbsolutePosition(); + break; + } + + case SeekOrigin.End: + { + offset += GetAbsoluteLength(); + break; + } + } + + if (offset < 0) + { + throw new IOException("Cannot seek beyond the beginning of the stream."); + } + + // special case + if (offset == 0) + { + SetCurrentBuffer(0); + return 0; + } + + int position = (int)offset; + + if (position > GetAbsolutePosition()) + { + CheckEndOfStream(); + } + + for (int ii = 0; ii < _buffers.Count; ii++) + { + int length = GetBufferCount(ii); + + if (offset <= length) + { + SetCurrentBuffer(ii); + _currentPosition = (int)offset; + return position; + } + + offset -= length; + } + + throw new IOException("Cannot seek beyond the end of the stream."); + } + + /// + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + public override void WriteByte(byte value) + { + do + { + // allocate new buffer if necessary + CheckEndOfStream(); + + int bytesLeft = _currentBuffer.Count - _currentPosition; + + // copy the byte requested. + if (bytesLeft >= 1) + { + _currentBuffer[_currentPosition] = value; + UpdateCurrentPosition(1); + + return; + } + + // move to next buffer. + SetCurrentBuffer(_bufferIndex + 1); + } while (true); + } + + /// + public override void Write(ReadOnlySpan buffer) + { + int count = buffer.Length; + int offset = 0; + while (count > 0) + { + // check for end of stream. + CheckEndOfStream(); + + int bytesLeft = _currentBuffer.Count - _currentPosition; + + // copy the bytes requested. + if (bytesLeft >= count) + { + buffer.Slice(offset, count).CopyTo(_currentBuffer.AsSpan(_currentPosition)); + + UpdateCurrentPosition(count); + + return; + } + + // copy the bytes available and move to next buffer. + buffer.Slice(offset, bytesLeft).CopyTo(_currentBuffer.AsSpan(_currentPosition)); + + offset += bytesLeft; + count -= bytesLeft; + + // move to next buffer. + SetCurrentBuffer(_bufferIndex + 1); + } + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + while (count > 0) + { + // check for end of stream. + CheckEndOfStream(); + + int bytesLeft = _currentBuffer.Count - _currentPosition; + + // copy the bytes requested. + if (bytesLeft >= count) + { + Array.Copy(buffer, offset, _currentBuffer.Array!, _currentPosition + _currentBuffer.Offset, count); + + UpdateCurrentPosition(count); + + return; + } + + // copy the bytes available and move to next buffer. + Array.Copy(buffer, offset, _currentBuffer.Array!, _currentPosition + _currentBuffer.Offset, bytesLeft); + + offset += bytesLeft; + count -= bytesLeft; + + // move to next buffer. + SetCurrentBuffer(_bufferIndex + 1); + } + } + + /// + public override byte[] ToArray() + { + if (_buffers == null) + { + throw new ObjectDisposedException(nameof(ArrayPoolMemoryStream)); + } + + int absoluteLength = GetAbsoluteLength(); + if (absoluteLength == 0) + { + return Array.Empty(); + } + + byte[] array = GC.AllocateUninitializedArray(absoluteLength); + + int offset = 0; + foreach (var buffer in _buffers) + { + if (buffer.Array != null) + { + int length = buffer.Count; + Array.Copy(buffer.Array, buffer.Offset, array, offset, length); + offset += length; + } + } + + return array; + } + + /// + /// Helper to benchmark the performance of the stream. + /// + internal void WriteMemoryStream(ReadOnlySpan buffer) => base.Write(buffer); + + /// + /// Helper to benchmark the performance of the stream. + /// + internal int ReadMemoryStream(Span buffer) => base.Read(buffer); + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_buffers != null) + { + if (!_externalBuffersReadOnly) + { + foreach (var buffer in _buffers) + { + if (buffer.Array != null) + { + ArrayPool.Shared.Return(buffer.Array); + } + } + } + + _buffers.Clear(); + } + } + + base.Dispose(disposing); + } + + /// + /// Update the current buffer count. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateCurrentPosition(int count) + { + _currentPosition += count; + + if (_bufferIndex == _buffers.Count - 1) + { + if (_endOfLastBuffer < _currentPosition) + { + _endOfLastBuffer = _currentPosition; + } + } + } + + /// + /// Sets the current buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetCurrentBuffer(int index) + { + if (index < 0 || index >= _buffers.Count) + { + _currentBuffer = default(ArraySegment); + _currentPosition = 0; + return; + } + + _bufferIndex = index; + _currentBuffer = _buffers[index]; + _currentPosition = 0; + } + + /// + /// Returns the total length in all buffers. + /// + private int GetAbsoluteLength() + { + int length = 0; + + for (int ii = 0; ii < _buffers.Count; ii++) + { + length += GetBufferCount(ii); + } + + return length; + } + + /// + /// Returns the current position. + /// + private int GetAbsolutePosition() + { + // check if at end of stream. + if (_currentBuffer.Array == null) + { + return GetAbsoluteLength(); + } + + // calculate position. + int position = 0; + + for (int ii = 0; ii < _bufferIndex; ii++) + { + position += GetBufferCount(ii); + } + + position += _currentPosition; + + return position; + } + + /// + /// Returns the number of bytes used in the buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetBufferCount(int index) + { + if (index == _buffers.Count - 1) + { + return _endOfLastBuffer; + } + + return _buffers[index].Count; + } + + /// + /// Check if end of stream is reached and take new buffer if necessary. + /// + /// Throws if end of stream is reached. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CheckEndOfStream() + { + // check for end of stream. + if (_currentBuffer.Array == null) + { + byte[] newBuffer = ArrayPool.Shared.Rent(_bufferSize); + _buffers.Add(new ArraySegment(newBuffer, _start, _count)); + _endOfLastBuffer = 0; + + SetCurrentBuffer(_buffers.Count - 1); + } + } + + /// + /// Clears the buffers and resets the state variables. + /// + private void ClearBuffers() + { + _buffers.Clear(); + _bufferIndex = 0; + _endOfLastBuffer = 0; + SetCurrentBuffer(0); + } + } +} diff --git a/Source/MQTTnet/Formatter/MqttBufferReader.cs b/Source/MQTTnet/Buffers/MqttBufferReader.cs similarity index 99% rename from Source/MQTTnet/Formatter/MqttBufferReader.cs rename to Source/MQTTnet/Buffers/MqttBufferReader.cs index 1a795c184..ee5181afc 100644 --- a/Source/MQTTnet/Formatter/MqttBufferReader.cs +++ b/Source/MQTTnet/Buffers/MqttBufferReader.cs @@ -9,7 +9,7 @@ using MQTTnet.Internal; using System.Buffers.Binary; -namespace MQTTnet.Formatter +namespace MQTTnet.Buffers { public sealed class MqttBufferReader { diff --git a/Source/MQTTnet/Formatter/MqttBufferWriter.cs b/Source/MQTTnet/Buffers/MqttBufferWriter.cs similarity index 99% rename from Source/MQTTnet/Formatter/MqttBufferWriter.cs rename to Source/MQTTnet/Buffers/MqttBufferWriter.cs index 14ad707b5..b0ed9429f 100644 --- a/Source/MQTTnet/Formatter/MqttBufferWriter.cs +++ b/Source/MQTTnet/Buffers/MqttBufferWriter.cs @@ -2,14 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using MQTTnet.Exceptions; +using MQTTnet.Protocol; using System; using System.Runtime.CompilerServices; using System.Text; -using MQTTnet.Exceptions; -using MQTTnet.Internal; -using MQTTnet.Protocol; -namespace MQTTnet.Formatter +namespace MQTTnet.Buffers { /// /// This is a custom implementation of a memory stream which provides only MQTTnet relevant features. diff --git a/Source/MQTTnet/Buffers/MqttMemoryHelper.cs b/Source/MQTTnet/Buffers/MqttMemoryHelper.cs new file mode 100644 index 000000000..b3368d42f --- /dev/null +++ b/Source/MQTTnet/Buffers/MqttMemoryHelper.cs @@ -0,0 +1,83 @@ +using System; +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace MQTTnet.Buffers +{ + public static class MqttMemoryHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Copy(byte[] source, int sourceIndex, byte[] destination, int destinationIndex, int length) + { + source.AsSpan(sourceIndex, length).CopyTo(destination.AsSpan(destinationIndex, length)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Copy(ReadOnlySequence sequence, int sourceIndex, byte[] destination, int destinationIndex, int length) + { + var offset = destinationIndex; + foreach (var segment in sequence) + { + if (segment.Length < sourceIndex) + { + sourceIndex -= segment.Length; + continue; + } + var targetLength = Math.Min(segment.Length - sourceIndex, length); + segment.Span.Slice(sourceIndex, targetLength).CopyTo(destination.AsSpan(offset)); + offset += targetLength; + length -= targetLength; + if (length == 0) + { + break; + } + sourceIndex = 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySequence RentCopy(ReadOnlySequence sequence, int sourceIndex, int length) + { + ArrayPoolBufferSegment firstSegment = null; + ArrayPoolBufferSegment nextSegment = null; + + var offset = sourceIndex; + foreach (var segment in sequence) + { + if (segment.Length >= sourceIndex) + { + sourceIndex -= segment.Length; + continue; + } + + var targetLength = Math.Min(segment.Length - sourceIndex, length); + if (firstSegment == null) + { + firstSegment = ArrayPoolBufferSegment.Rent(targetLength); + nextSegment = firstSegment; + } + else + { + nextSegment = nextSegment.RentAndAppend(targetLength); + } + + segment.Span.Slice(sourceIndex, targetLength).CopyTo(nextSegment.Array().AsSpan()); + offset += targetLength; + length -= targetLength; + if (length == 0) + { + break; + } + sourceIndex = 0; + } + + if (firstSegment == null) + { + return ReadOnlySequence.Empty; + } + return new ReadOnlySequence(firstSegment, 0, nextSegment, nextSegment.Memory.Length); + } + + } +} + diff --git a/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs b/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs index 549fe863f..854438d7f 100644 --- a/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs +++ b/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs @@ -3,18 +3,19 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; namespace MQTTnet.Diagnostics { public sealed class InspectMqttPacketEventArgs : EventArgs { - public InspectMqttPacketEventArgs(MqttPacketFlowDirection direction, byte[] buffer) + public InspectMqttPacketEventArgs(MqttPacketFlowDirection direction, ReadOnlySequence buffer) { Direction = direction; - Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); + Buffer = buffer; } - public byte[] Buffer { get; } + public ReadOnlySequence Buffer { get; } public MqttPacketFlowDirection Direction { get; } } diff --git a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs index 28f28873d..6ffc649cb 100644 --- a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs +++ b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using MQTTnet.Internal; +using MQTTnet.Buffers; using System; using System.Buffers; @@ -10,30 +10,39 @@ namespace MQTTnet.Formatter { public readonly struct MqttPacketBuffer { - public MqttPacketBuffer(ArraySegment packet, ReadOnlySequence payload) + public MqttPacketBuffer(ReadOnlySequence packet, ReadOnlySequence payload = default) { Packet = packet; Payload = payload; - if (Packet.Count + Payload.Length > int.MaxValue) + if (Packet.Length + Payload.Length > int.MaxValue) { throw new InvalidOperationException("The packet is too large."); } - Length = Packet.Count + (int)Payload.Length; + Length = (int)Packet.Length + (int)Payload.Length; } public MqttPacketBuffer(ArraySegment packet) { - Packet = packet; + Packet = new ReadOnlySequence(packet); Payload = ReadOnlySequence.Empty; - Length = Packet.Count; + if (Packet.Length > int.MaxValue) + { + throw new InvalidOperationException("The packet is too large."); + } + + Length = (int)Packet.Length; + } + + public MqttPacketBuffer(ReadOnlySequence packet) : this(packet, ReadOnlySequence.Empty) + { } public int Length { get; } - public ArraySegment Packet { get; } + public ReadOnlySequence Packet { get; } public ReadOnlySequence Payload { get; } @@ -41,30 +50,22 @@ public byte[] ToArray() { if (Payload.Length == 0) { - return Packet.ToArray(); + var packetBuffer = GC.AllocateUninitializedArray((int)Packet.Length); + Packet.CopyTo(packetBuffer); + return packetBuffer; } var buffer = GC.AllocateUninitializedArray(Length); - - MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count); - - int offset = Packet.Count; - foreach (ReadOnlyMemory segment in Payload) - { - segment.CopyTo(buffer.AsMemory(offset)); - offset += segment.Length; - } + int packetLength = (int)Packet.Length; + int payloadLength = (int)Payload.Length; + MqttMemoryHelper.Copy(Packet, 0, buffer, 0, packetLength); + MqttMemoryHelper.Copy(Payload, 0, buffer, packetLength, payloadLength); return buffer; } public ArraySegment Join() { - if (Payload.Length == 0) - { - return Packet; - } - return new ArraySegment(ToArray()); } } diff --git a/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs index af4e4976b..b852adb48 100644 --- a/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs +++ b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs @@ -5,6 +5,7 @@ using System; using System.Runtime.CompilerServices; using MQTTnet.Adapter; +using MQTTnet.Buffers; using MQTTnet.Exceptions; using MQTTnet.Formatter.V3; using MQTTnet.Formatter.V5; diff --git a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs index 773985332..f0c6c1e86 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using MQTTnet.Adapter; +using MQTTnet.Buffers; using MQTTnet.Exceptions; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -103,11 +104,11 @@ public MqttPacketBuffer Encode(MqttPacket packet) var fixedHeader = EncodePacket(packet, _bufferWriter); var remainingLength = (uint)(_bufferWriter.Length - 5); - var publishPacket = packet as MqttPublishPacket; - var payload = publishPacket?.Payload; - if (payload != null) + ReadOnlySequence payload = default; + if (packet is MqttPublishPacket publishPacket) { - remainingLength += (uint)payload.Value.Length; + payload = publishPacket.Payload; + remainingLength += (uint)payload.Length; } var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); @@ -120,12 +121,11 @@ public MqttPacketBuffer Encode(MqttPacket packet) _bufferWriter.WriteByte(fixedHeader); _bufferWriter.WriteVariableByteInteger(remainingLength); - var buffer = _bufferWriter.GetBuffer(); - var firstSegment = new ArraySegment(buffer, headerOffset, _bufferWriter.Length - headerOffset); + var firstSegment = new ReadOnlySequence(_bufferWriter.GetBuffer(), headerOffset, _bufferWriter.Length - headerOffset); - return payload == null + return payload.Length == 0 ? new MqttPacketBuffer(firstSegment) - : new MqttPacketBuffer(firstSegment, payload.Value); + : new MqttPacketBuffer(firstSegment, payload); } MqttPacket DecodeConnAckPacket(ArraySegment body) diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs index ad1a654f6..0905f6dc2 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Collections.Generic; using MQTTnet.Adapter; +using MQTTnet.Buffers; using MQTTnet.Exceptions; using MQTTnet.Packets; using MQTTnet.Protocol; diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs index 86461a790..365a06b15 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Linq; +using MQTTnet.Buffers; using MQTTnet.Exceptions; using MQTTnet.Packets; using MQTTnet.Protocol; @@ -35,14 +37,13 @@ public MqttPacketBuffer Encode(MqttPacket packet) _bufferWriter.Seek(ReservedHeaderSize); var fixedHeader = EncodePacket(packet); - var remainingLength = (uint)_bufferWriter.Length - ReservedHeaderSize; + uint remainingLength = (uint)_bufferWriter.Length - ReservedHeaderSize; - var publishPacket = packet as MqttPublishPacket; - var payload = publishPacket?.Payload; - - if (payload != null) + ReadOnlySequence payload = default; + if (packet is MqttPublishPacket publishPacket) { - remainingLength += (uint)payload.Value.Length; + payload = publishPacket.Payload; + remainingLength += (uint)payload.Length; } var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); @@ -55,12 +56,11 @@ public MqttPacketBuffer Encode(MqttPacket packet) _bufferWriter.WriteByte(fixedHeader); _bufferWriter.WriteVariableByteInteger(remainingLength); - var buffer = _bufferWriter.GetBuffer(); - var firstSegment = new ArraySegment(buffer, headerOffset, _bufferWriter.Length - headerOffset); + var firstSegment = new ReadOnlySequence(_bufferWriter.GetBuffer(), headerOffset, _bufferWriter.Length - headerOffset); - return payload == null + return payload.Length == 0 ? new MqttPacketBuffer(firstSegment) - : new MqttPacketBuffer(firstSegment, payload.Value); + : new MqttPacketBuffer(firstSegment, payload); } byte EncodeAuthPacket(MqttAuthPacket packet) diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketFormatter.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketFormatter.cs index 371cb868e..35102e0fb 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketFormatter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using MQTTnet.Adapter; +using MQTTnet.Buffers; using MQTTnet.Packets; namespace MQTTnet.Formatter.V5 diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PropertiesReader.cs b/Source/MQTTnet/Formatter/V5/MqttV5PropertiesReader.cs index e5a3efe7e..5b79be99d 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PropertiesReader.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PropertiesReader.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using MQTTnet.Buffers; using MQTTnet.Exceptions; using MQTTnet.Packets; using MQTTnet.Protocol; diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PropertiesWriter.cs b/Source/MQTTnet/Formatter/V5/MqttV5PropertiesWriter.cs index 29c065667..de653ae3e 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PropertiesWriter.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PropertiesWriter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using MQTTnet.Buffers; using MQTTnet.Packets; using MQTTnet.Protocol; diff --git a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs index 1749817c4..c27cbf705 100644 --- a/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs +++ b/Source/MQTTnet/Implementations/MqttClientAdapterFactory.cs @@ -8,6 +8,7 @@ using System; using MQTTnet.Channel; using MQTTnet.Client; +using MQTTnet.Buffers; namespace MQTTnet.Implementations { diff --git a/Source/MQTTnet/Internal/MqttMemoryHelper.cs b/Source/MQTTnet/Internal/MqttMemoryHelper.cs deleted file mode 100644 index 448e0296c..000000000 --- a/Source/MQTTnet/Internal/MqttMemoryHelper.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Runtime.CompilerServices; - -namespace MQTTnet.Internal -{ - public static class MqttMemoryHelper - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(byte[] source, int sourceIndex, byte[] destination, int destinationIndex, int length) - { - source.AsSpan(sourceIndex, length).CopyTo(destination.AsSpan(destinationIndex, length)); - } - } -} diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index 1d3adc6de..b97c68faa 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -2,7 +2,7 @@ - + @(ReleaseNotes, '%0a') @@ -61,7 +61,7 @@ - + \ No newline at end of file diff --git a/Source/MQTTnet/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs index f25d6d7d7..bffd845bb 100644 --- a/Source/MQTTnet/MqttApplicationMessageBuilder.cs +++ b/Source/MQTTnet/MqttApplicationMessageBuilder.cs @@ -2,17 +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.Buffers; +using MQTTnet.Exceptions; +using MQTTnet.Packets; +using MQTTnet.Protocol; using System; using System.Buffers; 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 { @@ -139,10 +138,10 @@ public MqttApplicationMessageBuilder WithPayload(Stream payload, long length) } var payloadBuffer = new byte[length]; - var totalRead = 0; + int totalRead = 0; do { - var bytesRead = payload.Read(payloadBuffer, totalRead, payloadBuffer.Length - totalRead); + int bytesRead = payload.Read(payloadBuffer, totalRead, payloadBuffer.Length - totalRead); if (bytesRead == 0) { break; diff --git a/Source/MQTTnet/Packets/MqttPublishPacket.cs b/Source/MQTTnet/Packets/MqttPublishPacket.cs index 31dfc75de..224883e95 100644 --- a/Source/MQTTnet/Packets/MqttPublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttPublishPacket.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using MQTTnet.Protocol; -using System; using System.Buffers; using System.Collections.Generic; From c281f58bb4f39d18620b223e7f605779fe9b43a9 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Fri, 21 Jun 2024 18:17:07 +0200 Subject: [PATCH 10/19] fix all tests --- Source/MQTTnet.AspnetCore/ReaderExtensions.cs | 2 +- .../ReaderExtensionsBenchmark.cs | 2 +- .../Internal/MqttRetainedMessagesManager.cs | 6 +- Source/MQTTnet.TestApp/PublicBrokerTest.cs | 1 + .../Clients/MqttClient/MqttClient_Tests.cs | 1 + .../Diagnostics/PacketInspection_Tests.cs | 2 +- Source/MQTTnet.Tests/MQTTnet.Tests.csproj | 18 +- Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs | 66 +- .../TestApplicationMessageReceivedHandler.cs | 12 +- .../Server/Cross_Version_Tests.cs | 4 +- Source/MQTTnet.Tests/Server/General.cs | 18 +- .../MQTTnet.Tests/Server/Injection_Tests.cs | 6 +- Source/MQTTnet.Tests/Server/No_Local_Tests.cs | 10 +- .../MQTTnet.Tests/Server/Publishing_Tests.cs | 22 +- Source/MQTTnet.Tests/Server/QoS_Tests.cs | 2 +- .../Server/Retain_As_Published_Tests.cs | 2 +- .../Server/Retain_Handling_Tests.cs | 2 +- .../Server/Retained_Messages_Tests.cs | 16 +- .../MQTTnet.Tests/Server/Subscribe_Tests.cs | 4 +- .../Server/Subscription_Identifier_Tests.cs | 4 +- Source/MQTTnet.Tests/Server/Will_Tests.cs | 4 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 20 +- Source/MQTTnet/Adapter/MqttPacketInspector.cs | 25 +- Source/MQTTnet/Adapter/ReceivedMqttPacket.cs | 21 +- .../MQTTnet/Buffers/ArrayPoolMemoryStream.cs | 567 ------------------ Source/MQTTnet/Buffers/MqttBufferReader.cs | 4 +- Source/MQTTnet/Client/MqttClient.cs | 115 ++-- ...MqttApplicationMessageReceivedEventArgs.cs | 34 +- .../InspectMqttPacketEventArgs.cs | 5 +- .../MQTTnet/Internal/AsyncEventInvocator.cs | 10 +- Source/MQTTnet/MqttApplicationMessage.cs | 43 +- 31 files changed, 302 insertions(+), 746 deletions(-) delete mode 100644 Source/MQTTnet/Buffers/ArrayPoolMemoryStream.cs diff --git a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs index 350249bad..01d443c7b 100644 --- a/Source/MQTTnet.AspnetCore/ReaderExtensions.cs +++ b/Source/MQTTnet.AspnetCore/ReaderExtensions.cs @@ -53,7 +53,7 @@ public static bool TryDecode( var bodySlice = copy.Slice(0, bodyLength); var bodySegment = GetArraySegment(ref bodySlice); - var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength); + using var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength); if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown) { formatter.DetectProtocolVersion(receivedMqttPacket); diff --git a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs index c940a9f45..3bed9d381 100644 --- a/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/ReaderExtensionsBenchmark.cs @@ -179,7 +179,7 @@ public static bool TryDecode(MqttPacketFormatterAdapter formatter, var bodySlice = copy.Slice(0, bodyLength); var buffer = GetMemory(bodySlice).ToArray(); - var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment(buffer, 0, buffer.Length), buffer.Length + 2); + using var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment(buffer, 0, buffer.Length), buffer.Length + 2); if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown) { diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index a2588bfe8..eeebd5a1e 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -78,14 +78,14 @@ public async Task UpdateMessage(string clientId, MqttApplicationMessage applicat { if (!_messages.TryGetValue(applicationMessage.Topic, out var existingMessage)) { - _messages[applicationMessage.Topic] = applicationMessage; + _messages[applicationMessage.Topic] = applicationMessage.Clone(); saveIsRequired = true; } else { if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !SequenceEqual(existingMessage.Payload, payload)) { - _messages[applicationMessage.Topic] = applicationMessage; + _messages[applicationMessage.Topic] = applicationMessage.Clone(); saveIsRequired = true; } } @@ -107,6 +107,8 @@ public async Task UpdateMessage(string clientId, MqttApplicationMessage applicat await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); } } + + applicationMessage.DisposePayload(); } catch (Exception exception) { diff --git a/Source/MQTTnet.TestApp/PublicBrokerTest.cs b/Source/MQTTnet.TestApp/PublicBrokerTest.cs index 5124ae967..3e9e326fd 100644 --- a/Source/MQTTnet.TestApp/PublicBrokerTest.cs +++ b/Source/MQTTnet.TestApp/PublicBrokerTest.cs @@ -144,6 +144,7 @@ static async Task ExecuteTestAsync(string name, MqttClientOptions options) MqttApplicationMessage receivedMessage = null; client.ApplicationMessageReceivedAsync += e => { + e.TransferPayload(true); receivedMessage = e.ApplicationMessage; return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs index e524894c7..795ac7116 100644 --- a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs @@ -870,6 +870,7 @@ public async Task Subscribe_In_Callback_Events() { lock (receivedMessages) { + e.TransferPayload(true); receivedMessages.Add(e.ApplicationMessage); } diff --git a/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs b/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs index 0d5eb16ec..cb998e6f9 100644 --- a/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs +++ b/Source/MQTTnet.Tests/Diagnostics/PacketInspection_Tests.cs @@ -35,7 +35,7 @@ public async Task Inspect_Client_Packets() mqttClient.InspectPacketAsync += eventArgs => { - packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer.ToArray())); + packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer)); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj index 90a2e1d89..f5b034fed 100644 --- a/Source/MQTTnet.Tests/MQTTnet.Tests.csproj +++ b/Source/MQTTnet.Tests/MQTTnet.Tests.csproj @@ -14,18 +14,18 @@ - - - - + + + + - - - - - + + + + + \ No newline at end of file diff --git a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs index e6d1c60c8..4ec523337 100644 --- a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs +++ b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs @@ -212,7 +212,7 @@ public async Task Subscribe_And_Publish() var client1 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client1")); - var testMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); + using var testMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); await client1.SubscribeAsync("a"); @@ -244,38 +244,46 @@ public async Task Publish_And_Receive_New_Properties() MqttApplicationMessage receivedMessage = null; receiver.ApplicationMessageReceivedAsync += e => { + e.TransferPayload(false); receivedMessage = e.ApplicationMessage; return CompletedTask.Instance; }; - var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - - var applicationMessage = new MqttApplicationMessageBuilder() - .WithTopic("Hello") - .WithPayload("World") - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce) - .WithUserProperty("x", "1") - .WithUserProperty("y", "2") - .WithResponseTopic("response") - .WithContentType("text") - .WithMessageExpiryInterval(50) - .WithCorrelationData(new byte[12]) - .WithTopicAlias(2) - .Build(); - - await sender.PublishAsync(applicationMessage); - - await Task.Delay(500); - - Assert.IsNotNull(receivedMessage); - Assert.AreEqual(applicationMessage.Topic, receivedMessage.Topic); - Assert.AreEqual(applicationMessage.TopicAlias, receivedMessage.TopicAlias); - Assert.AreEqual(applicationMessage.ContentType, receivedMessage.ContentType); - Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic); - Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval); - CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData); - CollectionAssert.AreEqual(applicationMessage.Payload.ToArray(), receivedMessage.Payload.ToArray()); - CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties); + try + { + var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + + var applicationMessage = new MqttApplicationMessageBuilder() + .WithTopic("Hello") + .WithPayload("World") + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce) + .WithUserProperty("x", "1") + .WithUserProperty("y", "2") + .WithResponseTopic("response") + .WithContentType("text") + .WithMessageExpiryInterval(50) + .WithCorrelationData(new byte[12]) + .WithTopicAlias(2) + .Build(); + + await sender.PublishAsync(applicationMessage); + + await Task.Delay(500); + + Assert.IsNotNull(receivedMessage); + Assert.AreEqual(applicationMessage.Topic, receivedMessage.Topic); + Assert.AreEqual(applicationMessage.TopicAlias, receivedMessage.TopicAlias); + Assert.AreEqual(applicationMessage.ContentType, receivedMessage.ContentType); + Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic); + Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval); + CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData); + CollectionAssert.AreEqual(applicationMessage.Payload.ToArray(), receivedMessage.Payload.ToArray()); + CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties); + } + finally + { + receivedMessage?.DisposePayload(); + } } } } diff --git a/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs b/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs index 21dfcfc68..b9d8d07bb 100644 --- a/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs +++ b/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs @@ -13,10 +13,18 @@ namespace MQTTnet.Tests.Mockups { - public sealed class TestApplicationMessageReceivedHandler + public sealed class TestApplicationMessageReceivedHandler : IDisposable { readonly List _receivedEventArgs = new List(); + public void Dispose() + { + foreach (var eventArgs in _receivedEventArgs) + { + eventArgs.ApplicationMessage?.DisposePayload(); + } + } + public TestApplicationMessageReceivedHandler(IMqttClient mqttClient) { if (mqttClient == null) @@ -76,6 +84,8 @@ Task OnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs e { lock (_receivedEventArgs) { + // take ownership of message payload to avoid cloning + eventArgs.TransferPayload(false); _receivedEventArgs.Add(eventArgs); } diff --git a/Source/MQTTnet.Tests/Server/Cross_Version_Tests.cs b/Source/MQTTnet.Tests/Server/Cross_Version_Tests.cs index 35683a843..e0f23f084 100644 --- a/Source/MQTTnet.Tests/Server/Cross_Version_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Cross_Version_Tests.cs @@ -18,7 +18,7 @@ public async Task Send_V311_Receive_V500() await testEnvironment.StartServer(); var receiver = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - var receivedApplicationMessages = testEnvironment.CreateApplicationMessageHandler(receiver); + using var receivedApplicationMessages = testEnvironment.CreateApplicationMessageHandler(receiver); await receiver.SubscribeAsync("#"); var sender = await testEnvironment.ConnectClient(); @@ -42,7 +42,7 @@ public async Task Send_V500_Receive_V311() await testEnvironment.StartServer(); var receiver = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V311)); - var receivedApplicationMessages = testEnvironment.CreateApplicationMessageHandler(receiver); + using var receivedApplicationMessages = testEnvironment.CreateApplicationMessageHandler(receiver); await receiver.SubscribeAsync("#"); var sender = await testEnvironment.ConnectClient(); diff --git a/Source/MQTTnet.Tests/Server/General.cs b/Source/MQTTnet.Tests/Server/General.cs index af4670c10..072848408 100644 --- a/Source/MQTTnet.Tests/Server/General.cs +++ b/Source/MQTTnet.Tests/Server/General.cs @@ -2,6 +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 Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Client; +using MQTTnet.Internal; +using MQTTnet.Packets; +using MQTTnet.Protocol; +using MQTTnet.Server; using System; using System.Buffers; using System.Collections.Generic; @@ -9,14 +15,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Adapter; -using MQTTnet.Client; -using MQTTnet.Formatter; -using MQTTnet.Internal; -using MQTTnet.Packets; -using MQTTnet.Protocol; -using MQTTnet.Server; namespace MQTTnet.Tests.Server { @@ -959,7 +957,7 @@ public async Task Disconnect_Client_with_Reason() { if (e.Buffer.Length > 0) { - if (e.Buffer.FirstSpan[0] == (byte)MqttControlPacketType.Disconnect << 4) + if (e.Buffer[0] == (byte)MqttControlPacketType.Disconnect << 4) { disconnectPacketReceived = true; } @@ -1015,7 +1013,7 @@ async Task TestPublishAsync( await testEnvironment.StartServer(); var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("receiver")); - var c1MessageHandler = testEnvironment.CreateApplicationMessageHandler(c1); + using var c1MessageHandler = testEnvironment.CreateApplicationMessageHandler(c1); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(topicFilter).WithQualityOfServiceLevel(filterQualityOfServiceLevel).Build()); var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("sender")); diff --git a/Source/MQTTnet.Tests/Server/Injection_Tests.cs b/Source/MQTTnet.Tests/Server/Injection_Tests.cs index 14d30026e..27979a867 100644 --- a/Source/MQTTnet.Tests/Server/Injection_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Injection_Tests.cs @@ -17,8 +17,8 @@ public async Task Inject_Application_Message_At_Session_Level() var server = await testEnvironment.StartServer(); var receiver1 = await testEnvironment.ConnectClient(); var receiver2 = await testEnvironment.ConnectClient(); - var messageReceivedHandler1 = testEnvironment.CreateApplicationMessageHandler(receiver1); - var messageReceivedHandler2 = testEnvironment.CreateApplicationMessageHandler(receiver2); + using var messageReceivedHandler1 = testEnvironment.CreateApplicationMessageHandler(receiver1); + using var messageReceivedHandler2 = testEnvironment.CreateApplicationMessageHandler(receiver2); var status = await server.GetSessionsAsync(); var clientStatus = status[0]; @@ -47,7 +47,7 @@ public async Task Inject_ApplicationMessage_At_Server_Level() var receiver = await testEnvironment.ConnectClient(); - var messageReceivedHandler = testEnvironment.CreateApplicationMessageHandler(receiver); + using var messageReceivedHandler = testEnvironment.CreateApplicationMessageHandler(receiver); await receiver.SubscribeAsync("#"); diff --git a/Source/MQTTnet.Tests/Server/No_Local_Tests.cs b/Source/MQTTnet.Tests/Server/No_Local_Tests.cs index 4f223edc4..a909247a7 100644 --- a/Source/MQTTnet.Tests/Server/No_Local_Tests.cs +++ b/Source/MQTTnet.Tests/Server/No_Local_Tests.cs @@ -17,7 +17,7 @@ public Task Subscribe_With_No_Local() { return ExecuteTest(true, 0); } - + [TestMethod] public Task Subscribe_Without_No_Local() { @@ -31,19 +31,19 @@ async Task ExecuteTest( using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) { await testEnvironment.StartServer(); - + var client1 = await testEnvironment.ConnectClient(); - var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); + using var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").WithNoLocal(noLocal).Build(); await client1.SubscribeAsync(topicFilter); await LongTestDelay(); applicationMessageHandler.AssertReceivedCountEquals(0); - + // The client will publish a message where it is itself subscribing to. await client1.PublishStringAsync("Topic", "Payload", retain: true); await LongTestDelay(); - + applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterPublish); } } diff --git a/Source/MQTTnet.Tests/Server/Publishing_Tests.cs b/Source/MQTTnet.Tests/Server/Publishing_Tests.cs index 9ac14aa55..3429fb1cc 100644 --- a/Source/MQTTnet.Tests/Server/Publishing_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Publishing_Tests.cs @@ -79,29 +79,29 @@ public async Task Intercept_Client_Enqueue() using (var testEnvironment = CreateTestEnvironment()) { var server = await testEnvironment.StartServer(); - + var sender = await testEnvironment.ConnectClient(); - + var receiver = await testEnvironment.ConnectClient(); await receiver.SubscribeAsync("A"); - var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); - + using var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); + await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); await LongTestDelay(); - + receivedMessages.AssertReceivedCountEquals(1); - + server.InterceptingClientEnqueueAsync += e => { e.AcceptEnqueue = false; return CompletedTask.Instance; }; - + await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce); await LongTestDelay(); - + // Do not increase because the internal enqueue to the target client is not accepted! receivedMessages.AssertReceivedCountEquals(1); } @@ -118,15 +118,15 @@ public async Task Intercept_Client_Enqueue_Multiple_Clients_Subscribed_Messages_ var receiverOne = await testEnvironment.ConnectClient(o => o.WithClientId("One")); await receiverOne.SubscribeAsync("A"); - var receivedMessagesOne = testEnvironment.CreateApplicationMessageHandler(receiverOne); + using var receivedMessagesOne = testEnvironment.CreateApplicationMessageHandler(receiverOne); var receiverTwo = await testEnvironment.ConnectClient(o => o.WithClientId("Two")); await receiverTwo.SubscribeAsync("A"); - var receivedMessagesTwo = testEnvironment.CreateApplicationMessageHandler(receiverTwo); + using var receivedMessagesTwo = testEnvironment.CreateApplicationMessageHandler(receiverTwo); var receiverThree = await testEnvironment.ConnectClient(o => o.WithClientId("Three")); await receiverThree.SubscribeAsync("A"); - var receivedMessagesThree = testEnvironment.CreateApplicationMessageHandler(receiverThree); + using var receivedMessagesThree = testEnvironment.CreateApplicationMessageHandler(receiverThree); server.InterceptingClientEnqueueAsync += e => { diff --git a/Source/MQTTnet.Tests/Server/QoS_Tests.cs b/Source/MQTTnet.Tests/Server/QoS_Tests.cs index 8f52d614b..e503b3b1b 100644 --- a/Source/MQTTnet.Tests/Server/QoS_Tests.cs +++ b/Source/MQTTnet.Tests/Server/QoS_Tests.cs @@ -148,7 +148,7 @@ public async Task Preserve_Message_Order_For_Queued_Messages() // Create a new client for the existing message. var client = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false)); - var messages = testEnvironment.CreateApplicationMessageHandler(client); + using var messages = testEnvironment.CreateApplicationMessageHandler(client); await LongTestDelay(); diff --git a/Source/MQTTnet.Tests/Server/Retain_As_Published_Tests.cs b/Source/MQTTnet.Tests/Server/Retain_As_Published_Tests.cs index 74dd9cfe4..29b3d4180 100644 --- a/Source/MQTTnet.Tests/Server/Retain_As_Published_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Retain_As_Published_Tests.cs @@ -31,7 +31,7 @@ async Task ExecuteTest(bool retainAsPublished) await testEnvironment.StartServer(); var client1 = await testEnvironment.ConnectClient(); - var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); + using var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").WithRetainAsPublished(retainAsPublished).Build(); await client1.SubscribeAsync(topicFilter); await LongTestDelay(); diff --git a/Source/MQTTnet.Tests/Server/Retain_Handling_Tests.cs b/Source/MQTTnet.Tests/Server/Retain_Handling_Tests.cs index dcc8702ff..2917b4673 100644 --- a/Source/MQTTnet.Tests/Server/Retain_Handling_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Retain_Handling_Tests.cs @@ -47,7 +47,7 @@ async Task ExecuteTest( await LongTestDelay(); var client2 = await testEnvironment.ConnectClient(); - var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client2); + using var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client2); var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").WithRetainHandling(retainHandling).Build(); await client2.SubscribeAsync(topicFilter); diff --git a/Source/MQTTnet.Tests/Server/Retained_Messages_Tests.cs b/Source/MQTTnet.Tests/Server/Retained_Messages_Tests.cs index 579260166..16aa2cee5 100644 --- a/Source/MQTTnet.Tests/Server/Retained_Messages_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Retained_Messages_Tests.cs @@ -30,7 +30,7 @@ public async Task Clear_Retained_Message_With_Empty_Payload() await c1.DisconnectAsync(); var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + using var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); await Task.Delay(200); await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); @@ -55,7 +55,7 @@ public async Task Clear_Retained_Message_With_Null_Payload() await c1.DisconnectAsync(); var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + using var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); await Task.Delay(200); await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); @@ -84,7 +84,7 @@ await c1.PublishAsync( // The second client uses QoS 1 so a downgrade is required. var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + using var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); await LongTestDelay(); @@ -114,7 +114,7 @@ await c1.PublishAsync( // The second client uses QoS 2 so an upgrade is expected but according to the MQTT spec this is not supported! var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + using var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce }); await LongTestDelay(); @@ -137,7 +137,7 @@ public async Task Receive_No_Retained_Message_After_Subscribe() await c1.DisconnectAsync(); var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + using var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained_other").Build()); await Task.Delay(500); @@ -158,7 +158,7 @@ public async Task Receive_Retained_Message_After_Subscribe() await c1.DisconnectAsync(); var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + using var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained").Build()); @@ -189,7 +189,7 @@ await c1.PublishAsync( // Subscribe using a new client. var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + using var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); await Task.Delay(200); // Using QoS 2 will lead to 1 instead because the publish was made with QoS level 1 (see 3.8.4 SUBSCRIBE Actions)! @@ -211,7 +211,7 @@ public async Task Retained_Messages_Flow() var c1 = await testEnvironment.ConnectClient(); var c2 = await testEnvironment.ConnectClient(); - var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); + using var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2); await c1.PublishAsync(retainedMessage); await c1.DisconnectAsync(); diff --git a/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs b/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs index cbedaefe1..e447cd157 100644 --- a/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Subscribe_Tests.cs @@ -45,7 +45,7 @@ public async Task Subscription_Roundtrip(string topic, string filter, bool shoul var receiver = await testEnvironment.ConnectClient(); await receiver.SubscribeAsync(filter).ConfigureAwait(false); - var receivedMessages = receiver.TrackReceivedMessages(); + using var receivedMessages = receiver.TrackReceivedMessages(); var sender = await testEnvironment.ConnectClient(); await sender.PublishStringAsync(topic, "PAYLOAD").ConfigureAwait(false); @@ -143,7 +143,7 @@ public async Task Enqueue_Message_After_Subscription() }; var client = await testEnvironment.ConnectClient(); - var receivedMessages = testEnvironment.CreateApplicationMessageHandler(client); + using var receivedMessages = testEnvironment.CreateApplicationMessageHandler(client); await client.SubscribeAsync("test_topic"); diff --git a/Source/MQTTnet.Tests/Server/Subscription_Identifier_Tests.cs b/Source/MQTTnet.Tests/Server/Subscription_Identifier_Tests.cs index 257189c4e..d9e818a7b 100644 --- a/Source/MQTTnet.Tests/Server/Subscription_Identifier_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Subscription_Identifier_Tests.cs @@ -36,7 +36,7 @@ public async Task Subscribe_With_Subscription_Identifier() await testEnvironment.StartServer(); var client1 = await testEnvironment.ConnectClient(); - var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); + using var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic").Build(); var subscribeOptions = testEnvironment.ClientFactory.CreateSubscribeOptionsBuilder().WithSubscriptionIdentifier(456).WithTopicFilter(topicFilter).Build(); @@ -63,7 +63,7 @@ public async Task Subscribe_With_Multiple_Subscription_Identifiers() await testEnvironment.StartServer(); var client1 = await testEnvironment.ConnectClient(); - var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); + using var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1); var topicFilter = testEnvironment.ClientFactory.CreateTopicFilterBuilder().WithTopic("Topic/A").Build(); var subscribeOptions = testEnvironment.ClientFactory.CreateSubscribeOptionsBuilder().WithSubscriptionIdentifier(456).WithTopicFilter(topicFilter).Build(); diff --git a/Source/MQTTnet.Tests/Server/Will_Tests.cs b/Source/MQTTnet.Tests/Server/Will_Tests.cs index e9c364ca1..267d97859 100644 --- a/Source/MQTTnet.Tests/Server/Will_Tests.cs +++ b/Source/MQTTnet.Tests/Server/Will_Tests.cs @@ -43,7 +43,7 @@ public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect() var receiver = await testEnvironment.ConnectClient().ConfigureAwait(false); - var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); + using var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); @@ -65,7 +65,7 @@ public async Task Will_Message_Send() await testEnvironment.StartServer(); var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false); - var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); + using var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver); await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce); diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index 61cee84b1..eef59b90c 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -140,12 +140,12 @@ public async Task ReceivePacketAsync(CancellationToken cancellationT cancellationToken.ThrowIfCancellationRequested(); ThrowIfDisposed(); + ReceivedMqttPacket receivedPacket = new(); try { var localPacketInspector = PacketInspector; localPacketInspector?.BeginReceivePacket(); - ReceivedMqttPacket receivedPacket; var receivedPacketTask = ReceiveAsync(cancellationToken); if (receivedPacketTask.IsCompleted) { @@ -196,6 +196,10 @@ public async Task ReceivePacketAsync(CancellationToken cancellationT throw; } } + finally + { + receivedPacket.Dispose(); + } return null; } @@ -404,20 +408,19 @@ async Task ReceiveAsync(CancellationToken cancellationToken) } var bodyLength = fixedHeader.RemainingLength; - var body = new byte[bodyLength]; - + var mqttPacket = new ReceivedMqttPacket(fixedHeader.Flags, bodyLength, fixedHeader.TotalLength); var bodyOffset = 0; var chunkSize = Math.Min(ReadBufferSize, bodyLength); - + var bodyArray = mqttPacket.Body.Array; do { - var bytesLeft = body.Length - bodyOffset; + var bytesLeft = bodyArray.Length - bodyOffset; if (chunkSize > bytesLeft) { chunkSize = bytesLeft; } - var readBytes = await _channel.ReadAsync(body, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false); + var readBytes = await _channel.ReadAsync(bodyArray, bodyOffset, chunkSize, cancellationToken).ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) { @@ -432,10 +435,9 @@ async Task ReceiveAsync(CancellationToken cancellationToken) bodyOffset += readBytes; } while (bodyOffset < bodyLength); - PacketInspector?.FillReceiveBuffer(body); + PacketInspector?.FillReceiveBuffer(mqttPacket.Body); - var bodySegment = new ArraySegment(body, 0, bodyLength); - return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength); + return mqttPacket; } static bool WrapAndThrowException(Exception exception) diff --git a/Source/MQTTnet/Adapter/MqttPacketInspector.cs b/Source/MQTTnet/Adapter/MqttPacketInspector.cs index 5b2d28010..02cc387a6 100644 --- a/Source/MQTTnet/Adapter/MqttPacketInspector.cs +++ b/Source/MQTTnet/Adapter/MqttPacketInspector.cs @@ -18,7 +18,7 @@ public sealed class MqttPacketInspector readonly AsyncEvent _asyncEvent; readonly MqttNetSourceLogger _logger; - ArrayPoolMemoryStream _receivedPacketBuffer; + MemoryStream _receivedPacketBuffer; public MqttPacketInspector(AsyncEvent asyncEvent, IMqttNetLogger logger) { @@ -41,8 +41,9 @@ public void BeginReceivePacket() if (_receivedPacketBuffer == null) { - _receivedPacketBuffer = new ArrayPoolMemoryStream(1024); + _receivedPacketBuffer = new MemoryStream(); } + _receivedPacketBuffer.SetLength(0); } public Task BeginSendPacket(MqttPacketBuffer buffer) @@ -55,25 +56,24 @@ public Task BeginSendPacket(MqttPacketBuffer buffer) // Create a copy of the actual packet so that the inspector gets no access // to the internal buffers. This is waste of memory but this feature is only // intended for debugging etc. so that this is OK. - var bufferCopy = new ReadOnlySequence(buffer.ToArray()); + var bufferCopy = buffer.ToArray(); - return InspectPacket(bufferCopy, null, MqttPacketFlowDirection.Outbound); + return InspectPacket(bufferCopy, MqttPacketFlowDirection.Outbound); } public Task EndReceivePacket() { - if (!_asyncEvent.HasHandlers) + if (!_asyncEvent.HasHandlers || _receivedPacketBuffer == null) { return CompletedTask.Instance; } - var sequence = _receivedPacketBuffer.GetReadOnlySequence(); + var buffer = _receivedPacketBuffer.ToArray(); + //var sequence = _receivedPacketBuffer.GetReadOnlySequence(); + _receivedPacketBuffer.SetLength(0); // set sequence and transform ownership of stream - Task t = InspectPacket(sequence, _receivedPacketBuffer, MqttPacketFlowDirection.Inbound); - _receivedPacketBuffer = null; - - return t; + return InspectPacket(buffer, MqttPacketFlowDirection.Inbound); } public void FillReceiveBuffer(ReadOnlySpan buffer) @@ -86,13 +86,12 @@ public void FillReceiveBuffer(ReadOnlySpan buffer) _receivedPacketBuffer?.Write(buffer); } - async Task InspectPacket(ReadOnlySequence sequence, IDisposable owner, MqttPacketFlowDirection direction) + async Task InspectPacket(byte[] buffer, MqttPacketFlowDirection direction) { try { - var eventArgs = new InspectMqttPacketEventArgs(direction, sequence); + var eventArgs = new InspectMqttPacketEventArgs(direction, buffer); await _asyncEvent.InvokeAsync(eventArgs).ConfigureAwait(false); - owner?.Dispose(); } catch (Exception exception) { diff --git a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs index f290e5656..1bea9d85e 100644 --- a/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs +++ b/Source/MQTTnet/Adapter/ReceivedMqttPacket.cs @@ -3,11 +3,13 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; namespace MQTTnet.Adapter; -public readonly struct ReceivedMqttPacket +public readonly struct ReceivedMqttPacket : IDisposable { + private readonly bool _arrayPool; public static readonly ReceivedMqttPacket Empty = new(); public ReceivedMqttPacket(byte fixedHeader, ArraySegment body, int totalLength) @@ -15,6 +17,23 @@ public ReceivedMqttPacket(byte fixedHeader, ArraySegment body, int totalLe FixedHeader = fixedHeader; Body = body; TotalLength = totalLength; + _arrayPool = false; + } + + public ReceivedMqttPacket(byte fixedHeader, int bodyLength, int totalLength) + { + FixedHeader = fixedHeader; + Body = new ArraySegment(ArrayPool.Shared.Rent(bodyLength), 0, bodyLength); + TotalLength = totalLength; + _arrayPool = true; + } + + public void Dispose() + { + if (_arrayPool) + { + ArrayPool.Shared.Return(Body.Array); + } } public byte FixedHeader { get; } diff --git a/Source/MQTTnet/Buffers/ArrayPoolMemoryStream.cs b/Source/MQTTnet/Buffers/ArrayPoolMemoryStream.cs deleted file mode 100644 index 5ebcf7c74..000000000 --- a/Source/MQTTnet/Buffers/ArrayPoolMemoryStream.cs +++ /dev/null @@ -1,567 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. -// ------------------------------------------------------------ - -using System; -using System.Buffers; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; - -namespace MQTTnet.Buffers -{ - /// - /// Class to create a MemoryStream which uses ArrayPool buffers. - /// - public sealed class ArrayPoolMemoryStream : MemoryStream - { - private const int DefaultBufferListSize = 8; - private int _bufferIndex; - private ArraySegment _currentBuffer; - private int _currentPosition; - private List> _buffers; - private int _start; - private int _count; - private int _bufferSize; - private int _endOfLastBuffer; - private bool _externalBuffersReadOnly; - - /// - /// Initializes a new instance of the class. - /// Creates a writeable stream that rents ArrayPool buffers as necessary. - /// - public ArrayPoolMemoryStream(int bufferSize, int start, int count) - { - _buffers = new List>(DefaultBufferListSize); - _bufferSize = bufferSize; - _start = start; - _count = count; - _endOfLastBuffer = 0; - _externalBuffersReadOnly = false; - - SetCurrentBuffer(0); - } - - /// - /// Initializes a new instance of the class. - /// Creates a writeable stream that creates buffers as necessary. - /// - public ArrayPoolMemoryStream(int bufferSize) - { - _buffers = new List>(DefaultBufferListSize); - _bufferSize = bufferSize; - _start = 0; - _count = bufferSize; - _endOfLastBuffer = 0; - _externalBuffersReadOnly = false; - } - - /// - public override bool CanRead - { - get { return _buffers != null; } - } - - /// - public override bool CanSeek - { - get { return _buffers != null; } - } - - /// - public override bool CanWrite - { - get { return _buffers != null && !_externalBuffersReadOnly; } - } - - /// - public override long Length - { - get { return GetAbsoluteLength(); } - } - - /// - public override long Position - { - get { return GetAbsolutePosition(); } - set { Seek(value, SeekOrigin.Begin); } - } - - /// - public override void Flush() - { - // nothing to do. - } - - /// - /// Returns ReadOnlySequence of the buffers stored in the stream. - /// ReadOnlySequence is only valid as long as the stream is not - /// disposed and no more data is written. - /// - public ReadOnlySequence GetReadOnlySequence() - { - if (_buffers.Count == 0 || _buffers[0].Array == null) - { - return ReadOnlySequence.Empty; - } - - int endIndex = GetBufferCount(0); - if (endIndex == 0) - { - return ReadOnlySequence.Empty; - } - - var firstSegment = new ArrayPoolBufferSegment(_buffers[0].Array!, _buffers[0].Offset, endIndex); - var nextSegment = firstSegment; - for (int ii = 1; ii < _buffers.Count; ii++) - { - var buffer = _buffers[ii]; - if (buffer.Array != null && endIndex > 0) - { - endIndex = GetBufferCount(ii); - nextSegment = nextSegment.Append(buffer.Array, buffer.Offset, endIndex); - } - } - - return new ReadOnlySequence(firstSegment, 0, nextSegment, endIndex); - } - - /// - public override int ReadByte() - { - do - { - // check for end of stream. - if (_currentBuffer.Array == null) - { - return -1; - } - - int bytesLeft = GetBufferCount(_bufferIndex) - _currentPosition; - - // copy the bytes requested. - if (bytesLeft > 0) - { - return _currentBuffer[_currentPosition++]; - } - - // move to next buffer. - SetCurrentBuffer(_bufferIndex + 1); - } while (true); - } - - /// - public override int Read(Span buffer) - { - int count = buffer.Length; - int offset = 0; - int bytesRead = 0; - - while (count > 0) - { - // check for end of stream. - if (_currentBuffer.Array == null) - { - return bytesRead; - } - - int bytesLeft = GetBufferCount(_bufferIndex) - _currentPosition; - - // copy the bytes requested. - if (bytesLeft > count) - { - _currentBuffer.AsSpan(_currentPosition, count).CopyTo(buffer.Slice(offset)); - bytesRead += count; - _currentPosition += count; - return bytesRead; - } - - // copy the bytes available and move to next buffer. - _currentBuffer.AsSpan(_currentPosition, bytesLeft).CopyTo(buffer.Slice(offset)); - bytesRead += bytesLeft; - - offset += bytesLeft; - count -= bytesLeft; - - // move to next buffer. - SetCurrentBuffer(_bufferIndex + 1); - } - - return bytesRead; - } - - /// - public override int Read(byte[] buffer, int offset, int count) - { - int bytesRead = 0; - - while (count > 0) - { - // check for end of stream. - if (_currentBuffer.Array == null) - { - return bytesRead; - } - - int bytesLeft = GetBufferCount(_bufferIndex) - _currentPosition; - - // copy the bytes requested. - if (bytesLeft > count) - { - Array.Copy(_currentBuffer.Array, _currentPosition + _currentBuffer.Offset, buffer, offset, count); - bytesRead += count; - _currentPosition += count; - return bytesRead; - } - - // copy the bytes available and move to next buffer. - Array.Copy(_currentBuffer.Array, _currentPosition + _currentBuffer.Offset, buffer, offset, bytesLeft); - bytesRead += bytesLeft; - - offset += bytesLeft; - count -= bytesLeft; - - // move to next buffer. - SetCurrentBuffer(_bufferIndex + 1); - } - - return bytesRead; - } - - /// - public override long Seek(long offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - { - break; - } - - case SeekOrigin.Current: - { - offset += GetAbsolutePosition(); - break; - } - - case SeekOrigin.End: - { - offset += GetAbsoluteLength(); - break; - } - } - - if (offset < 0) - { - throw new IOException("Cannot seek beyond the beginning of the stream."); - } - - // special case - if (offset == 0) - { - SetCurrentBuffer(0); - return 0; - } - - int position = (int)offset; - - if (position > GetAbsolutePosition()) - { - CheckEndOfStream(); - } - - for (int ii = 0; ii < _buffers.Count; ii++) - { - int length = GetBufferCount(ii); - - if (offset <= length) - { - SetCurrentBuffer(ii); - _currentPosition = (int)offset; - return position; - } - - offset -= length; - } - - throw new IOException("Cannot seek beyond the end of the stream."); - } - - /// - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - /// - public override void WriteByte(byte value) - { - do - { - // allocate new buffer if necessary - CheckEndOfStream(); - - int bytesLeft = _currentBuffer.Count - _currentPosition; - - // copy the byte requested. - if (bytesLeft >= 1) - { - _currentBuffer[_currentPosition] = value; - UpdateCurrentPosition(1); - - return; - } - - // move to next buffer. - SetCurrentBuffer(_bufferIndex + 1); - } while (true); - } - - /// - public override void Write(ReadOnlySpan buffer) - { - int count = buffer.Length; - int offset = 0; - while (count > 0) - { - // check for end of stream. - CheckEndOfStream(); - - int bytesLeft = _currentBuffer.Count - _currentPosition; - - // copy the bytes requested. - if (bytesLeft >= count) - { - buffer.Slice(offset, count).CopyTo(_currentBuffer.AsSpan(_currentPosition)); - - UpdateCurrentPosition(count); - - return; - } - - // copy the bytes available and move to next buffer. - buffer.Slice(offset, bytesLeft).CopyTo(_currentBuffer.AsSpan(_currentPosition)); - - offset += bytesLeft; - count -= bytesLeft; - - // move to next buffer. - SetCurrentBuffer(_bufferIndex + 1); - } - } - - /// - public override void Write(byte[] buffer, int offset, int count) - { - while (count > 0) - { - // check for end of stream. - CheckEndOfStream(); - - int bytesLeft = _currentBuffer.Count - _currentPosition; - - // copy the bytes requested. - if (bytesLeft >= count) - { - Array.Copy(buffer, offset, _currentBuffer.Array!, _currentPosition + _currentBuffer.Offset, count); - - UpdateCurrentPosition(count); - - return; - } - - // copy the bytes available and move to next buffer. - Array.Copy(buffer, offset, _currentBuffer.Array!, _currentPosition + _currentBuffer.Offset, bytesLeft); - - offset += bytesLeft; - count -= bytesLeft; - - // move to next buffer. - SetCurrentBuffer(_bufferIndex + 1); - } - } - - /// - public override byte[] ToArray() - { - if (_buffers == null) - { - throw new ObjectDisposedException(nameof(ArrayPoolMemoryStream)); - } - - int absoluteLength = GetAbsoluteLength(); - if (absoluteLength == 0) - { - return Array.Empty(); - } - - byte[] array = GC.AllocateUninitializedArray(absoluteLength); - - int offset = 0; - foreach (var buffer in _buffers) - { - if (buffer.Array != null) - { - int length = buffer.Count; - Array.Copy(buffer.Array, buffer.Offset, array, offset, length); - offset += length; - } - } - - return array; - } - - /// - /// Helper to benchmark the performance of the stream. - /// - internal void WriteMemoryStream(ReadOnlySpan buffer) => base.Write(buffer); - - /// - /// Helper to benchmark the performance of the stream. - /// - internal int ReadMemoryStream(Span buffer) => base.Read(buffer); - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - if (_buffers != null) - { - if (!_externalBuffersReadOnly) - { - foreach (var buffer in _buffers) - { - if (buffer.Array != null) - { - ArrayPool.Shared.Return(buffer.Array); - } - } - } - - _buffers.Clear(); - } - } - - base.Dispose(disposing); - } - - /// - /// Update the current buffer count. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void UpdateCurrentPosition(int count) - { - _currentPosition += count; - - if (_bufferIndex == _buffers.Count - 1) - { - if (_endOfLastBuffer < _currentPosition) - { - _endOfLastBuffer = _currentPosition; - } - } - } - - /// - /// Sets the current buffer. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SetCurrentBuffer(int index) - { - if (index < 0 || index >= _buffers.Count) - { - _currentBuffer = default(ArraySegment); - _currentPosition = 0; - return; - } - - _bufferIndex = index; - _currentBuffer = _buffers[index]; - _currentPosition = 0; - } - - /// - /// Returns the total length in all buffers. - /// - private int GetAbsoluteLength() - { - int length = 0; - - for (int ii = 0; ii < _buffers.Count; ii++) - { - length += GetBufferCount(ii); - } - - return length; - } - - /// - /// Returns the current position. - /// - private int GetAbsolutePosition() - { - // check if at end of stream. - if (_currentBuffer.Array == null) - { - return GetAbsoluteLength(); - } - - // calculate position. - int position = 0; - - for (int ii = 0; ii < _bufferIndex; ii++) - { - position += GetBufferCount(ii); - } - - position += _currentPosition; - - return position; - } - - /// - /// Returns the number of bytes used in the buffer. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetBufferCount(int index) - { - if (index == _buffers.Count - 1) - { - return _endOfLastBuffer; - } - - return _buffers[index].Count; - } - - /// - /// Check if end of stream is reached and take new buffer if necessary. - /// - /// Throws if end of stream is reached. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CheckEndOfStream() - { - // check for end of stream. - if (_currentBuffer.Array == null) - { - byte[] newBuffer = ArrayPool.Shared.Rent(_bufferSize); - _buffers.Add(new ArraySegment(newBuffer, _start, _count)); - _endOfLastBuffer = 0; - - SetCurrentBuffer(_buffers.Count - 1); - } - } - - /// - /// Clears the buffers and resets the state variables. - /// - private void ClearBuffers() - { - _buffers.Clear(); - _bufferIndex = 0; - _endOfLastBuffer = 0; - SetCurrentBuffer(0); - } - } -} diff --git a/Source/MQTTnet/Buffers/MqttBufferReader.cs b/Source/MQTTnet/Buffers/MqttBufferReader.cs index ee5181afc..42d295821 100644 --- a/Source/MQTTnet/Buffers/MqttBufferReader.cs +++ b/Source/MQTTnet/Buffers/MqttBufferReader.cs @@ -35,7 +35,7 @@ public byte[] ReadBinaryData() ValidateReceiveBuffer(length); - var result = new byte[length]; + var result = GC.AllocateUninitializedArray(length); MqttMemoryHelper.Copy(_buffer, _position, result, 0, length); _position += length; @@ -66,7 +66,7 @@ public byte[] ReadRemainingData() return EmptyBuffer.Array; } - var buffer = new byte[bufferLength]; + var buffer = GC.AllocateUninitializedArray(bufferLength); MqttMemoryHelper.Copy(_buffer, _position, buffer, 0, bufferLength); _position += bufferLength; diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 59e26ec9b..8308dfb12 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -273,21 +274,21 @@ public Task PublishAsync(MqttApplicationMessage applica switch (applicationMessage.QualityOfServiceLevel) { case MqttQualityOfServiceLevel.AtMostOnce: - { - return PublishAtMostOnce(publishPacket, cancellationToken); - } + { + return PublishAtMostOnce(publishPacket, cancellationToken); + } case MqttQualityOfServiceLevel.AtLeastOnce: - { - return PublishAtLeastOnce(publishPacket, cancellationToken); - } + { + return PublishAtLeastOnce(publishPacket, cancellationToken); + } case MqttQualityOfServiceLevel.ExactlyOnce: - { - return PublishExactlyOnce(publishPacket, cancellationToken); - } + { + return PublishExactlyOnce(publishPacket, cancellationToken); + } default: - { - throw new NotSupportedException(); - } + { + throw new NotSupportedException(); + } } } @@ -407,34 +408,34 @@ Task AcknowledgeReceivedPublishPacket(MqttApplicationMessageReceivedEventArgs ev switch (eventArgs.PublishPacket.QualityOfServiceLevel) { case MqttQualityOfServiceLevel.AtMostOnce: - { - // no response required - break; - } - case MqttQualityOfServiceLevel.AtLeastOnce: - { - if (!eventArgs.ProcessingFailed) { - var pubAckPacket = MqttPubAckPacketFactory.Create(eventArgs); - return Send(pubAckPacket, cancellationToken); + // no response required + break; } + case MqttQualityOfServiceLevel.AtLeastOnce: + { + if (!eventArgs.ProcessingFailed) + { + var pubAckPacket = MqttPubAckPacketFactory.Create(eventArgs); + return Send(pubAckPacket, cancellationToken); + } - break; - } + break; + } case MqttQualityOfServiceLevel.ExactlyOnce: - { - if (!eventArgs.ProcessingFailed) { - var pubRecPacket = MqttPubRecPacketFactory.Create(eventArgs); - return Send(pubRecPacket, cancellationToken); - } + if (!eventArgs.ProcessingFailed) + { + var pubRecPacket = MqttPubRecPacketFactory.Create(eventArgs); + return Send(pubRecPacket, cancellationToken); + } - break; - } + break; + } default: - { - throw new MqttProtocolViolationException("Received a not supported QoS level."); - } + { + throw new MqttProtocolViolationException("Received a not supported QoS level."); + } } return CompletedTask.Instance; @@ -454,22 +455,22 @@ async Task Authenticate(IMqttChannelAdapter channelAdap switch (receivedPacket) { case MqttConnAckPacket connAckPacket: - { - result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion); - break; - } + { + result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion); + break; + } case MqttAuthPacket _: - { - throw new NotSupportedException("Extended authentication handler is not yet supported"); - } + { + throw new NotSupportedException("Extended authentication handler is not yet supported"); + } case null: - { - throw new MqttCommunicationException("Connection closed."); - } + { + throw new MqttCommunicationException("Connection closed."); + } default: - { - throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket})."); - } + { + throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket})."); + } } } catch (Exception exception) @@ -683,6 +684,7 @@ async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { + MqttApplicationMessageReceivedEventArgs eventArgs = null; try { var publishPacketDequeueResult = await _publishPacketReceiverQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false); @@ -692,7 +694,7 @@ async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken) } var publishPacket = publishPacketDequeueResult.Item; - var eventArgs = await HandleReceivedApplicationMessage(publishPacket).ConfigureAwait(false); + eventArgs = await HandleReceivedApplicationMessage(publishPacket).ConfigureAwait(false); if (eventArgs.AutoAcknowledge) { @@ -709,6 +711,13 @@ async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken) { _logger.Error(exception, "Error while handling application message"); } + finally + { + if (eventArgs?.TransferredPayload == false) + { + eventArgs.ApplicationMessage?.DisposePayload(); + } + } } } @@ -982,14 +991,14 @@ async Task TryProcessReceivedPacket(MqttPacket packet, CancellationToken cancell case MqttPingReqPacket _: throw new MqttProtocolViolationException("The PINGREQ Packet is sent from a client to the server only."); default: - { - if (!_packetDispatcher.TryDispatch(packet)) { - throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time."); - } + if (!_packetDispatcher.TryDispatch(packet)) + { + throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time."); + } - break; - } + break; + } } } catch (Exception exception) diff --git a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs index fc4e43bf1..49e34cca3 100644 --- a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs +++ b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs @@ -28,7 +28,30 @@ public MqttApplicationMessageReceivedEventArgs( _acknowledgeHandler = acknowledgeHandler; } - public MqttApplicationMessage ApplicationMessage { get; } + /// + /// The invoked message receiver can take ownership of the payload to avoid cloning. + /// If not cloned, it is the obligation of the new owner to dispose the payload by + /// calling . + /// + /// + /// If set to true, clones the applicationMessage and copies the payload. + /// The new instance does not need to be disposed. + /// + public void TransferPayload(bool clonePayload) + { + TransferredPayload = true; + if (clonePayload) + { + var applicationMessage = ApplicationMessage; + if (applicationMessage != null) + { + ApplicationMessage = applicationMessage.Clone(); + applicationMessage.DisposePayload(); + } + } + } + + public MqttApplicationMessage ApplicationMessage { get; private set; } /// /// Gets or sets whether the library should send MQTT ACK packets automatically if required. @@ -41,6 +64,15 @@ public MqttApplicationMessageReceivedEventArgs( /// public string ClientId { get; } + /// + /// Gets or sets whether the ownership of the message payload + /// was handed over to the invoked code. This value determines + /// if the payload can be disposed after the callback returns. + /// If transferred, the new owner is responsible + /// to dispose the payload after processing. + /// + public bool TransferredPayload { get; private set; } = false; + /// /// Gets or sets whether this message was handled. /// This value can be used in user code for custom control flow. diff --git a/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs b/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs index 854438d7f..9b1182be4 100644 --- a/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs +++ b/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs @@ -3,19 +3,18 @@ // See the LICENSE file in the project root for more information. using System; -using System.Buffers; namespace MQTTnet.Diagnostics { public sealed class InspectMqttPacketEventArgs : EventArgs { - public InspectMqttPacketEventArgs(MqttPacketFlowDirection direction, ReadOnlySequence buffer) + public InspectMqttPacketEventArgs(MqttPacketFlowDirection direction, byte[] buffer) { Direction = direction; Buffer = buffer; } - public ReadOnlySequence Buffer { get; } + public byte[] Buffer { get; } public MqttPacketFlowDirection Direction { get; } } diff --git a/Source/MQTTnet/Internal/AsyncEventInvocator.cs b/Source/MQTTnet/Internal/AsyncEventInvocator.cs index f344cb1e6..d46e56a35 100644 --- a/Source/MQTTnet/Internal/AsyncEventInvocator.cs +++ b/Source/MQTTnet/Internal/AsyncEventInvocator.cs @@ -33,15 +33,17 @@ public bool WrapsHandler(Func handler) public Task InvokeAsync(TEventArgs eventArgs) { - if (_handler != null) + var handler = _handler; + if (handler != null) { - _handler.Invoke(eventArgs); + handler.Invoke(eventArgs); return CompletedTask.Instance; } - if (_asyncHandler != null) + var asyncHandler = _asyncHandler; + if (asyncHandler != null) { - return _asyncHandler.Invoke(eventArgs); + return asyncHandler.Invoke(eventArgs); } throw new InvalidOperationException(); diff --git a/Source/MQTTnet/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs index 42ef2f710..4e35c1a42 100644 --- a/Source/MQTTnet/MqttApplicationMessage.cs +++ b/Source/MQTTnet/MqttApplicationMessage.cs @@ -4,13 +4,49 @@ using MQTTnet.Packets; using MQTTnet.Protocol; +using System; using System.Buffers; using System.Collections.Generic; namespace MQTTnet { - public sealed class MqttApplicationMessage + public sealed class MqttApplicationMessage { + /// + /// Create a clone of the + /// with a deep copy of the Payload which is cleaned up by the GC. + /// + public MqttApplicationMessage Clone() + { + return new MqttApplicationMessage() + { + ContentType = this.ContentType, + CorrelationData = this.CorrelationData, + Dup = this.Dup, + MessageExpiryInterval = this.MessageExpiryInterval, + Payload = new ReadOnlySequence(this.Payload.ToArray()), + PayloadOwner = null, + PayloadFormatIndicator = this.PayloadFormatIndicator, + QualityOfServiceLevel = this.QualityOfServiceLevel, + ResponseTopic = this.ResponseTopic, + Retain = this.Retain, + SubscriptionIdentifiers = this.SubscriptionIdentifiers, + Topic = this.Topic, + TopicAlias = this.TopicAlias, + UserProperties = this.UserProperties + }; + } + + /// + /// Disposes the payload used by the current instance of the class. + /// + public void DisposePayload() + { + Payload = ReadOnlySequence.Empty; + PayloadOwner?.Dispose(); + PayloadOwner = null; + } + /// /// Gets or sets the content type. /// The content type must be a UTF-8 encoded string. The content type value identifies the kind of UTF-8 encoded @@ -53,6 +89,11 @@ public sealed class MqttApplicationMessage /// public ReadOnlySequence Payload { get; set; } + /// + /// Get or set the owner of the memory. + /// + public IDisposable PayloadOwner { get; set; } + /// /// Gets or sets the payload format indicator. /// The payload format indicator is part of any MQTT packet that can contain a payload. The indicator is an optional From 27f848628d7059627c59e10ec4ccf3d127de3d5d Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Fri, 21 Jun 2024 19:13:37 +0200 Subject: [PATCH 11/19] code review --- .../Diagnostics/PackageInspection_Samples.cs | 15 ++-- .../MqttConnectionContext.cs | 13 +++- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 2 +- .../Internal/MqttRetainedMessagesManager.cs | 70 +------------------ Source/MQTTnet/Adapter/MqttPacketInspector.cs | 5 +- Source/MQTTnet/Buffers/MqttMemoryHelper.cs | 66 ++++++++++++++++- .../InspectMqttPacketEventArgs.cs | 2 +- 7 files changed, 90 insertions(+), 83 deletions(-) diff --git a/Samples/Diagnostics/PackageInspection_Samples.cs b/Samples/Diagnostics/PackageInspection_Samples.cs index e6fc25aa3..a8daa5657 100644 --- a/Samples/Diagnostics/PackageInspection_Samples.cs +++ b/Samples/Diagnostics/PackageInspection_Samples.cs @@ -7,7 +7,6 @@ // ReSharper disable InconsistentNaming using MQTTnet.Diagnostics; -using System.Buffers; namespace MQTTnet.Samples.Diagnostics; @@ -18,19 +17,19 @@ public static async Task Inspect_Outgoing_Package() /* * This sample covers the inspection of outgoing packages from the client. */ - + var mqttFactory = new MqttClientFactory(); - + using (var mqttClient = mqttFactory.CreateMqttClient()) { var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder() .WithTcpServer("broker.hivemq.com") .Build(); - + mqttClient.InspectPacketAsync += OnInspectPacket; - + await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); - + Console.WriteLine("MQTT client is connected."); var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() @@ -44,11 +43,11 @@ static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs) { if (eventArgs.Direction == MqttPacketFlowDirection.Inbound) { - Console.WriteLine($"IN: {Convert.ToBase64String(eventArgs.Buffer.ToArray())}"); + Console.WriteLine($"IN: {Convert.ToBase64String(eventArgs.Buffer)}"); } else { - Console.WriteLine($"OUT: {Convert.ToBase64String(eventArgs.Buffer.ToArray())}"); + Console.WriteLine($"OUT: {Convert.ToBase64String(eventArgs.Buffer)}"); } return Task.CompletedTask; diff --git a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs index 6f0dc9e83..dbe46ab6e 100644 --- a/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs +++ b/Source/MQTTnet.AspnetCore/MqttConnectionContext.cs @@ -203,8 +203,17 @@ public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellat { var buffer = PacketFormatterAdapter.Encode(packet); - WritePacketBuffer(_output, buffer); - await _output.FlushAsync(cancellationToken).ConfigureAwait(false); + if (buffer.Packet.IsSingleSegment && buffer.Payload.Length == 0) + { + // zero copy + // https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279 + await _output.WriteAsync(buffer.Packet.First, cancellationToken).ConfigureAwait(false); + } + else + { + WritePacketBuffer(_output, buffer); + await _output.FlushAsync(cancellationToken).ConfigureAwait(false); + } BytesSent += buffer.Length; } diff --git a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs index e0f6ce8f8..ff875439e 100644 --- a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -39,7 +39,7 @@ public void GlobalSetup() _bufferWriter = new MqttBufferWriter(4096, 65535); _serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311); - _serializedPacket = _serializer.Encode(_packet).ToArray(); + _serializedPacket = _serializer.Encode(_packet).Join(); } [Benchmark] diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index eeebd5a1e..c52a1f661 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using MQTTnet.Buffers; using MQTTnet.Diagnostics; using MQTTnet.Internal; using System.Buffers; @@ -83,7 +84,8 @@ public async Task UpdateMessage(string clientId, MqttApplicationMessage applicat } else { - if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !SequenceEqual(existingMessage.Payload, payload)) + if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || + !MqttMemoryHelper.SequenceEqual(existingMessage.Payload, payload)) { _messages[applicationMessage.Topic] = applicationMessage.Clone(); saveIsRequired = true; @@ -150,71 +152,5 @@ public async Task ClearMessages() await _eventContainer.RetainedMessagesClearedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false); } } - - static bool SequenceEqual(ArraySegment source, ArraySegment target) - { - return source.AsSpan().SequenceEqual(target); - } - - static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence target) - { - if (source.Length != target.Length) - { - return false; - } - - long comparedLength = 0; - long length = source.Length; - - int sourceOffset = 0; - int targetOffset = 0; - - var sourceEnumerator = source.GetEnumerator(); - var targetEnumerator = target.GetEnumerator(); - - ReadOnlyMemory sourceSegment = sourceEnumerator.Current; - ReadOnlyMemory targetSegment = targetEnumerator.Current; - - while (true) - { - int compareLength = Math.Min(sourceSegment.Length - sourceOffset, targetSegment.Length - targetOffset); - - if (compareLength > 0 && - !sourceSegment.Span.Slice(sourceOffset, compareLength).SequenceEqual(targetSegment.Span.Slice(targetOffset, compareLength))) - { - return false; - } - - comparedLength += compareLength; - if (comparedLength >= length) - { - return true; - } - - sourceOffset += compareLength; - if (sourceOffset >= sourceSegment.Length) - { - if (!sourceEnumerator.MoveNext()) - { - return false; - } - - sourceSegment = sourceEnumerator.Current; - sourceOffset = 0; - } - - targetOffset += compareLength; - if (targetOffset >= targetSegment.Length) - { - if (!targetEnumerator.MoveNext()) - { - return false; - } - - targetSegment = targetEnumerator.Current; - targetOffset = 0; - } - } - } } } \ No newline at end of file diff --git a/Source/MQTTnet/Adapter/MqttPacketInspector.cs b/Source/MQTTnet/Adapter/MqttPacketInspector.cs index 02cc387a6..fe0f70858 100644 --- a/Source/MQTTnet/Adapter/MqttPacketInspector.cs +++ b/Source/MQTTnet/Adapter/MqttPacketInspector.cs @@ -43,7 +43,8 @@ public void BeginReceivePacket() { _receivedPacketBuffer = new MemoryStream(); } - _receivedPacketBuffer.SetLength(0); + + _receivedPacketBuffer?.SetLength(0); } public Task BeginSendPacket(MqttPacketBuffer buffer) @@ -69,10 +70,8 @@ public Task EndReceivePacket() } var buffer = _receivedPacketBuffer.ToArray(); - //var sequence = _receivedPacketBuffer.GetReadOnlySequence(); _receivedPacketBuffer.SetLength(0); - // set sequence and transform ownership of stream return InspectPacket(buffer, MqttPacketFlowDirection.Inbound); } diff --git a/Source/MQTTnet/Buffers/MqttMemoryHelper.cs b/Source/MQTTnet/Buffers/MqttMemoryHelper.cs index b3368d42f..7d1bc79d5 100644 --- a/Source/MQTTnet/Buffers/MqttMemoryHelper.cs +++ b/Source/MQTTnet/Buffers/MqttMemoryHelper.cs @@ -35,7 +35,6 @@ public static void Copy(ReadOnlySequence sequence, int sourceIndex, byte[] } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ReadOnlySequence RentCopy(ReadOnlySequence sequence, int sourceIndex, int length) { ArrayPoolBufferSegment firstSegment = null; @@ -78,6 +77,71 @@ public static ReadOnlySequence RentCopy(ReadOnlySequence sequence, i return new ReadOnlySequence(firstSegment, 0, nextSegment, nextSegment.Memory.Length); } + public static bool SequenceEqual(ArraySegment source, ArraySegment target) + { + return source.AsSpan().SequenceEqual(target); + } + + public static bool SequenceEqual(ReadOnlySequence source, ReadOnlySequence target) + { + if (source.Length != target.Length) + { + return false; + } + + long comparedLength = 0; + long length = source.Length; + + int sourceOffset = 0; + int targetOffset = 0; + + var sourceEnumerator = source.GetEnumerator(); + var targetEnumerator = target.GetEnumerator(); + + ReadOnlyMemory sourceSegment = sourceEnumerator.Current; + ReadOnlyMemory targetSegment = targetEnumerator.Current; + + while (true) + { + int compareLength = Math.Min(sourceSegment.Length - sourceOffset, targetSegment.Length - targetOffset); + + if (compareLength > 0 && + !sourceSegment.Span.Slice(sourceOffset, compareLength).SequenceEqual(targetSegment.Span.Slice(targetOffset, compareLength))) + { + return false; + } + + comparedLength += compareLength; + if (comparedLength >= length) + { + return true; + } + + sourceOffset += compareLength; + if (sourceOffset >= sourceSegment.Length) + { + if (!sourceEnumerator.MoveNext()) + { + return false; + } + + sourceSegment = sourceEnumerator.Current; + sourceOffset = 0; + } + + targetOffset += compareLength; + if (targetOffset >= targetSegment.Length) + { + if (!targetEnumerator.MoveNext()) + { + return false; + } + + targetSegment = targetEnumerator.Current; + targetOffset = 0; + } + } + } } } diff --git a/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs b/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs index 9b1182be4..549fe863f 100644 --- a/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs +++ b/Source/MQTTnet/Diagnostics/PacketInspection/InspectMqttPacketEventArgs.cs @@ -11,7 +11,7 @@ public sealed class InspectMqttPacketEventArgs : EventArgs public InspectMqttPacketEventArgs(MqttPacketFlowDirection direction, byte[] buffer) { Direction = direction; - Buffer = buffer; + Buffer = buffer ?? throw new ArgumentNullException(nameof(buffer)); } public byte[] Buffer { get; } From 08b23b9d7564c7baa044cdaa155380af9ac12fc9 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Sun, 23 Jun 2024 09:00:09 +0200 Subject: [PATCH 12/19] use memory pool for special case --- .../MQTTnet.Benchmarks/AsyncLockBenchmark.cs | 2 +- Source/MQTTnet.Benchmarks/LoggerBenchmark.cs | 2 +- .../MQTTnet.Benchmarks/MemoryCopyBenchmark.cs | 2 +- .../MessageProcessingBenchmark.cs | 2 +- ...rocessingMqttConnectionContextBenchmark.cs | 2 +- .../MqttBufferReaderBenchmark.cs | 2 +- .../MqttPacketReaderWriterBenchmark.cs | 11 +++---- .../MqttTcpChannelBenchmark.cs | 2 +- .../RoundtripProcessingBenchmark.cs | 2 +- .../SendPacketAsyncBenchmark.cs | 2 +- .../MQTTnet.Benchmarks/SerializerBenchmark.cs | 2 +- .../ServerProcessingBenchmark.cs | 2 +- .../MQTTnet.Benchmarks/TcpPipesBenchmark.cs | 2 +- .../TopicFilterComparerBenchmark.cs | 2 +- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 12 +++++-- Source/MQTTnet/Buffers/MqttMemoryHelper.cs | 9 +++++- Source/MQTTnet/Formatter/MqttPacketBuffer.cs | 28 +++++++++++------ .../MQTTnet/MqttApplicationMessageBuilder.cs | 31 +++++++------------ 18 files changed, 64 insertions(+), 53 deletions(-) diff --git a/Source/MQTTnet.Benchmarks/AsyncLockBenchmark.cs b/Source/MQTTnet.Benchmarks/AsyncLockBenchmark.cs index 27a348d4f..96b40c568 100644 --- a/Source/MQTTnet.Benchmarks/AsyncLockBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/AsyncLockBenchmark.cs @@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [MemoryDiagnoser] public class AsyncLockBenchmark : BaseBenchmark { diff --git a/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs b/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs index 70f0ff51d..fd635cb35 100644 --- a/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/LoggerBenchmark.cs @@ -8,7 +8,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [RPlotExporter] [MemoryDiagnoser] public class LoggerBenchmark : BaseBenchmark diff --git a/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs b/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs index feb08c8d3..991e3577a 100644 --- a/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MemoryCopyBenchmark.cs @@ -5,7 +5,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [RPlotExporter, RankColumn] [MemoryDiagnoser] public class MemoryCopyBenchmark diff --git a/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs b/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs index b0c869244..cf693f154 100644 --- a/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MessageProcessingBenchmark.cs @@ -9,7 +9,7 @@ namespace MQTTnet.Benchmarks; -[SimpleJob(RuntimeMoniker.Net60)] +[SimpleJob(RuntimeMoniker.Net80)] [RPlotExporter] [RankColumn] [MemoryDiagnoser] diff --git a/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs b/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs index 4d6e364d4..6f2fa91fb 100644 --- a/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MessageProcessingMqttConnectionContextBenchmark.cs @@ -13,7 +13,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [MemoryDiagnoser] public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark { diff --git a/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs index c3fb468ee..b5be9a714 100644 --- a/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MqttBufferReaderBenchmark.cs @@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [MemoryDiagnoser] public class MqttBufferReaderBenchmark { diff --git a/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs index ccc2affe1..e7a9e912c 100644 --- a/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MqttPacketReaderWriterBenchmark.cs @@ -2,21 +2,20 @@ // 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 BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using MQTTnet.Buffers; -using MQTTnet.Formatter; using MQTTnet.Tests.Mockups; +using System; namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [MemoryDiagnoser] public class MqttPacketReaderWriterBenchmark : BaseBenchmark { readonly byte[] _demoPayload = new byte[1024]; - + byte[] _readPayload; [GlobalCleanup] @@ -28,7 +27,7 @@ public void GlobalCleanup() public void GlobalSetup() { TestEnvironment.EnableLogger = false; - + var writer = new MqttBufferWriter(4096, 65535); writer.WriteString("A relative short string."); writer.WriteBinary(_demoPayload); @@ -70,7 +69,7 @@ public void Read_100_000_Messages() reader.ReadBinaryData(); } } - + [Benchmark] public void Write_100_000_Messages() { diff --git a/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs b/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs index 99dbaede6..209dec63f 100644 --- a/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/MqttTcpChannelBenchmark.cs @@ -17,7 +17,7 @@ namespace MQTTnet.Benchmarks; -[SimpleJob(RuntimeMoniker.Net60)] +[SimpleJob(RuntimeMoniker.Net80)] [MemoryDiagnoser] public class MqttTcpChannelBenchmark : BaseBenchmark { diff --git a/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs b/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs index e3358fb91..69fedda41 100644 --- a/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/RoundtripProcessingBenchmark.cs @@ -5,7 +5,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [RPlotExporter, RankColumn] [MemoryDiagnoser] public class RoundtripProcessingBenchmark : BaseBenchmark diff --git a/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs index e8785e2b7..7886d184a 100644 --- a/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SendPacketAsyncBenchmark.cs @@ -8,7 +8,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [RPlotExporter, RankColumn] [MemoryDiagnoser] public class SendPacketAsyncBenchmark : BaseBenchmark diff --git a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs index ff875439e..f451ce60c 100644 --- a/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/SerializerBenchmark.cs @@ -19,7 +19,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [RPlotExporter] [MemoryDiagnoser] public class SerializerBenchmark : BaseBenchmark diff --git a/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs b/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs index fbac6dc02..f2e582af7 100644 --- a/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/ServerProcessingBenchmark.cs @@ -9,7 +9,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [RPlotExporter, RankColumn] [MemoryDiagnoser] public class ServerProcessingBenchmark : BaseBenchmark diff --git a/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs b/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs index 32e45a3ac..57845070d 100644 --- a/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/TcpPipesBenchmark.cs @@ -13,7 +13,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [MemoryDiagnoser] public class TcpPipesBenchmark : BaseBenchmark { diff --git a/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs b/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs index a75d3521a..248db82c4 100644 --- a/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs +++ b/Source/MQTTnet.Benchmarks/TopicFilterComparerBenchmark.cs @@ -8,7 +8,7 @@ namespace MQTTnet.Benchmarks { - [SimpleJob(RuntimeMoniker.Net60)] + [SimpleJob(RuntimeMoniker.Net80)] [RPlotExporter] [MemoryDiagnoser] public class TopicFilterComparerBenchmark : BaseBenchmark diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index eef59b90c..f55abc40a 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -233,14 +233,20 @@ public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellat _logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet); - if (packetBuffer.Payload.Length == 0 || !AllowPacketFragmentation) + if (!AllowPacketFragmentation) { - await _channel.WriteAsync(new ReadOnlySequence(packetBuffer.ToArray()), true, cancellationToken).ConfigureAwait(false); + using (var memoryOwner = packetBuffer.ToMemoryOwner()) + { + await _channel.WriteAsync(new ReadOnlySequence(memoryOwner.Memory), true, cancellationToken).ConfigureAwait(false); + } } else { await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false); - await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false); + if (packetBuffer.Payload.Length > 0) + { + await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false); + } } Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length); diff --git a/Source/MQTTnet/Buffers/MqttMemoryHelper.cs b/Source/MQTTnet/Buffers/MqttMemoryHelper.cs index 7d1bc79d5..c7f1062b4 100644 --- a/Source/MQTTnet/Buffers/MqttMemoryHelper.cs +++ b/Source/MQTTnet/Buffers/MqttMemoryHelper.cs @@ -14,6 +14,12 @@ public static void Copy(byte[] source, int sourceIndex, byte[] destination, int [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Copy(ReadOnlySequence sequence, int sourceIndex, byte[] destination, int destinationIndex, int length) + { + sequence.Slice(sourceIndex).CopyTo(destination.AsSpan(destinationIndex, length)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Copy(ReadOnlySequence sequence, int sourceIndex, Memory destination, int destinationIndex, int length) { var offset = destinationIndex; foreach (var segment in sequence) @@ -23,8 +29,9 @@ public static void Copy(ReadOnlySequence sequence, int sourceIndex, byte[] sourceIndex -= segment.Length; continue; } + var targetLength = Math.Min(segment.Length - sourceIndex, length); - segment.Span.Slice(sourceIndex, targetLength).CopyTo(destination.AsSpan(offset)); + segment.Span.Slice(sourceIndex, targetLength).CopyTo(destination.Span.Slice(offset)); offset += targetLength; length -= targetLength; if (length == 0) diff --git a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs index 6ffc649cb..46b78a937 100644 --- a/Source/MQTTnet/Formatter/MqttPacketBuffer.cs +++ b/Source/MQTTnet/Formatter/MqttPacketBuffer.cs @@ -48,22 +48,30 @@ public MqttPacketBuffer(ReadOnlySequence packet) : this(packet, ReadOnlySe public byte[] ToArray() { - if (Payload.Length == 0) - { - var packetBuffer = GC.AllocateUninitializedArray((int)Packet.Length); - Packet.CopyTo(packetBuffer); - return packetBuffer; - } - var buffer = GC.AllocateUninitializedArray(Length); int packetLength = (int)Packet.Length; - int payloadLength = (int)Payload.Length; MqttMemoryHelper.Copy(Packet, 0, buffer, 0, packetLength); - MqttMemoryHelper.Copy(Payload, 0, buffer, packetLength, payloadLength); - + if (Payload.Length > 0) + { + int payloadLength = (int)Payload.Length; + MqttMemoryHelper.Copy(Payload, 0, buffer, packetLength, payloadLength); + } return buffer; } + public IMemoryOwner ToMemoryOwner() + { + var memoryOwner = MemoryPool.Shared.Rent(Length); + int packetLength = (int)Packet.Length; + MqttMemoryHelper.Copy(Packet, 0, memoryOwner.Memory, 0, packetLength); + if (Payload.Length > 0) + { + int payloadLength = (int)Payload.Length; + MqttMemoryHelper.Copy(Payload, 0, memoryOwner.Memory, packetLength, payloadLength); + } + return memoryOwner; + } + public ArraySegment Join() { return new ArraySegment(ToArray()); diff --git a/Source/MQTTnet/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs index bffd845bb..f7f5e665f 100644 --- a/Source/MQTTnet/MqttApplicationMessageBuilder.cs +++ b/Source/MQTTnet/MqttApplicationMessageBuilder.cs @@ -23,6 +23,7 @@ public sealed class MqttApplicationMessageBuilder MqttPayloadFormatIndicator _payloadFormatIndicator; ReadOnlySequence _payload; + IDisposable _payloadOwner; MqttQualityOfServiceLevel _qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce; string _responseTopic; bool _retain; @@ -42,6 +43,7 @@ public MqttApplicationMessage Build() { Topic = _topic, Payload = _payload, + PayloadOwner = _payloadOwner, QualityOfServiceLevel = _qualityOfServiceLevel, Retain = _retain, ContentType = _contentType, @@ -99,9 +101,16 @@ public MqttApplicationMessageBuilder WithPayload(ArraySegment payloadSegme return this; } - public MqttApplicationMessageBuilder WithPayload(ReadOnlySequence payload) + public MqttApplicationMessageBuilder WithPayload(ReadOnlyMemory payload) + { + _payload = new ReadOnlySequence(payload); + return this; + } + + public MqttApplicationMessageBuilder WithPayload(ReadOnlySequence payload, IDisposable payloadOwner = null) { _payload = payload; + _payloadOwner = payloadOwner; return this; } @@ -119,7 +128,7 @@ public MqttApplicationMessageBuilder WithPayload(IEnumerable payload) if (payload is ArraySegment arraySegment) { - return WithPayloadSegment(arraySegment); + return WithPayload(arraySegment); } return WithPayload(payload.ToArray()); @@ -174,24 +183,6 @@ public MqttApplicationMessageBuilder WithPayloadFormatIndicator(MqttPayloadForma return this; } - public MqttApplicationMessageBuilder WithPayloadSegment(ArraySegment payloadSegment) - { - _payload = new ReadOnlySequence(payloadSegment); - return this; - } - - public MqttApplicationMessageBuilder WithPayloadSegment(ReadOnlyMemory payloadSegment) - { - _payload = new ReadOnlySequence(payloadSegment); - return this; - } - - public MqttApplicationMessageBuilder WithPayloadSequence(ReadOnlySequence payload) - { - _payload = payload; - return this; - } - /// /// 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 From 3075031bdbf58e65ba9ee063f2c26a681d2f8fe9 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Tue, 25 Jun 2024 13:47:42 +0200 Subject: [PATCH 13/19] improve some more --- .../MQTTnet/Buffers/ArrayPoolMemoryOwner.cs | 54 +++++++++++++++++++ Source/MQTTnet/Client/MqttClient.cs | 43 ++++++++------- Source/MQTTnet/MqttApplicationMessage.cs | 4 +- .../MQTTnet/MqttApplicationMessageBuilder.cs | 17 ++++-- 4 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 Source/MQTTnet/Buffers/ArrayPoolMemoryOwner.cs diff --git a/Source/MQTTnet/Buffers/ArrayPoolMemoryOwner.cs b/Source/MQTTnet/Buffers/ArrayPoolMemoryOwner.cs new file mode 100644 index 000000000..274e625c7 --- /dev/null +++ b/Source/MQTTnet/Buffers/ArrayPoolMemoryOwner.cs @@ -0,0 +1,54 @@ +// 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.Buffers; + +namespace MQTTnet.Buffers +{ + /// + /// Owner of memory rented from that + /// is responsible for disposing the underlying memory appropriately. + /// + public sealed class ArrayPoolMemoryOwner : IMemoryOwner + { + public static ArrayPoolMemoryOwner Rent(int length) + { + var memory = ArrayPool.Shared.Rent(length); + return new ArrayPoolMemoryOwner(memory); + } + + private ArrayPoolMemoryOwner(T[] memory) + { + Initialize(memory); + } + + private void Initialize(T[] array) + { + _array = array; + } + + private T[] _array; + + /// + /// Gets the rented memory./>. + /// + public T[] Array => _array; + + /// + public Memory Memory => _array.AsMemory(); + + /// + /// Returns the underlying memory and sets the to null. + /// + public void Dispose() + { + if (_array != null) + { + ArrayPool.Shared.Return(_array); + _array = null; + } + } + } +} diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 8308dfb12..3f275f933 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -269,26 +269,33 @@ public Task PublishAsync(MqttApplicationMessage applica MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion); } - var publishPacket = MqttPublishPacketFactory.Create(applicationMessage); + try + { + var publishPacket = MqttPublishPacketFactory.Create(applicationMessage); - switch (applicationMessage.QualityOfServiceLevel) + switch (applicationMessage.QualityOfServiceLevel) + { + case MqttQualityOfServiceLevel.AtMostOnce: + { + return PublishAtMostOnce(publishPacket, cancellationToken); + } + case MqttQualityOfServiceLevel.AtLeastOnce: + { + return PublishAtLeastOnce(publishPacket, cancellationToken); + } + case MqttQualityOfServiceLevel.ExactlyOnce: + { + return PublishExactlyOnce(publishPacket, cancellationToken); + } + default: + { + throw new NotSupportedException(); + } + } + } + finally { - case MqttQualityOfServiceLevel.AtMostOnce: - { - return PublishAtMostOnce(publishPacket, cancellationToken); - } - case MqttQualityOfServiceLevel.AtLeastOnce: - { - return PublishAtLeastOnce(publishPacket, cancellationToken); - } - case MqttQualityOfServiceLevel.ExactlyOnce: - { - return PublishExactlyOnce(publishPacket, cancellationToken); - } - default: - { - throw new NotSupportedException(); - } + applicationMessage?.PayloadOwner?.Dispose(); } } diff --git a/Source/MQTTnet/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs index 4e35c1a42..d1fc44c65 100644 --- a/Source/MQTTnet/MqttApplicationMessage.cs +++ b/Source/MQTTnet/MqttApplicationMessage.cs @@ -85,12 +85,12 @@ public void DisposePayload() public uint MessageExpiryInterval { get; set; } /// - /// Get or set ReadOnlySequence style of Payload. + /// Get or set ReadOnlySequence style of Payload. /// public ReadOnlySequence Payload { get; set; } /// - /// Get or set the owner of the memory. + /// Get or set the owner of the memory. /// public IDisposable PayloadOwner { get; set; } diff --git a/Source/MQTTnet/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs index f7f5e665f..71f604880 100644 --- a/Source/MQTTnet/MqttApplicationMessageBuilder.cs +++ b/Source/MQTTnet/MqttApplicationMessageBuilder.cs @@ -95,9 +95,9 @@ public MqttApplicationMessageBuilder WithPayload(byte[] payload) return this; } - public MqttApplicationMessageBuilder WithPayload(ArraySegment payloadSegment) + public MqttApplicationMessageBuilder WithPayload(ArraySegment payload) { - _payload = new ReadOnlySequence(payloadSegment); + _payload = new ReadOnlySequence(payload); return this; } @@ -146,11 +146,18 @@ public MqttApplicationMessageBuilder WithPayload(Stream payload, long length) return WithPayload(default(byte[])); } - var payloadBuffer = new byte[length]; + if (length > int.MaxValue || length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + // for most streams the Read(byte[]) method is most efficient way to tread the buffer + int checkedLength = (int)length; + var payloadBuffer = ArrayPoolMemoryOwner.Rent(checkedLength); int totalRead = 0; do { - int bytesRead = payload.Read(payloadBuffer, totalRead, payloadBuffer.Length - totalRead); + int bytesRead = payload.Read(payloadBuffer.Array, totalRead, checkedLength - totalRead); if (bytesRead == 0) { break; @@ -159,7 +166,7 @@ public MqttApplicationMessageBuilder WithPayload(Stream payload, long length) totalRead += bytesRead; } while (totalRead < length); - return WithPayload(payloadBuffer); + return WithPayload(new ReadOnlySequence(payloadBuffer.Array.AsMemory(0, totalRead)), payloadBuffer); } public MqttApplicationMessageBuilder WithPayload(string payload) From 0db31705cfd1ac94ff7183fc4a942dff6670cbb6 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Tue, 25 Jun 2024 15:19:46 +0200 Subject: [PATCH 14/19] read decoded payload as owned memory --- .../Formatter/MqttPublishPacketFactory.cs | 1 + .../MQTTnet/Buffers/ArrayPoolMemoryOwner.cs | 14 ++-- Source/MQTTnet/Buffers/EmptyMemoryOwner.cs | 37 ++++++++++ .../MQTTnet/Buffers/IReadOnlySequenceOwner.cs | 21 ++++++ Source/MQTTnet/Buffers/MqttBufferReader.cs | 16 +++++ Source/MQTTnet/Buffers/MqttPayload.cs | 69 +++++++++++++++++++ .../MqttApplicationMessageFactory.cs | 1 + .../Formatter/MqttPublishPacketFactory.cs | 1 + .../Formatter/V3/MqttV3PacketFormatter.cs | 4 +- .../Formatter/V5/MqttV5PacketDecoder.cs | 5 +- Source/MQTTnet/Packets/MqttPublishPacket.cs | 5 +- 11 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 Source/MQTTnet/Buffers/EmptyMemoryOwner.cs create mode 100644 Source/MQTTnet/Buffers/IReadOnlySequenceOwner.cs create mode 100644 Source/MQTTnet/Buffers/MqttPayload.cs diff --git a/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs index 1e2db79e4..c569ddbfd 100644 --- a/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs @@ -58,6 +58,7 @@ public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage { Topic = applicationMessage.Topic, Payload = applicationMessage.Payload, + PayloadOwner = applicationMessage.PayloadOwner, QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, Retain = applicationMessage.Retain, Dup = applicationMessage.Dup, diff --git a/Source/MQTTnet/Buffers/ArrayPoolMemoryOwner.cs b/Source/MQTTnet/Buffers/ArrayPoolMemoryOwner.cs index 274e625c7..a1d2e9736 100644 --- a/Source/MQTTnet/Buffers/ArrayPoolMemoryOwner.cs +++ b/Source/MQTTnet/Buffers/ArrayPoolMemoryOwner.cs @@ -11,24 +11,26 @@ namespace MQTTnet.Buffers /// Owner of memory rented from that /// is responsible for disposing the underlying memory appropriately. /// - public sealed class ArrayPoolMemoryOwner : IMemoryOwner + public struct ArrayPoolMemoryOwner : IMemoryOwner { public static ArrayPoolMemoryOwner Rent(int length) { var memory = ArrayPool.Shared.Rent(length); - return new ArrayPoolMemoryOwner(memory); + return new ArrayPoolMemoryOwner(memory, length); } - private ArrayPoolMemoryOwner(T[] memory) + private ArrayPoolMemoryOwner(T[] memory, int length) { - Initialize(memory); + Initialize(memory, length); } - private void Initialize(T[] array) + private void Initialize(T[] array, int length) { + _length = length; _array = array; } + private int _length; private T[] _array; /// @@ -37,7 +39,7 @@ private void Initialize(T[] array) public T[] Array => _array; /// - public Memory Memory => _array.AsMemory(); + public Memory Memory => _array.AsMemory(0, _length); /// /// Returns the underlying memory and sets the to null. diff --git a/Source/MQTTnet/Buffers/EmptyMemoryOwner.cs b/Source/MQTTnet/Buffers/EmptyMemoryOwner.cs new file mode 100644 index 000000000..e26738f0f --- /dev/null +++ b/Source/MQTTnet/Buffers/EmptyMemoryOwner.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers; +using System.Linq; + +namespace MQTTnet.Buffers +{ + /// + /// Holds an empty array of type and + /// provides a view of it. + /// The static memory is not disposed. + /// + public struct EmptyMemoryOwner : IMemoryOwner + { + public static IMemoryOwner Empty { get; } = new EmptyMemoryOwner(); + + private T[] _array; + + public EmptyMemoryOwner() + { + _array = Array.Empty(); + } + + /// + public Memory Memory => _array.AsMemory(); + + /// + /// Nothing to do. + /// + public void Dispose() + { + } + } +} diff --git a/Source/MQTTnet/Buffers/IReadOnlySequenceOwner.cs b/Source/MQTTnet/Buffers/IReadOnlySequenceOwner.cs new file mode 100644 index 000000000..5ff5185d3 --- /dev/null +++ b/Source/MQTTnet/Buffers/IReadOnlySequenceOwner.cs @@ -0,0 +1,21 @@ +// 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.Buffers; + +namespace MQTTnet.Buffers +{ + /// + /// Owner of that is responsible + /// for disposing the underlying memory appropriately. + /// + public interface IReadOnlySequenceOwner : IDisposable + { + /// + /// Gets a . + /// + ReadOnlySequence Sequence { get; } + } +} diff --git a/Source/MQTTnet/Buffers/MqttBufferReader.cs b/Source/MQTTnet/Buffers/MqttBufferReader.cs index 42d295821..87ee5bf8a 100644 --- a/Source/MQTTnet/Buffers/MqttBufferReader.cs +++ b/Source/MQTTnet/Buffers/MqttBufferReader.cs @@ -8,6 +8,7 @@ using MQTTnet.Exceptions; using MQTTnet.Internal; using System.Buffers.Binary; +using System.Buffers; namespace MQTTnet.Buffers { @@ -73,6 +74,21 @@ public byte[] ReadRemainingData() return buffer; } + public IMemoryOwner ReadPayload() + { + var bufferLength = BytesLeft; + if (bufferLength == 0) + { + return EmptyMemoryOwner.Empty; + } + + var result = ArrayPoolMemoryOwner.Rent(bufferLength); + MqttMemoryHelper.Copy(_buffer, _position, result.Array, 0, bufferLength); + _position += bufferLength; + + return result; + } + public string ReadString() { var length = ReadTwoByteInteger(); diff --git a/Source/MQTTnet/Buffers/MqttPayload.cs b/Source/MQTTnet/Buffers/MqttPayload.cs new file mode 100644 index 000000000..7323e0775 --- /dev/null +++ b/Source/MQTTnet/Buffers/MqttPayload.cs @@ -0,0 +1,69 @@ +// 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.Buffers; + +namespace MQTTnet.Buffers +{ + /// + /// Owner of that is responsible + /// for disposing the underlying payload appropriately. + /// + public struct MqttPayload : IReadOnlySequenceOwner + { + public MqttPayload() + { + Initialize(ReadOnlySequence.Empty, null); + } + + public MqttPayload(T[] memory, IDisposable owner = null) + { + Initialize(new ReadOnlySequence(memory), owner); + } + + public MqttPayload(ReadOnlySequence sequence, IDisposable owner = null) + { + Initialize(sequence, owner); + } + + public MqttPayload(ReadOnlyMemory memory, IDisposable owner = null) + { + Initialize(new ReadOnlySequence(memory), owner); + } + + public MqttPayload(ArraySegment memory, IDisposable owner = null) + { + Initialize(new ReadOnlySequence(memory), owner); + } + + private void Initialize(ReadOnlySequence sequence, IDisposable owner) + { + _sequence = sequence; + _owner = owner; + } + + private ReadOnlySequence _sequence; + private IDisposable _owner; + + /// + /// Gets a . + /// + public ReadOnlySequence Sequence { get => _sequence; } + + /// + /// Gets the owner of the . + /// + public IDisposable Owner => _owner; + + /// + /// Frees the underlying memory and sets the to empty. + /// + public void Dispose() + { + _sequence = ReadOnlySequence.Empty; + _owner?.Dispose(); + } + } +} diff --git a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs index 1fd23bf70..0f3e28ac0 100644 --- a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs +++ b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs @@ -20,6 +20,7 @@ public static MqttApplicationMessage Create(MqttPublishPacket publishPacket) { Topic = publishPacket.Topic, Payload = publishPacket.Payload, + PayloadOwner = publishPacket.PayloadOwner, QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, Retain = publishPacket.Retain, Dup = publishPacket.Dup, diff --git a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs index 761e60a54..5e89fa8da 100644 --- a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs @@ -23,6 +23,7 @@ public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage { Topic = applicationMessage.Topic, Payload = applicationMessage.Payload, + PayloadOwner = applicationMessage.PayloadOwner, QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, Retain = applicationMessage.Retain, Dup = applicationMessage.Dup, diff --git a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs index f0c6c1e86..1ab0d67e5 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs @@ -279,7 +279,9 @@ MqttPacket DecodePublishPacket(ReceivedMqttPacket receivedMqttPacket) if (!_bufferReader.EndOfStream) { - packet.Payload = new ReadOnlySequence(_bufferReader.ReadRemainingData()); + IMemoryOwner payloadOwner = _bufferReader.ReadPayload(); + packet.Payload = new ReadOnlySequence(payloadOwner.Memory); + packet.PayloadOwner = payloadOwner; } return packet; diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs index 0905f6dc2..f1ca69d0d 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs @@ -472,7 +472,6 @@ MqttPacket DecodePubCompPacket(ArraySegment body) return packet; } - MqttPacket DecodePublishPacket(byte header, ArraySegment body) { ThrowIfBodyIsEmpty(body); @@ -542,7 +541,9 @@ MqttPacket DecodePublishPacket(byte header, ArraySegment body) if (!_bufferReader.EndOfStream) { - packet.Payload = new ReadOnlySequence(_bufferReader.ReadRemainingData()); + IMemoryOwner payloadOwner = _bufferReader.ReadPayload(); + packet.Payload = new ReadOnlySequence(payloadOwner.Memory); + packet.PayloadOwner = payloadOwner; } return packet; diff --git a/Source/MQTTnet/Packets/MqttPublishPacket.cs b/Source/MQTTnet/Packets/MqttPublishPacket.cs index 224883e95..3b999ceb9 100644 --- a/Source/MQTTnet/Packets/MqttPublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttPublishPacket.cs @@ -3,12 +3,13 @@ // See the LICENSE file in the project root for more information. using MQTTnet.Protocol; +using System; using System.Buffers; using System.Collections.Generic; namespace MQTTnet.Packets; -public sealed class MqttPublishPacket : MqttPacketWithIdentifier +public class MqttPublishPacket : MqttPacketWithIdentifier { public string ContentType { get; set; } @@ -22,6 +23,8 @@ public sealed class MqttPublishPacket : MqttPacketWithIdentifier public ReadOnlySequence Payload { get; set; } + public IDisposable PayloadOwner { get; set; } + public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } = MqttQualityOfServiceLevel.AtMostOnce; public string ResponseTopic { get; set; } From 8852e7bd367032019d6df50108d3cb0ab02686a4 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Tue, 25 Jun 2024 22:46:14 +0200 Subject: [PATCH 15/19] use Mqtt payload owner --- .../Server_Retained_Messages_Samples.cs | 2 +- .../MQTTnet.Extensions.Rpc/MqttRpcClient.cs | 2 +- .../Formatter/MqttPublishPacketFactory.cs | 1 - .../Internal/MqttRetainedMessagesManager.cs | 2 +- Source/MQTTnet.TestApp/ClientTest.cs | 2 +- Source/MQTTnet.TestApp/ServerTest.cs | 2 +- .../Clients/MqttClient/MqttClient_Tests.cs | 2 +- ...MqttPacketSerialization_V3_Binary_Tests.cs | 4 ++-- .../MqttPacketSerialization_V3_Tests.cs | 2 +- .../MqttPacketSerialization_V5_Tests.cs | 2 +- Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs | 2 +- .../MqttApplicationMessageBuilder_Tests.cs | 4 ++-- Source/MQTTnet.Tests/Server/General.cs | 4 ++-- .../{MqttPayload.cs => MqttPayloadOwner.cs} | 24 ++++++++++++++----- Source/MQTTnet/Client/MqttClient.cs | 2 +- .../MqttApplicationMessageFactory.cs | 1 - .../Formatter/MqttPublishPacketFactory.cs | 1 - .../Formatter/V3/MqttV3PacketFormatter.cs | 5 ++-- .../Formatter/V5/MqttV5PacketDecoder.cs | 3 +-- .../Formatter/V5/MqttV5PacketEncoder.cs | 2 +- Source/MQTTnet/MqttApplicationMessage.cs | 17 ++++--------- .../MQTTnet/MqttApplicationMessageBuilder.cs | 1 - .../MqttApplicationMessageExtensions.cs | 2 +- Source/MQTTnet/Packets/MqttPublishPacket.cs | 5 ++-- 24 files changed, 46 insertions(+), 48 deletions(-) rename Source/MQTTnet/Buffers/{MqttPayload.cs => MqttPayloadOwner.cs} (57%) diff --git a/Samples/Server/Server_Retained_Messages_Samples.cs b/Samples/Server/Server_Retained_Messages_Samples.cs index c309ddfd8..2711868df 100644 --- a/Samples/Server/Server_Retained_Messages_Samples.cs +++ b/Samples/Server/Server_Retained_Messages_Samples.cs @@ -113,7 +113,7 @@ public static MqttRetainedMessageModel Create(MqttApplicationMessage message) // Create a copy of the buffer from the payload segment because // it cannot be serialized and deserialized with the JSON serializer. - Payload = message.Payload.ToArray(), + Payload = message.Payload.Sequence.ToArray(), UserProperties = message.UserProperties, ResponseTopic = message.ResponseTopic, CorrelationData = message.CorrelationData, diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index eaad21855..b667f0c5b 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -133,7 +133,7 @@ Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventAr return CompletedTask.Instance; } - var payloadBuffer = eventArgs.ApplicationMessage.Payload.ToArray(); + var payloadBuffer = eventArgs.ApplicationMessage.Payload.Sequence.ToArray(); awaitable.TrySetResult(payloadBuffer); // Set this message to handled to that other code can avoid execution etc. diff --git a/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs index c569ddbfd..1e2db79e4 100644 --- a/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet.Server/Internal/Formatter/MqttPublishPacketFactory.cs @@ -58,7 +58,6 @@ public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage { Topic = applicationMessage.Topic, Payload = applicationMessage.Payload, - PayloadOwner = applicationMessage.PayloadOwner, QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, Retain = applicationMessage.Retain, Dup = applicationMessage.Dup, diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index c52a1f661..df2aa72f4 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -85,7 +85,7 @@ public async Task UpdateMessage(string clientId, MqttApplicationMessage applicat else { if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || - !MqttMemoryHelper.SequenceEqual(existingMessage.Payload, payload)) + !MqttMemoryHelper.SequenceEqual(existingMessage.Payload.Sequence, payload.Sequence)) { _messages[applicationMessage.Topic] = applicationMessage.Clone(); saveIsRequired = true; diff --git a/Source/MQTTnet.TestApp/ClientTest.cs b/Source/MQTTnet.TestApp/ClientTest.cs index 3abc6230c..a278332a5 100644 --- a/Source/MQTTnet.TestApp/ClientTest.cs +++ b/Source/MQTTnet.TestApp/ClientTest.cs @@ -37,7 +37,7 @@ public static async Task RunAsync() var payloadText = string.Empty; if (e.ApplicationMessage.Payload.Length > 0) { - payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); + payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.Payload.Sequence); } Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###"); diff --git a/Source/MQTTnet.TestApp/ServerTest.cs b/Source/MQTTnet.TestApp/ServerTest.cs index dbca99c81..b52bfde40 100644 --- a/Source/MQTTnet.TestApp/ServerTest.cs +++ b/Source/MQTTnet.TestApp/ServerTest.cs @@ -148,7 +148,7 @@ public static async Task RunAsync() var payloadText = string.Empty; if (e.ApplicationMessage.Payload.Length > 0) { - payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.Payload); + payloadText = Encoding.UTF8.GetString(e.ApplicationMessage.Payload.Sequence); } MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta); diff --git a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs index 795ac7116..ecb1e518c 100644 --- a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs @@ -509,7 +509,7 @@ public async Task Publish_QoS_1_In_ApplicationMessageReceiveHandler() client2.ApplicationMessageReceivedAsync += e => { - client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload)); + client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload.Sequence)); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs index 8c079df3e..87567ab35 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Binary_Tests.cs @@ -330,7 +330,7 @@ public void Serialize_LargePacket() Assert.IsNotNull(publishPacketCopy); Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic); - CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), publishPacketCopy.Payload.ToArray()); + CollectionAssert.AreEqual(publishPacket.Payload.Sequence.ToArray(), publishPacketCopy.Payload.Sequence.ToArray()); // Now modify the payload and test again. publishPacket.Payload = new ReadOnlySequence(Encoding.UTF8.GetBytes("MQTT")); @@ -340,7 +340,7 @@ public void Serialize_LargePacket() Assert.IsNotNull(publishPacketCopy2); Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic); - CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), publishPacketCopy2.Payload.ToArray()); + CollectionAssert.AreEqual(publishPacket.Payload.Sequence.ToArray(), publishPacketCopy2.Payload.Sequence.ToArray()); } [TestMethod] diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs index 1f3349d0e..c5bf1078c 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V3_Tests.cs @@ -322,7 +322,7 @@ public void Serialize_Full_MqttPublishPacket_V311() Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Retain, deserialized.Retain); - CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), deserialized.Payload.ToArray()); + CollectionAssert.AreEqual(publishPacket.Payload.Sequence.ToArray(), deserialized.Payload.Sequence.ToArray()); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1. diff --git a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs index 69bd67a7c..766be15d3 100644 --- a/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs +++ b/Source/MQTTnet.Tests/Formatter/MqttPacketSerialization_V5_Tests.cs @@ -284,7 +284,7 @@ public void Serialize_Full_MqttPublishPacket_V500() Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier); Assert.AreEqual(publishPacket.Dup, deserialized.Dup); Assert.AreEqual(publishPacket.Retain, deserialized.Retain); - CollectionAssert.AreEqual(publishPacket.Payload.ToArray(), deserialized.Payload.ToArray()); + CollectionAssert.AreEqual(publishPacket.Payload.Sequence.ToArray(), deserialized.Payload.Sequence.ToArray()); Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel); Assert.AreEqual(publishPacket.Topic, deserialized.Topic); Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic); diff --git a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs index 4ec523337..8cd119f2f 100644 --- a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs +++ b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs @@ -277,7 +277,7 @@ public async Task Publish_And_Receive_New_Properties() Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic); Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval); CollectionAssert.AreEqual(applicationMessage.CorrelationData, receivedMessage.CorrelationData); - CollectionAssert.AreEqual(applicationMessage.Payload.ToArray(), receivedMessage.Payload.ToArray()); + CollectionAssert.AreEqual(applicationMessage.Payload.Sequence.ToArray(), receivedMessage.Payload.Sequence.ToArray()); CollectionAssert.AreEqual(applicationMessage.UserProperties, receivedMessage.UserProperties); } finally diff --git a/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs b/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs index cf17e9fee..7115280fd 100644 --- a/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs +++ b/Source/MQTTnet.Tests/MqttApplicationMessageBuilder_Tests.cs @@ -31,7 +31,7 @@ public void CreateApplicationMessage_TimeStampPayload() Assert.AreEqual("xyz", message.Topic); Assert.IsFalse(message.Retain); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00"); + Assert.AreEqual(Encoding.UTF8.GetString(message.Payload.Sequence), "00:06:00"); } [TestMethod] @@ -43,7 +43,7 @@ public void CreateApplicationMessage_StreamPayload() Assert.AreEqual("123", message.Topic); Assert.IsFalse(message.Retain); Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel); - Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello"); + Assert.AreEqual(Encoding.UTF8.GetString(message.Payload.Sequence), "Hello"); } [TestMethod] diff --git a/Source/MQTTnet.Tests/Server/General.cs b/Source/MQTTnet.Tests/Server/General.cs index 072848408..9ef75a155 100644 --- a/Source/MQTTnet.Tests/Server/General.cs +++ b/Source/MQTTnet.Tests/Server/General.cs @@ -316,7 +316,7 @@ public async Task Intercept_Message() var isIntercepted = false; c2.ApplicationMessageReceivedAsync += e => { - isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; + isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload.Sequence), StringComparison.Ordinal) == 0; return CompletedTask.Instance; }; @@ -782,7 +782,7 @@ public async Task Send_Long_Body() var client1 = await testEnvironment.ConnectClient(); client1.ApplicationMessageReceivedAsync += e => { - receivedBody = e.ApplicationMessage.Payload.ToArray(); + receivedBody = e.ApplicationMessage.Payload.Sequence.ToArray(); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet/Buffers/MqttPayload.cs b/Source/MQTTnet/Buffers/MqttPayloadOwner.cs similarity index 57% rename from Source/MQTTnet/Buffers/MqttPayload.cs rename to Source/MQTTnet/Buffers/MqttPayloadOwner.cs index 7323e0775..62e8a51a9 100644 --- a/Source/MQTTnet/Buffers/MqttPayload.cs +++ b/Source/MQTTnet/Buffers/MqttPayloadOwner.cs @@ -11,29 +11,29 @@ namespace MQTTnet.Buffers /// Owner of that is responsible /// for disposing the underlying payload appropriately. /// - public struct MqttPayload : IReadOnlySequenceOwner + public struct MqttPayloadOwner : IReadOnlySequenceOwner { - public MqttPayload() + public MqttPayloadOwner() { Initialize(ReadOnlySequence.Empty, null); } - public MqttPayload(T[] memory, IDisposable owner = null) + public MqttPayloadOwner(T[] memory, IDisposable owner = null) { Initialize(new ReadOnlySequence(memory), owner); } - public MqttPayload(ReadOnlySequence sequence, IDisposable owner = null) + public MqttPayloadOwner(ReadOnlySequence sequence, IDisposable owner = null) { Initialize(sequence, owner); } - public MqttPayload(ReadOnlyMemory memory, IDisposable owner = null) + public MqttPayloadOwner(ReadOnlyMemory memory, IDisposable owner = null) { Initialize(new ReadOnlySequence(memory), owner); } - public MqttPayload(ArraySegment memory, IDisposable owner = null) + public MqttPayloadOwner(ArraySegment memory, IDisposable owner = null) { Initialize(new ReadOnlySequence(memory), owner); } @@ -57,6 +57,11 @@ private void Initialize(ReadOnlySequence sequence, IDisposable owner) /// public IDisposable Owner => _owner; + /// + /// Gets the length of the . + /// + public long Length => _sequence.Length; + /// /// Frees the underlying memory and sets the to empty. /// @@ -64,6 +69,13 @@ public void Dispose() { _sequence = ReadOnlySequence.Empty; _owner?.Dispose(); + _owner = null; } + + public static implicit operator MqttPayloadOwner(ArrayPoolMemoryOwner memoryOwner) => new MqttPayloadOwner(memoryOwner.Memory, memoryOwner); + public static implicit operator MqttPayloadOwner(ReadOnlySequence sequence) => new MqttPayloadOwner(sequence); + public static implicit operator MqttPayloadOwner(ReadOnlyMemory memory) => new MqttPayloadOwner(memory); + public static implicit operator MqttPayloadOwner(ArraySegment memory) => new MqttPayloadOwner(memory); + public static implicit operator MqttPayloadOwner(T[] memory) => new MqttPayloadOwner(memory); } } diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 3f275f933..376493de0 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -295,7 +295,7 @@ public Task PublishAsync(MqttApplicationMessage applica } finally { - applicationMessage?.PayloadOwner?.Dispose(); + applicationMessage?.Payload.Dispose(); } } diff --git a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs index 0f3e28ac0..1fd23bf70 100644 --- a/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs +++ b/Source/MQTTnet/Formatter/MqttApplicationMessageFactory.cs @@ -20,7 +20,6 @@ public static MqttApplicationMessage Create(MqttPublishPacket publishPacket) { Topic = publishPacket.Topic, Payload = publishPacket.Payload, - PayloadOwner = publishPacket.PayloadOwner, QualityOfServiceLevel = publishPacket.QualityOfServiceLevel, Retain = publishPacket.Retain, Dup = publishPacket.Dup, diff --git a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs index 5e89fa8da..761e60a54 100644 --- a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs @@ -23,7 +23,6 @@ public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage { Topic = applicationMessage.Topic, Payload = applicationMessage.Payload, - PayloadOwner = applicationMessage.PayloadOwner, QualityOfServiceLevel = applicationMessage.QualityOfServiceLevel, Retain = applicationMessage.Retain, Dup = applicationMessage.Dup, diff --git a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs index 1ab0d67e5..348845fd5 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs @@ -107,7 +107,7 @@ public MqttPacketBuffer Encode(MqttPacket packet) ReadOnlySequence payload = default; if (packet is MqttPublishPacket publishPacket) { - payload = publishPacket.Payload; + payload = publishPacket.Payload.Sequence; remainingLength += (uint)payload.Length; } @@ -280,8 +280,7 @@ MqttPacket DecodePublishPacket(ReceivedMqttPacket receivedMqttPacket) if (!_bufferReader.EndOfStream) { IMemoryOwner payloadOwner = _bufferReader.ReadPayload(); - packet.Payload = new ReadOnlySequence(payloadOwner.Memory); - packet.PayloadOwner = payloadOwner; + packet.Payload = new MqttPayloadOwner(payloadOwner.Memory, payloadOwner); } return packet; diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs index f1ca69d0d..5643869fa 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketDecoder.cs @@ -542,8 +542,7 @@ MqttPacket DecodePublishPacket(byte header, ArraySegment body) if (!_bufferReader.EndOfStream) { IMemoryOwner payloadOwner = _bufferReader.ReadPayload(); - packet.Payload = new ReadOnlySequence(payloadOwner.Memory); - packet.PayloadOwner = payloadOwner; + packet.Payload = new MqttPayloadOwner(payloadOwner.Memory, payloadOwner); } return packet; diff --git a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs index 365a06b15..070e2ad4a 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV5PacketEncoder.cs @@ -42,7 +42,7 @@ public MqttPacketBuffer Encode(MqttPacket packet) ReadOnlySequence payload = default; if (packet is MqttPublishPacket publishPacket) { - payload = publishPacket.Payload; + payload = publishPacket.Payload.Sequence; remainingLength += (uint)payload.Length; } diff --git a/Source/MQTTnet/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs index d1fc44c65..f70bd62be 100644 --- a/Source/MQTTnet/MqttApplicationMessage.cs +++ b/Source/MQTTnet/MqttApplicationMessage.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using MQTTnet.Buffers; using MQTTnet.Packets; using MQTTnet.Protocol; using System; @@ -24,8 +25,7 @@ public MqttApplicationMessage Clone() CorrelationData = this.CorrelationData, Dup = this.Dup, MessageExpiryInterval = this.MessageExpiryInterval, - Payload = new ReadOnlySequence(this.Payload.ToArray()), - PayloadOwner = null, + Payload = this.Payload.Sequence.ToArray(), PayloadFormatIndicator = this.PayloadFormatIndicator, QualityOfServiceLevel = this.QualityOfServiceLevel, ResponseTopic = this.ResponseTopic, @@ -42,9 +42,7 @@ public MqttApplicationMessage Clone() /// public void DisposePayload() { - Payload = ReadOnlySequence.Empty; - PayloadOwner?.Dispose(); - PayloadOwner = null; + Payload.Dispose(); } /// @@ -85,14 +83,9 @@ public void DisposePayload() public uint MessageExpiryInterval { get; set; } /// - /// Get or set ReadOnlySequence style of Payload. + /// Get or set Mqtt Payload owner. /// - public ReadOnlySequence Payload { get; set; } - - /// - /// Get or set the owner of the memory. - /// - public IDisposable PayloadOwner { get; set; } + public MqttPayloadOwner Payload{ get; set; } /// /// Gets or sets the payload format indicator. diff --git a/Source/MQTTnet/MqttApplicationMessageBuilder.cs b/Source/MQTTnet/MqttApplicationMessageBuilder.cs index 71f604880..c8eda8adb 100644 --- a/Source/MQTTnet/MqttApplicationMessageBuilder.cs +++ b/Source/MQTTnet/MqttApplicationMessageBuilder.cs @@ -43,7 +43,6 @@ public MqttApplicationMessage Build() { Topic = _topic, Payload = _payload, - PayloadOwner = _payloadOwner, QualityOfServiceLevel = _qualityOfServiceLevel, Retain = _retain, ContentType = _contentType, diff --git a/Source/MQTTnet/MqttApplicationMessageExtensions.cs b/Source/MQTTnet/MqttApplicationMessageExtensions.cs index f111611e0..83ed9306f 100644 --- a/Source/MQTTnet/MqttApplicationMessageExtensions.cs +++ b/Source/MQTTnet/MqttApplicationMessageExtensions.cs @@ -21,6 +21,6 @@ public static string ConvertPayloadToString(this MqttApplicationMessage applicat return null; } - return Encoding.UTF8.GetString(applicationMessage.Payload); + return Encoding.UTF8.GetString(applicationMessage.Payload.Sequence); } } \ No newline at end of file diff --git a/Source/MQTTnet/Packets/MqttPublishPacket.cs b/Source/MQTTnet/Packets/MqttPublishPacket.cs index 3b999ceb9..d379d0397 100644 --- a/Source/MQTTnet/Packets/MqttPublishPacket.cs +++ b/Source/MQTTnet/Packets/MqttPublishPacket.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using MQTTnet.Buffers; using MQTTnet.Protocol; using System; using System.Buffers; @@ -21,9 +22,7 @@ public class MqttPublishPacket : MqttPacketWithIdentifier public MqttPayloadFormatIndicator PayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified; - public ReadOnlySequence Payload { get; set; } - - public IDisposable PayloadOwner { get; set; } + public MqttPayloadOwner Payload { get; set; } public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } = MqttQualityOfServiceLevel.AtMostOnce; From d60c7c0a3fe8bf3b2d339604e55e9a1b4b61961c Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Wed, 26 Jun 2024 07:24:08 +0200 Subject: [PATCH 16/19] some renaming --- .../Internal/MqttRetainedMessagesManager.cs | 2 +- Source/MQTTnet.TestApp/PublicBrokerTest.cs | 3 +-- .../Clients/MqttClient/MqttClient_Tests.cs | 3 +-- Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs | 5 ++-- .../TestApplicationMessageReceivedHandler.cs | 6 ++--- Source/MQTTnet/Buffers/MqttPayloadOwner.cs | 12 +++++++++ Source/MQTTnet/Client/MqttClient.cs | 4 +-- ...MqttApplicationMessageReceivedEventArgs.cs | 25 +++++++++++-------- Source/MQTTnet/MQTTnet.csproj | 3 ++- Source/MQTTnet/MqttApplicationMessage.cs | 8 +++--- 10 files changed, 42 insertions(+), 29 deletions(-) diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index df2aa72f4..1159a2fcf 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -110,7 +110,7 @@ public async Task UpdateMessage(string clientId, MqttApplicationMessage applicat } } - applicationMessage.DisposePayload(); + applicationMessage.Dispose(); } catch (Exception exception) { diff --git a/Source/MQTTnet.TestApp/PublicBrokerTest.cs b/Source/MQTTnet.TestApp/PublicBrokerTest.cs index 3e9e326fd..d1b0cf462 100644 --- a/Source/MQTTnet.TestApp/PublicBrokerTest.cs +++ b/Source/MQTTnet.TestApp/PublicBrokerTest.cs @@ -144,8 +144,7 @@ static async Task ExecuteTestAsync(string name, MqttClientOptions options) MqttApplicationMessage receivedMessage = null; client.ApplicationMessageReceivedAsync += e => { - e.TransferPayload(true); - receivedMessage = e.ApplicationMessage; + receivedMessage = e.TransferApplicationMessageOwnership(true); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs index ecb1e518c..0d1f5c593 100644 --- a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs @@ -870,8 +870,7 @@ public async Task Subscribe_In_Callback_Events() { lock (receivedMessages) { - e.TransferPayload(true); - receivedMessages.Add(e.ApplicationMessage); + receivedMessages.Add(e.TransferApplicationMessageOwnership(true)); } return CompletedTask.Instance; diff --git a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs index 8cd119f2f..1c508d1bc 100644 --- a/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs +++ b/Source/MQTTnet.Tests/MQTTv5/Client_Tests.cs @@ -244,8 +244,7 @@ public async Task Publish_And_Receive_New_Properties() MqttApplicationMessage receivedMessage = null; receiver.ApplicationMessageReceivedAsync += e => { - e.TransferPayload(false); - receivedMessage = e.ApplicationMessage; + receivedMessage = e.TransferApplicationMessageOwnership(true); return CompletedTask.Instance; }; @@ -282,7 +281,7 @@ public async Task Publish_And_Receive_New_Properties() } finally { - receivedMessage?.DisposePayload(); + receivedMessage?.Dispose(); } } } diff --git a/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs b/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs index b9d8d07bb..679815b7d 100644 --- a/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs +++ b/Source/MQTTnet.Tests/Mockups/TestApplicationMessageReceivedHandler.cs @@ -21,7 +21,7 @@ public void Dispose() { foreach (var eventArgs in _receivedEventArgs) { - eventArgs.ApplicationMessage?.DisposePayload(); + eventArgs.ApplicationMessage?.Dispose(); } } @@ -84,8 +84,8 @@ Task OnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs e { lock (_receivedEventArgs) { - // take ownership of message payload to avoid cloning - eventArgs.TransferPayload(false); + // take ownership of application message to avoid cloning + eventArgs.TransferApplicationMessageOwnership(false); _receivedEventArgs.Add(eventArgs); } diff --git a/Source/MQTTnet/Buffers/MqttPayloadOwner.cs b/Source/MQTTnet/Buffers/MqttPayloadOwner.cs index 62e8a51a9..efd59674b 100644 --- a/Source/MQTTnet/Buffers/MqttPayloadOwner.cs +++ b/Source/MQTTnet/Buffers/MqttPayloadOwner.cs @@ -72,6 +72,18 @@ public void Dispose() _owner = null; } + /// + /// Returns a new with the same + /// and transfers the ownership + /// to the caller. + /// + public MqttPayloadOwner TransferOwnership() + { + var payload = new MqttPayloadOwner(_sequence, _owner); + _owner = null; + return payload; + } + public static implicit operator MqttPayloadOwner(ArrayPoolMemoryOwner memoryOwner) => new MqttPayloadOwner(memoryOwner.Memory, memoryOwner); public static implicit operator MqttPayloadOwner(ReadOnlySequence sequence) => new MqttPayloadOwner(sequence); public static implicit operator MqttPayloadOwner(ReadOnlyMemory memory) => new MqttPayloadOwner(memory); diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 376493de0..857d10343 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -720,9 +720,9 @@ async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken) } finally { - if (eventArgs?.TransferredPayload == false) + if (eventArgs?.DisposeApplicationMessage == true) { - eventArgs.ApplicationMessage?.DisposePayload(); + eventArgs.ApplicationMessage?.Dispose(); } } } diff --git a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs index 49e34cca3..0d37fd55f 100644 --- a/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs +++ b/Source/MQTTnet/Client/Receiving/MqttApplicationMessageReceivedEventArgs.cs @@ -29,26 +29,29 @@ public MqttApplicationMessageReceivedEventArgs( } /// - /// The invoked message receiver can take ownership of the payload to avoid cloning. - /// If not cloned, it is the obligation of the new owner to dispose the payload by - /// calling . + /// The invoked message receiver can take ownership of the application + /// message with payload to avoid cloning. + /// It is then the obligation of the new owner to dispose the obtained + /// application message. /// /// - /// If set to true, clones the applicationMessage and copies the payload. - /// The new instance does not need to be disposed. + /// If set to true, clones the ApplicationMessage and copies the payload. /// - public void TransferPayload(bool clonePayload) + public MqttApplicationMessage TransferApplicationMessageOwnership(bool clonePayload) { - TransferredPayload = true; + DisposeApplicationMessage = false; if (clonePayload) { var applicationMessage = ApplicationMessage; - if (applicationMessage != null) + // replace application message with a clone + // if the payload is owner managed + if (applicationMessage?.Payload.Owner != null) { ApplicationMessage = applicationMessage.Clone(); - applicationMessage.DisposePayload(); + applicationMessage.Dispose(); } } + return ApplicationMessage; } public MqttApplicationMessage ApplicationMessage { get; private set; } @@ -68,10 +71,10 @@ public void TransferPayload(bool clonePayload) /// Gets or sets whether the ownership of the message payload /// was handed over to the invoked code. This value determines /// if the payload can be disposed after the callback returns. - /// If transferred, the new owner is responsible + /// If transferred, the new owner of the message is responsible /// to dispose the payload after processing. /// - public bool TransferredPayload { get; private set; } = false; + public bool DisposeApplicationMessage { get; private set; } = true; /// /// Gets or sets whether this message was handled. diff --git a/Source/MQTTnet/MQTTnet.csproj b/Source/MQTTnet/MQTTnet.csproj index b97c68faa..fb3e0e5c6 100644 --- a/Source/MQTTnet/MQTTnet.csproj +++ b/Source/MQTTnet/MQTTnet.csproj @@ -44,10 +44,11 @@ all true latest-Recommended + latest - Full + portable diff --git a/Source/MQTTnet/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs index f70bd62be..855eb2f06 100644 --- a/Source/MQTTnet/MqttApplicationMessage.cs +++ b/Source/MQTTnet/MqttApplicationMessage.cs @@ -11,11 +11,11 @@ namespace MQTTnet { - public sealed class MqttApplicationMessage + public sealed class MqttApplicationMessage : IDisposable { /// - /// Create a clone of the - /// with a deep copy of the Payload which is cleaned up by the GC. + /// Create a clone of the . + /// with a deep copy of the Payload allocated from the heap. /// public MqttApplicationMessage Clone() { @@ -40,7 +40,7 @@ public MqttApplicationMessage Clone() /// /// Disposes the payload used by the current instance of the class. /// - public void DisposePayload() + public void Dispose() { Payload.Dispose(); } From a3fb0facce47f7e3266bd8533231292380a8c79a Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Thu, 4 Jul 2024 18:52:39 +0200 Subject: [PATCH 17/19] fix issue with payload transfer and potential overread due to array pool buffer size --- Source/MQTTnet/Adapter/MqttChannelAdapter.cs | 2 +- Source/MQTTnet/Buffers/MqttPayloadOwner.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs index f55abc40a..ef2cde1bf 100644 --- a/Source/MQTTnet/Adapter/MqttChannelAdapter.cs +++ b/Source/MQTTnet/Adapter/MqttChannelAdapter.cs @@ -420,7 +420,7 @@ async Task ReceiveAsync(CancellationToken cancellationToken) var bodyArray = mqttPacket.Body.Array; do { - var bytesLeft = bodyArray.Length - bodyOffset; + var bytesLeft = bodyLength - bodyOffset; if (chunkSize > bytesLeft) { chunkSize = bytesLeft; diff --git a/Source/MQTTnet/Buffers/MqttPayloadOwner.cs b/Source/MQTTnet/Buffers/MqttPayloadOwner.cs index efd59674b..9c15c9950 100644 --- a/Source/MQTTnet/Buffers/MqttPayloadOwner.cs +++ b/Source/MQTTnet/Buffers/MqttPayloadOwner.cs @@ -11,7 +11,7 @@ namespace MQTTnet.Buffers /// Owner of that is responsible /// for disposing the underlying payload appropriately. /// - public struct MqttPayloadOwner : IReadOnlySequenceOwner + public class MqttPayloadOwner : IReadOnlySequenceOwner { public MqttPayloadOwner() { From 1b86ded40ba12ff6586ccc30c06e88fb3b2b6f09 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Thu, 4 Jul 2024 22:35:01 +0200 Subject: [PATCH 18/19] improve calculation of buffer for utf 8 --- Source/MQTTnet/Buffers/MqttBufferWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/MQTTnet/Buffers/MqttBufferWriter.cs b/Source/MQTTnet/Buffers/MqttBufferWriter.cs index b0ed9429f..6212fc893 100644 --- a/Source/MQTTnet/Buffers/MqttBufferWriter.cs +++ b/Source/MQTTnet/Buffers/MqttBufferWriter.cs @@ -180,9 +180,9 @@ public void WriteString(string value) // UTF8 chars can have a max length of 4 and the used buffer increase *2 every time. // So the buffer should always have much more capacity left so that a correct value // here is only waste of CPU cycles. - var byteCount = value.Length * 4; + var maxByteCount = Encoding.UTF8.GetMaxByteCount(value.Length); - EnsureAdditionalCapacity(byteCount + 2); + EnsureAdditionalCapacity(maxByteCount + 2); var writtenBytes = Encoding.UTF8.GetBytes(value, 0, value.Length, _buffer, _position + 2); From 4c99517c9af668591ded73e50c7ad23f732ae3ac Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Wed, 10 Jul 2024 11:14:12 +0200 Subject: [PATCH 19/19] fix tests, catch access to disposed payload --- .../Internal/MqttRetainedMessagesManager.cs | 2 - .../Clients/MqttClient/MqttClient_Tests.cs | 19 ++++---- Source/MQTTnet/Buffers/EmptyMemoryOwner.cs | 1 - Source/MQTTnet/Buffers/MqttPayloadOwner.cs | 9 ++-- Source/MQTTnet/Client/MqttClient.cs | 43 ++++++++---------- .../Formatter/MqttPublishPacketFactory.cs | 1 - Source/MQTTnet/MqttApplicationMessage.cs | 45 ++++++++++++++++++- 7 files changed, 74 insertions(+), 46 deletions(-) diff --git a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs index 1159a2fcf..2505671bf 100644 --- a/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet.Server/Internal/MqttRetainedMessagesManager.cs @@ -109,8 +109,6 @@ public async Task UpdateMessage(string clientId, MqttApplicationMessage applicat await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); } } - - applicationMessage.Dispose(); } catch (Exception exception) { diff --git a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs index 0d1f5c593..02938911f 100644 --- a/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs +++ b/Source/MQTTnet.Tests/Clients/MqttClient/MqttClient_Tests.cs @@ -2,15 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; using MQTTnet.Exceptions; @@ -20,6 +11,14 @@ using MQTTnet.Protocol; using MQTTnet.Server; using MQTTnet.Tests.Mockups; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; // ReSharper disable InconsistentNaming @@ -288,7 +287,7 @@ await receiver.SubscribeAsync( MqttApplicationMessage receivedMessage = null; receiver.ApplicationMessageReceivedAsync += e => { - receivedMessage = e.ApplicationMessage; + receivedMessage = e.ApplicationMessage.Clone(); return CompletedTask.Instance; }; diff --git a/Source/MQTTnet/Buffers/EmptyMemoryOwner.cs b/Source/MQTTnet/Buffers/EmptyMemoryOwner.cs index e26738f0f..66f3463f3 100644 --- a/Source/MQTTnet/Buffers/EmptyMemoryOwner.cs +++ b/Source/MQTTnet/Buffers/EmptyMemoryOwner.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; -using System.Linq; namespace MQTTnet.Buffers { diff --git a/Source/MQTTnet/Buffers/MqttPayloadOwner.cs b/Source/MQTTnet/Buffers/MqttPayloadOwner.cs index 9c15c9950..f9f04c9d5 100644 --- a/Source/MQTTnet/Buffers/MqttPayloadOwner.cs +++ b/Source/MQTTnet/Buffers/MqttPayloadOwner.cs @@ -11,7 +11,7 @@ namespace MQTTnet.Buffers /// Owner of that is responsible /// for disposing the underlying payload appropriately. /// - public class MqttPayloadOwner : IReadOnlySequenceOwner + public struct MqttPayloadOwner : IReadOnlySequenceOwner { public MqttPayloadOwner() { @@ -77,14 +77,13 @@ public void Dispose() /// and transfers the ownership /// to the caller. /// - public MqttPayloadOwner TransferOwnership() + public static MqttPayloadOwner TransferOwnership(ref MqttPayloadOwner payloadOwner) { - var payload = new MqttPayloadOwner(_sequence, _owner); - _owner = null; + var payload = new MqttPayloadOwner(payloadOwner._sequence, payloadOwner._owner); + payloadOwner._owner = null; return payload; } - public static implicit operator MqttPayloadOwner(ArrayPoolMemoryOwner memoryOwner) => new MqttPayloadOwner(memoryOwner.Memory, memoryOwner); public static implicit operator MqttPayloadOwner(ReadOnlySequence sequence) => new MqttPayloadOwner(sequence); public static implicit operator MqttPayloadOwner(ReadOnlyMemory memory) => new MqttPayloadOwner(memory); public static implicit operator MqttPayloadOwner(ArraySegment memory) => new MqttPayloadOwner(memory); diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 857d10343..948ad9331 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -269,33 +269,26 @@ public Task PublishAsync(MqttApplicationMessage applica MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion); } - try - { - var publishPacket = MqttPublishPacketFactory.Create(applicationMessage); + var publishPacket = MqttPublishPacketFactory.Create(applicationMessage); - switch (applicationMessage.QualityOfServiceLevel) - { - case MqttQualityOfServiceLevel.AtMostOnce: - { - return PublishAtMostOnce(publishPacket, cancellationToken); - } - case MqttQualityOfServiceLevel.AtLeastOnce: - { - return PublishAtLeastOnce(publishPacket, cancellationToken); - } - case MqttQualityOfServiceLevel.ExactlyOnce: - { - return PublishExactlyOnce(publishPacket, cancellationToken); - } - default: - { - throw new NotSupportedException(); - } - } - } - finally + switch (applicationMessage.QualityOfServiceLevel) { - applicationMessage?.Payload.Dispose(); + case MqttQualityOfServiceLevel.AtMostOnce: + { + return PublishAtMostOnce(publishPacket, cancellationToken); + } + case MqttQualityOfServiceLevel.AtLeastOnce: + { + return PublishAtLeastOnce(publishPacket, cancellationToken); + } + case MqttQualityOfServiceLevel.ExactlyOnce: + { + return PublishExactlyOnce(publishPacket, cancellationToken); + } + default: + { + throw new NotSupportedException(); + } } } diff --git a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs index 761e60a54..e63239064 100644 --- a/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttPublishPacketFactory.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using MQTTnet.Exceptions; using MQTTnet.Packets; namespace MQTTnet.Formatter diff --git a/Source/MQTTnet/MqttApplicationMessage.cs b/Source/MQTTnet/MqttApplicationMessage.cs index 855eb2f06..82b3bf1bd 100644 --- a/Source/MQTTnet/MqttApplicationMessage.cs +++ b/Source/MQTTnet/MqttApplicationMessage.cs @@ -13,6 +13,9 @@ namespace MQTTnet { public sealed class MqttApplicationMessage : IDisposable { + private bool _disposed; + private MqttPayloadOwner _payload; + /// /// Create a clone of the . /// with a deep copy of the Payload allocated from the heap. @@ -37,12 +40,27 @@ public MqttApplicationMessage Clone() }; } + /// + /// Transfers the payload ownership to the caller. + /// + /// + /// This method is used to transfer the ownership of the payload to the caller. + /// It returns the with a reference to the + /// payload owner and sets the owner in this application message to null. + /// After the transfer the caller is responsible to dispose the payload. + /// + public MqttPayloadOwner TransferPayloadOwnership() + { + return MqttPayloadOwner.TransferOwnership(ref _payload); + } + /// /// Disposes the payload used by the current instance of the class. /// public void Dispose() { - Payload.Dispose(); + _disposed = true; + _payload.Dispose(); } /// @@ -85,7 +103,30 @@ public void Dispose() /// /// Get or set Mqtt Payload owner. /// - public MqttPayloadOwner Payload{ get; set; } + /// + /// is a struct that wraps a + /// and provides a way to manage the lifetime of the buffers. Special care has to be + /// taken to dispose the object, because it is a struct with a " method + /// which must be called on this instance to properly track the owner. The property always + /// returns a Value type which has no owner, to avoid double dispose and double ownership./> + /// + public MqttPayloadOwner Payload + { + get + { + if (_disposed) + { + throw new ObjectDisposedException("Accessing the MqttApplicationMessage.Payload which is already disposed"); + } + + // Since the payload is a value type, do not pass the owner, + // so we return a new instance which contains only the sequence. + // There is no allocation involved, because the sequence is a value type. + return new MqttPayloadOwner(_payload.Sequence, null); + } + + set => _payload = value; + } /// /// Gets or sets the payload format indicator.