diff --git a/SignalR.sln b/SignalR.sln index 0a0d5e979a..9436f9d8a2 100644 --- a/SignalR.sln +++ b/SignalR.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26510.0 +VisualStudioVersion = 15.0.26606.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DA69F624-5398-4884-87E4-B816698CDE65}" EndProject @@ -52,8 +52,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Signal EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{6CEC3DC2-5B01-45A8-8F0D-8531315DA90B}" ProjectSection(SolutionItems) = preProject - test\Common\ArrayOutput.cs = test\Common\ArrayOutput.cs - test\Common\ByteArrayExtensions.cs = test\Common\ByteArrayExtensions.cs test\Common\ChannelExtensions.cs = test\Common\ChannelExtensions.cs test\Common\TaskExtensions.cs = test\Common\TaskExtensions.cs EndProjectSection diff --git a/build/dependencies.props b/build/dependencies.props index a2f64ad9f2..7cf7a55447 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,6 +3,7 @@ 0.4.0-* 2.0.0-* 0.1.0-* + 4.4.0-* 3.1.0 2.1.0-* 10.0.1 diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/EchoEndPoint.cs b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/EchoEndPoint.cs index 2852c885ae..835bbbd2f0 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/EchoEndPoint.cs +++ b/client-ts/Microsoft.AspNetCore.SignalR.Test.Server/EchoEndPoint.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO.Pipelines; using System.Threading.Tasks; using Microsoft.AspNetCore.Sockets; diff --git a/samples/SocialWeather/SocialWeatherEndPoint.cs b/samples/SocialWeather/SocialWeatherEndPoint.cs index 685787d559..2e8e4fa7da 100644 --- a/samples/SocialWeather/SocialWeatherEndPoint.cs +++ b/samples/SocialWeather/SocialWeatherEndPoint.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; -using System.IO.Pipelines; using System.Threading.Tasks; using Microsoft.AspNetCore.Sockets; using Microsoft.Extensions.Logging; diff --git a/samples/SocketsSample/EndPoints/MessagesEndPoint.cs b/samples/SocketsSample/EndPoints/MessagesEndPoint.cs index 4d910b1f66..eee54395e2 100644 --- a/samples/SocketsSample/EndPoints/MessagesEndPoint.cs +++ b/samples/SocketsSample/EndPoints/MessagesEndPoint.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using System.IO.Pipelines; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Sockets; diff --git a/src/Common/IOutputExtensions.cs b/src/Common/IOutputExtensions.cs deleted file mode 100644 index 19b5d3b1e4..0000000000 --- a/src/Common/IOutputExtensions.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Binary; -using System.Runtime; -using System.Runtime.CompilerServices; - -namespace System.Buffers -{ - internal static class IOutputExtensions - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryWriteBigEndian<[Primitive] T>(this IOutput self, T value) where T : struct - { - var size = Unsafe.SizeOf(); - if (self.Buffer.Length < size) - { - self.Enlarge(size); - if (self.Buffer.Length < size) - { - return false; - } - } - - self.Buffer.WriteBigEndian(value); - self.Advance(size); - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryWrite(this IOutput self, ReadOnlySpan data) - { - while (data.Length > 0) - { - if (self.Buffer.Length == 0) - { - self.Enlarge(data.Length); - if (self.Buffer.Length == 0) - { - // Failed to enlarge - return false; - } - } - - var toWrite = Math.Min(self.Buffer.Length, data.Length); - - // Slice based on what we can fit - var chunk = data.Slice(0, toWrite); - data = data.Slice(toWrite); - - // Copy the chunk - chunk.CopyTo(self.Buffer); - self.Advance(chunk.Length); - } - - return true; - } - } -} diff --git a/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs b/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs index fb6678d1b8..afbc2ad32a 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs @@ -133,7 +133,7 @@ namespace Microsoft.AspNetCore.SignalR.Client { try { - var payload = await _protocol.WriteToArrayAsync(invocationMessage); + var payload = _protocol.WriteToArray(invocationMessage); _logger.LogInformation("Sending Invocation '{invocationId}'", invocationMessage.InvocationId); diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs index 3d8df0ca8c..4a9b3ecab8 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs @@ -2,24 +2,26 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Binary; using System.Buffers; +using System.IO; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { public static class BinaryMessageFormatter { - public static bool TryWriteMessage(ReadOnlySpan payload, IOutput output) + public static bool TryWriteMessage(ReadOnlySpan payload, MemoryStream output) { - // Try to write the data - if (!output.TryWriteBigEndian((long)payload.Length)) - { - return false; - } + var length = sizeof(long); + var buffer = ArrayPool.Shared.Rent(length); + BufferWriter.WriteBigEndian(buffer, payload.Length); + output.Write(buffer, 0, length); + ArrayPool.Shared.Return(buffer); - if (!output.TryWrite(payload)) - { - return false; - } + buffer = ArrayPool.Shared.Rent(payload.Length); + payload.CopyTo(buffer); + output.Write(buffer, 0, payload.Length); + ArrayPool.Shared.Return(buffer); return true; } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs index 8cc02cfa3e..56c1c52793 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs @@ -3,7 +3,6 @@ using System; using System.Binary; -using System.Buffers; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { @@ -16,33 +15,27 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters _state = default(ParserState); } - public bool TryParseMessage(ref BytesReader buffer, out ReadOnlyBuffer payload) + public bool TryParseMessage(ref ReadOnlySpan buffer, out ReadOnlyBuffer payload) { if (_state.Length == null) { - var lengthBuffer = buffer.TryReadBytes(sizeof(long)); + long length = 0; - if (lengthBuffer == null) + if (buffer.Length < sizeof(long)) { payload = default(ReadOnlyBuffer); return false; } - var length = lengthBuffer.Value.ToSingleSpan(); + length = buffer.Slice(0, sizeof(long)).ReadBigEndian(); - if (length.Length < sizeof(long)) - { - payload = default(ReadOnlyBuffer); - return false; - } - - var longLength = length.ReadBigEndian(); - if (longLength > Int32.MaxValue) + if (length > Int32.MaxValue) { throw new FormatException("Messages over 2GB in size are not supported"); } - buffer.Advance(length.Length); - _state.Length = (int)longLength; + + buffer = buffer.Slice(sizeof(long)); + _state.Length = (int)length; } if (_state.Payload == null) @@ -50,13 +43,13 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters _state.Payload = new byte[_state.Length.Value]; } - while (_state.Read < _state.Payload.Length && buffer.Unread.Length > 0) + while (_state.Read < _state.Payload.Length && buffer.Length > 0) { // Copy what we can from the current unread segment - var toCopy = Math.Min(_state.Payload.Length - _state.Read, buffer.Unread.Length); - buffer.Unread.Slice(0, toCopy).CopyTo(_state.Payload.Slice(_state.Read)); + var toCopy = Math.Min(_state.Payload.Length - _state.Read, buffer.Length); + buffer.Slice(0, toCopy).CopyTo(new Span(_state.Payload, _state.Read)); _state.Read += toCopy; - buffer.Advance(toCopy); + buffer = buffer.Slice(toCopy); } if (_state.Read == _state.Payload.Length) diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BufferExtensions.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BufferExtensions.cs deleted file mode 100644 index c9c1f257dd..0000000000 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BufferExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace System.Buffers -{ - internal static class BufferExtensions - { - public static ReadOnlySpan ToSingleSpan(this ReadOnlyBytes self) - { - if (self.Rest == null) - { - return self.First.Span; - } - else - { - return self.ToSpan(); - } - } - - public static ReadOnlyBytes? TryReadBytes(this BytesReader self, int count) - { - try - { - return self.ReadBytes(count); - } - catch (ArgumentOutOfRangeException) - { - return null; - } - } - } -} diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs index ed4a67580a..f66c4880d2 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs @@ -2,37 +2,44 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Binary; using System.Buffers; +using System.Globalization; +using System.IO; using System.Text; -using System.Text.Formatting; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { public static class TextMessageFormatter { + private const int Int32OverflowLength = 10; + internal const char FieldDelimiter = ':'; internal const char MessageDelimiter = ';'; - - public static bool TryWriteMessage(ReadOnlySpan payload, IOutput output) + + public static bool TryWriteMessage(ReadOnlySpan payload, Stream output) { // Calculate the length, it's the number of characters for text messages, but number of base64 characters for binary - var length = payload.Length; // Write the length as a string - output.Append(length, TextEncoder.Utf8); + + // Super inefficient... + var lengthString = payload.Length.ToString(CultureInfo.InvariantCulture); + var buffer = ArrayPool.Shared.Rent(Int32OverflowLength); + var encodedLength = Encoding.UTF8.GetBytes(lengthString, 0, lengthString.Length, buffer, 0); + output.Write(buffer, 0, encodedLength); + ArrayPool.Shared.Return(buffer); // Write the field delimiter ':' - output.Append(FieldDelimiter, TextEncoder.Utf8); + output.WriteByte((byte)FieldDelimiter); - // Write the payload - if (!output.TryWrite(payload)) - { - return false; - } + buffer = ArrayPool.Shared.Rent(payload.Length); + payload.CopyTo(buffer); + output.Write(buffer, 0, payload.Length); + ArrayPool.Shared.Return(buffer); // Terminator - output.Append(MessageDelimiter, TextEncoder.Utf8); + output.WriteByte((byte)MessageDelimiter); + return true; } } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs index 6e7357dce2..9793dcf793 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs @@ -2,13 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Buffers; using System.Text; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { public class TextMessageParser { + private const int Int32OverflowLength = 10; + private ParserState _state; public void Reset() @@ -20,9 +21,9 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters /// Attempts to parse a message from the buffer. Returns 'false' if there is not enough data to complete a message. Throws an /// exception if there is a format error in the provided data. /// - public bool TryParseMessage(ref BytesReader buffer, out ReadOnlyBuffer payload) + public bool TryParseMessage(ref ReadOnlySpan buffer, out ReadOnlyBuffer payload) { - while (buffer.Unread.Length > 0) + while (buffer.Length > 0) { switch (_state.Phase) { @@ -65,53 +66,50 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters return false; } - private bool TryReadLength(ref BytesReader buffer) + private bool TryReadLength(ref ReadOnlySpan buffer) { // Read until the first ':' to find the length - var lengthBuffer = buffer.ReadBytesUntil((byte)TextMessageFormatter.FieldDelimiter); + var found = buffer.IndexOf((byte)TextMessageFormatter.FieldDelimiter); - if (lengthBuffer == null) + if (found == -1) { // Insufficient data return false; } - var lengthSpan = lengthBuffer.Value.ToSingleSpan(); + var lengthSpan = buffer.Slice(0, found); - // Parse the length - if (!PrimitiveParser.TryParseInt32(lengthSpan, out var length, out var consumedByLength, encoder: TextEncoder.Utf8) || consumedByLength < lengthSpan.Length) + if (!TryParseInt32(lengthSpan, out var length, out var bytesConsumed) || bytesConsumed < lengthSpan.Length) { - if (TextEncoder.Utf8.TryDecode(lengthSpan, out var lengthString, out _)) - { - throw new FormatException($"Invalid length: '{lengthString}'"); - } - - throw new FormatException("Invalid length"); + throw new FormatException($"Invalid length: '{Encoding.UTF8.GetString(lengthSpan.ToArray())}'"); } + buffer = buffer.Slice(found); + _state.Length = length; _state.Phase = ParsePhase.LengthComplete; return true; } - private bool TryReadDelimiter(ref BytesReader buffer, char delimiter, ParsePhase nextPhase, string field) + private bool TryReadDelimiter(ref ReadOnlySpan buffer, char delimiter, ParsePhase nextPhase, string field) { - if (buffer.Unread.Length == 0) + if (buffer.Length == 0) { return false; } - if (buffer.Unread[0] != delimiter) + if (buffer[0] != delimiter) { throw new FormatException($"Missing delimiter '{delimiter}' after {field}"); } - buffer.Advance(1); + + buffer = buffer.Slice(1); _state.Phase = nextPhase; return true; } - private void ReadPayload(ref BytesReader buffer) + private void ReadPayload(ref ReadOnlySpan buffer) { if (_state.Payload == null) { @@ -125,13 +123,105 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters else { // Copy as much as possible from the Unread buffer - var toCopy = Math.Min(_state.Length - _state.Read, buffer.Unread.Length); - buffer.Unread.Slice(0, toCopy).CopyTo(_state.Payload.Slice(_state.Read)); + var toCopy = Math.Min(_state.Length - _state.Read, buffer.Length); + + buffer.Slice(0, toCopy).CopyTo(new Span(_state.Payload, _state.Read)); _state.Read += toCopy; - buffer.Advance(toCopy); + buffer = buffer.Slice(toCopy); } } + public static bool TryParseInt32(ReadOnlySpan text, out int value, out int bytesConsumed) + { + if (text.Length < 1) + { + bytesConsumed = 0; + value = default(int); + return false; + } + + int indexOfFirstDigit = 0; + int sign = 1; + if (text[0] == '-') + { + indexOfFirstDigit = 1; + sign = -1; + } + else if (text[0] == '+') + { + indexOfFirstDigit = 1; + } + + int overflowLength = Int32OverflowLength + indexOfFirstDigit; + + // Parse the first digit separately. If invalid here, we need to return false. + int firstDigit = text[indexOfFirstDigit] - 48; // '0' + if (firstDigit < 0 || firstDigit > 9) + { + bytesConsumed = 0; + value = default(int); + return false; + } + int parsedValue = firstDigit; + + if (text.Length < overflowLength) + { + // Length is less than Int32OverflowLength; overflow is not possible + for (int index = indexOfFirstDigit + 1; index < text.Length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + else + { + // Length is greater than Int32OverflowLength; overflow is only possible after Int32OverflowLength + // digits. There may be no overflow after Int32OverflowLength if there are leading zeroes. + for (int index = indexOfFirstDigit + 1; index < overflowLength - 1; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + parsedValue = parsedValue * 10 + nextDigit; + } + for (int index = overflowLength - 1; index < text.Length; index++) + { + int nextDigit = text[index] - 48; // '0' + if (nextDigit < 0 || nextDigit > 9) + { + bytesConsumed = index; + value = parsedValue * sign; + return true; + } + // If parsedValue > (int.MaxValue / 10), any more appended digits will cause overflow. + // if parsedValue == (int.MaxValue / 10), any nextDigit greater than 7 or 8 (depending on sign) implies overflow. + bool positive = sign > 0; + bool nextDigitTooLarge = nextDigit > 8 || (positive && nextDigit > 7); + if (parsedValue > int.MaxValue / 10 || parsedValue == int.MaxValue / 10 && nextDigitTooLarge) + { + bytesConsumed = 0; + value = default(int); + return false; + } + parsedValue = parsedValue * 10 + nextDigit; + } + } + + bytesConsumed = text.Length; + value = parsedValue * sign; + return true; + } + private struct ParserState { public ParsePhase Phase; diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolWriteMessageExtensions.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolWriteMessageExtensions.cs index b53f2909ee..6f499e6cd6 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolWriteMessageExtensions.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/HubProtocolWriteMessageExtensions.cs @@ -3,34 +3,22 @@ using System; using System.IO; -using System.IO.Pipelines; -using System.IO.Pipelines.Text.Primitives; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.AspNetCore.SignalR.Internal.Protocol { public static class HubProtocolWriteMessageExtensions { - public static async ValueTask WriteToArrayAsync(this IHubProtocol protocol, HubMessage message) + public static byte[] WriteToArray(this IHubProtocol protocol, HubMessage message) { - using (var memoryStream = new MemoryStream()) + using (var output = new MemoryStream()) { - var pipe = memoryStream.AsPipelineWriter(); - - // See https://github.com/dotnet/corefxlab/issues/1460, the TextEncoder is unimportant but required. - var output = new PipelineTextOutput(pipe, TextEncoder.Utf8); - // Encode the message if (!protocol.TryWriteMessage(message, output)) { throw new InvalidOperationException("Failed to write message to the output stream"); } - - await output.FlushAsync(); - - // Create a message - return memoryStream.ToArray(); + + return output.ToArray(); } } } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/IHubProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/IHubProtocol.cs index a8bae3cc05..4ba7423321 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/IHubProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/IHubProtocol.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.IO; namespace Microsoft.AspNetCore.SignalR.Internal.Protocol { @@ -11,6 +12,6 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol { bool TryParseMessages(ReadOnlySpan input, IInvocationBinder binder, out IList messages); - bool TryWriteMessage(HubMessage message, IOutput output); + bool TryWriteMessage(HubMessage message, Stream output); } } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs index 370f219010..82ed1fffeb 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs @@ -47,11 +47,10 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol public bool TryParseMessages(ReadOnlySpan input, IInvocationBinder binder, out IList messages) { - var reader = new BytesReader(input.ToArray()); messages = new List(); var parser = new TextMessageParser(); - while (parser.TryParseMessage(ref reader, out var payload)) + while (parser.TryParseMessage(ref input, out var payload)) { // TODO: Need a span-native JSON parser! using (var memoryStream = new MemoryStream(payload.ToArray())) @@ -63,9 +62,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol return messages.Count > 0; } - public bool TryWriteMessage(HubMessage message, IOutput output) + public bool TryWriteMessage(HubMessage message, Stream output) { - // TODO: Need IOutput-compatible JSON serializer! using (var memoryStream = new MemoryStream()) { WriteMessage(message, memoryStream); diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj b/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj index e783c7c491..2c479940fe 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj +++ b/src/Microsoft.AspNetCore.SignalR.Common/Microsoft.AspNetCore.SignalR.Common.csproj @@ -12,13 +12,10 @@ Microsoft.AspNetCore.SignalR - - - - - + + diff --git a/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs b/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs index dd80dc5afc..13308c2224 100644 --- a/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs +++ b/src/Microsoft.AspNetCore.SignalR.Redis/RedisHubLifetimeManager.cs @@ -5,15 +5,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; -using System.IO.Pipelines; -using System.IO.Pipelines.Text.Primitives; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.Sockets; -using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; @@ -313,7 +310,7 @@ namespace Microsoft.AspNetCore.SignalR.Redis private async Task WriteAsync(ConnectionContext connection, HubMessage hubMessage) { var protocol = connection.Metadata.Get(HubConnectionMetadataNames.HubProtocol); - var data = await protocol.WriteToArrayAsync(hubMessage); + var data = protocol.WriteToArray(hubMessage); while (await connection.Transport.Output.WaitToWriteAsync()) { diff --git a/src/Microsoft.AspNetCore.SignalR/DefaultHubLifetimeManager.cs b/src/Microsoft.AspNetCore.SignalR/DefaultHubLifetimeManager.cs index 15218eb4d6..4d1ee56803 100644 --- a/src/Microsoft.AspNetCore.SignalR/DefaultHubLifetimeManager.cs +++ b/src/Microsoft.AspNetCore.SignalR/DefaultHubLifetimeManager.cs @@ -4,14 +4,11 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Pipelines; -using System.IO.Pipelines.Text.Primitives; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.Sockets; -using Microsoft.AspNetCore.Sockets.Internal.Formatters; namespace Microsoft.AspNetCore.SignalR { @@ -125,7 +122,7 @@ namespace Microsoft.AspNetCore.SignalR private async Task WriteAsync(ConnectionContext connection, HubMessage hubMessage) { var protocol = connection.Metadata.Get(HubConnectionMetadataNames.HubProtocol); - var payload = await protocol.WriteToArrayAsync(hubMessage); + var payload = protocol.WriteToArray(hubMessage); while (await connection.Transport.Output.WaitToWriteAsync()) { diff --git a/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs b/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs index 139920dd05..cd04d3acd7 100644 --- a/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs +++ b/src/Microsoft.AspNetCore.SignalR/HubEndPoint.cs @@ -230,7 +230,7 @@ namespace Microsoft.AspNetCore.SignalR private async Task SendMessageAsync(ConnectionContext connection, IHubProtocol protocol, HubMessage hubMessage) { - var payload = await protocol.WriteToArrayAsync(hubMessage); + var payload = protocol.WriteToArray(hubMessage); while (await connection.Transport.Output.WaitToWriteAsync()) { diff --git a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs index 5a7ed4af4e..e5a5b70d17 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/HttpConnectionDispatcher.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.IO.Pipelines; using System.Text; using System.Threading; using System.Threading.Tasks; diff --git a/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsMessageFormatter.cs b/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsMessageFormatter.cs index 896134e1a5..54b6a6ca87 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsMessageFormatter.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsMessageFormatter.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Binary; using System.Buffers; +using System.IO; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters private const byte LineFeed = (byte)'\n'; - public static bool TryWriteMessage(ReadOnlySpan payload, IOutput output) + public static bool TryWriteMessage(ReadOnlySpan payload, MemoryStream output) { // Write the payload if (!TryWritePayload(payload, output)) @@ -22,15 +22,12 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters return false; } - if (!output.TryWrite(Newline)) - { - return false; - } + output.Write(Newline, 0, Newline.Length); return true; } - private static bool TryWritePayload(ReadOnlySpan payload, IOutput output) + private static bool TryWritePayload(ReadOnlySpan payload, Stream output) { // Short-cut for empty payload if (payload.Length == 0) @@ -88,22 +85,16 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters return true; } - private static bool TryWriteLine(ReadOnlySpan line, IOutput output) + private static bool TryWriteLine(ReadOnlySpan payload, Stream output) { - if (!output.TryWrite(DataPrefix)) - { - return false; - } + output.Write(DataPrefix, 0, DataPrefix.Length); - if (!output.TryWrite(line)) - { - return false; - } + var buffer = ArrayPool.Shared.Rent(payload.Length); + payload.CopyTo(buffer); + output.Write(buffer, 0, payload.Length); + ArrayPool.Shared.Return(buffer); - if (!output.TryWrite(Newline)) - { - return false; - } + output.Write(Newline, 0, Newline.Length); return true; } diff --git a/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsTransport.cs b/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsTransport.cs index a0d9b8fc99..a2007c6dfc 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsTransport.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/Internal/Transports/ServerSentEventsTransport.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO.Pipelines; -using System.IO.Pipelines.Text.Primitives; -using System.Text; +using System.IO; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Channels; @@ -44,17 +42,15 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Transports await context.Response.WriteAsync(":\r\n"); await context.Response.Body.FlushAsync(); - var pipe = context.Response.Body.AsPipelineWriter(); - var output = new PipelineTextOutput(pipe, TextEncoder.Utf8); // We don't need the Encoder, but it's harmless to set. - try { + var ms = new MemoryStream(); while (await _application.WaitToReadAsync(token)) { while (_application.TryRead(out var buffer)) { _logger.SSEWritingMessage(_connectionId, buffer.Length); - if (!ServerSentEventsMessageFormatter.TryWriteMessage(buffer, output)) + if (!ServerSentEventsMessageFormatter.TryWriteMessage(buffer, ms)) { // We ran out of space to write, even after trying to enlarge. // This should only happen in a significant lack-of-memory scenario. @@ -64,11 +60,12 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Transports // Throwing InvalidOperationException here, but it's not quite an invalid operation... throw new InvalidOperationException("Ran out of space to format messages!"); } - - await output.FlushAsync(); } } + ms.Seek(0, SeekOrigin.Begin); + await ms.CopyToAsync(context.Response.Body); + await _application.Completion; } catch (OperationCanceledException) diff --git a/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj b/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj index 971786f6ca..93e9f92f60 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj +++ b/src/Microsoft.AspNetCore.Sockets.Http/Microsoft.AspNetCore.Sockets.Http.csproj @@ -11,10 +11,6 @@ false - - - - @@ -24,7 +20,8 @@ - + + diff --git a/test/Common/ArrayOutput.cs b/test/Common/ArrayOutput.cs deleted file mode 100644 index 463bf20580..0000000000 --- a/test/Common/ArrayOutput.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -namespace Microsoft.AspNetCore.Sockets.Tests.Internal -{ - internal class ArrayOutput : IOutput - { - private IList> _buffers = new List>(); - - private int _chunkSize; - private byte[] _activeBuffer; - private int _offset; - - public Span Buffer => _activeBuffer.Slice(_offset); - - public ArrayOutput(int chunkSize) - { - _chunkSize = chunkSize; - AdvanceChunk(); - } - - public void Advance(int bytes) - { - // Determine the new location - _offset += bytes; - Debug.Assert(_offset <= _activeBuffer.Length, "How did we write more data than we had space?"); - } - - public void Enlarge(int desiredBufferLength = 0) - { - if (desiredBufferLength == 0 || _activeBuffer.Length - _offset < desiredBufferLength) - { - AdvanceChunk(); - } - } - - public byte[] ToArray() - { - var totalLength = _buffers.Sum(b => b.Count) + _offset; - - var arr = new byte[totalLength]; - - int offset = 0; - foreach (var buffer in _buffers) - { - System.Buffer.BlockCopy(buffer.Array, 0, arr, offset, buffer.Count); - offset += buffer.Count; - } - - if (_offset > 0) - { - System.Buffer.BlockCopy(_activeBuffer, 0, arr, offset, _offset); - } - - return arr; - } - - private void AdvanceChunk() - { - if (_activeBuffer != null) - { - _buffers.Add(new ArraySegment(_activeBuffer, 0, _offset)); - } - - _activeBuffer = new byte[_chunkSize]; - _offset = 0; - } - } -} diff --git a/test/Common/ByteArrayExtensions.cs b/test/Common/ByteArrayExtensions.cs deleted file mode 100644 index 9b02403b27..0000000000 --- a/test/Common/ByteArrayExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Buffers; -using System.Collections.Generic; - -namespace System -{ - internal static class ByteArrayExtensions - { - public static ReadOnlyBytes ToChunkedReadOnlyBytes(this byte[] data, int chunkSize) - { - if (chunkSize == 0) - { - return new ReadOnlyBytes(data); - } - - var chunks = new List(); - for (var i = 0; i < data.Length; i += chunkSize) - { - var thisChunkSize = Math.Min(chunkSize, data.Length - i); - var chunk = new byte[thisChunkSize]; - for (var j = 0; j < thisChunkSize; j++) - { - chunk[j] = data[i + j]; - } - chunks.Add(chunk); - } - - chunks.Reverse(); - - ReadOnlyBytes? bytes = null; - foreach (var chunk in chunks) - { - if (bytes == null) - { - bytes = new ReadOnlyBytes(chunk); - } - else - { - bytes = new ReadOnlyBytes(chunk, bytes); - } - } - return bytes.Value; - } - } -} diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs index 7b97e55a18..cf15f6688c 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Internal.Protocol; @@ -191,7 +192,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests throw new InvalidOperationException("No Parsed Message provided"); } - public bool TryWriteMessage(HubMessage message, IOutput output) + public bool TryWriteMessage(HubMessage message, Stream output) { WriteCalls += 1; diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj b/test/Microsoft.AspNetCore.SignalR.Client.Tests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj index c564fcbb8a..71be053e5d 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/Microsoft.AspNetCore.SignalR.Client.Tests.csproj @@ -9,7 +9,6 @@ - diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs index 5e5104ced9..a2ab964f01 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs @@ -2,15 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Channels; -using Microsoft.AspNetCore.Sockets; using Microsoft.AspNetCore.Sockets.Client; using Microsoft.AspNetCore.Sockets.Internal.Formatters; -using Microsoft.AspNetCore.Sockets.Tests.Internal; using Newtonsoft.Json; using Xunit; @@ -88,7 +87,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests private byte[] FormatMessageToArray(byte[] message) { - var output = new ArrayOutput(1024); + var output = new MemoryStream(); Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); return output.ToArray(); } diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs index e26aad5d85..d36d1b16d6 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.Sockets.Internal.Formatters; -using Microsoft.AspNetCore.Sockets.Tests.Internal; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Xunit; @@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol [Theory] [MemberData(nameof(ProtocolTestData))] - public async Task WriteMessage(HubMessage message, bool camelCase, NullValueHandling nullValueHandling, string expectedOutput) + public void WriteMessage(HubMessage message, bool camelCase, NullValueHandling nullValueHandling, string expectedOutput) { expectedOutput = Frame(expectedOutput); @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol }; var protocol = new JsonHubProtocol(jsonSerializer); - var encoded = await protocol.WriteToArrayAsync(message); + var encoded = protocol.WriteToArray(message); var json = Encoding.UTF8.GetString(encoded); Assert.Equal(expectedOutput, json); @@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol private static byte[] FormatMessageToArray(byte[] message) { - var output = new ArrayOutput(1024); + var output = new MemoryStream(); Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); return output.ToArray(); } diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj index c9fe4062b9..7518cdb42c 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Microsoft.AspNetCore.SignalR.Common.Tests.csproj @@ -7,10 +7,6 @@ netcoreapp2.0 - - - - diff --git a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs index 40443b692a..20331dea06 100644 --- a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs +++ b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs @@ -1,8 +1,8 @@ using System; using System.Buffers; +using System.IO; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Sockets.Internal.Formatters; -using Microsoft.AspNetCore.Sockets.Tests.Internal; namespace Microsoft.AspNetCore.SignalR.Microbenchmarks { @@ -12,8 +12,8 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks private static readonly Random Random = new Random(); private readonly TextMessageParser _textMessageParser = new TextMessageParser(); private readonly BinaryMessageParser _binaryMessageParser = new BinaryMessageParser(); - private ReadOnlyBytes _binaryInput; - private ReadOnlyBytes _textInput; + private ReadOnlyBuffer _binaryInput; + private ReadOnlyBuffer _textInput; [Params(32, 64)] public int ChunkSize { get; set; } @@ -26,30 +26,30 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks { var buffer = new byte[MessageLength]; Random.NextBytes(buffer); - var output = new ArrayOutput(MessageLength + 32); + var output = new MemoryStream(); if (!BinaryMessageFormatter.TryWriteMessage(buffer, output)) { throw new InvalidOperationException("Failed to format message"); } - _binaryInput = output.ToArray().ToChunkedReadOnlyBytes(ChunkSize); + _binaryInput = output.ToArray(); buffer = new byte[MessageLength]; Random.NextBytes(buffer); - output = new ArrayOutput(MessageLength + 32); + output = new MemoryStream(); if (!TextMessageFormatter.TryWriteMessage(buffer, output)) { throw new InvalidOperationException("Failed to format message"); } - _textInput = output.ToArray().ToChunkedReadOnlyBytes(ChunkSize); + _textInput = output.ToArray(); } [Benchmark] public void SingleBinaryMessage() { - var reader = new BytesReader(_binaryInput); - if (!_binaryMessageParser.TryParseMessage(ref reader, out _)) + var buffer = _binaryInput.Span; + if (!_binaryMessageParser.TryParseMessage(ref buffer, out _)) { throw new InvalidOperationException("Failed to parse"); } @@ -58,8 +58,8 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks [Benchmark] public void SingleTextMessage() { - var reader = new BytesReader(_textInput); - if (!_textMessageParser.TryParseMessage(ref reader, out _)) + var buffer = _textInput.Span; + if (!_textMessageParser.TryParseMessage(ref buffer, out _)) { throw new InvalidOperationException("Failed to parse"); } diff --git a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj index af3aa9a7b2..f838587df2 100644 --- a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj +++ b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks.csproj @@ -7,11 +7,6 @@ netcoreapp2.0;net461 - - - - - diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs index 595cc5641f..ef47d6e9bf 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.IO; using System.Text; using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Xunit; @@ -28,7 +29,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters Encoding.UTF8.GetBytes("Hello,\r\nWorld!") }; - var output = new ArrayOutput(chunkSize: 8); // Use small chunks to test Advance/Enlarge and partial payload writing + var output = new MemoryStream(); // Use small chunks to test Advance/Enlarge and partial payload writing foreach (var message in messages) { Assert.True(BinaryMessageFormatter.TryWriteMessage(message, output)); @@ -46,11 +47,11 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters [InlineData(0, 256, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })] public void WriteBinaryMessage(int offset, int chunkSize, byte[] encoded, byte[] payload) { - var output = new ArrayOutput(chunkSize); + var output = new MemoryStream(); if (offset > 0) { - output.Advance(offset); + output.Seek(offset, SeekOrigin.Begin); } Assert.True(BinaryMessageFormatter.TryWriteMessage(payload, output)); @@ -66,11 +67,11 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters public void WriteTextMessage(int offset, int chunkSize, byte[] encoded, string payload) { var message = Encoding.UTF8.GetBytes(payload); - var output = new ArrayOutput(chunkSize); + var output = new MemoryStream(); if (offset > 0) { - output.Advance(offset); + output.Seek(offset, SeekOrigin.Begin); } Assert.True(BinaryMessageFormatter.TryWriteMessage(message, output)); diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs index 9ae8d374bd..d1ba3651ad 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs @@ -19,9 +19,9 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters public void ReadMessage(byte[] encoded, string payload) { var parser = new BinaryMessageParser(); - var reader = new BytesReader(encoded); - Assert.True(parser.TryParseMessage(ref reader, out var message)); - Assert.Equal(reader.Index, encoded.Length); + ReadOnlySpan span = encoded.AsSpan(); + Assert.True(parser.TryParseMessage(ref span, out var message)); + Assert.Equal(0, span.Length); Assert.Equal(Encoding.UTF8.GetBytes(payload), message.ToArray()); } @@ -32,18 +32,14 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters public void ReadBinaryMessage(byte[] encoded, byte[] payload) { var parser = new BinaryMessageParser(); - var reader = new BytesReader(encoded); - Assert.True(parser.TryParseMessage(ref reader, out var message)); - Assert.Equal(reader.Index, encoded.Length); + ReadOnlySpan span = encoded.AsSpan(); + Assert.True(parser.TryParseMessage(ref span, out var message)); + Assert.Equal(0, span.Length); Assert.Equal(payload, message.ToArray()); } - [Theory] - [InlineData(0)] // No chunking - [InlineData(4)] - [InlineData(8)] - [InlineData(256)] - public void ReadMultipleMessages(int chunkSize) + [Fact] + public void ReadMultipleMessages() { var encoded = new byte[] { @@ -53,16 +49,15 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters /* body: */ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2C, 0x0D, 0x0A, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, }; var parser = new BinaryMessageParser(); - var buffer = encoded.ToChunkedReadOnlyBytes(chunkSize); - var reader = new BytesReader(buffer); + ReadOnlySpan span = encoded.AsSpan(); var messages = new List(); - while (parser.TryParseMessage(ref reader, out var message)) + while (parser.TryParseMessage(ref span, out var message)) { messages.Add(message.ToArray()); } - Assert.Equal(encoded.Length, reader.Index); + Assert.Equal(0, span.Length); Assert.Equal(2, messages.Count); Assert.Equal(new byte[0], messages[0]); @@ -75,9 +70,9 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters public void ReadIncompleteMessages(byte[] encoded) { var parser = new BinaryMessageParser(); - var reader = new BytesReader(new ReadOnlyBytes(encoded)); - Assert.False(parser.TryParseMessage(ref reader, out var message)); - Assert.Equal(encoded.Length, reader.Index); + ReadOnlySpan span = encoded.AsSpan(); + Assert.False(parser.TryParseMessage(ref span, out var message)); + Assert.Equal(0, span.Length); } } } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs index 10677c2407..e3c78c8940 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; +using System.IO; using System.Text; using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Xunit; @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters Encoding.UTF8.GetBytes("Hello,\r\nWorld!") }; - var output = new ArrayOutput(chunkSize: 8); // Use small chunks to test Advance/Enlarge and partial payload writing + var output = new MemoryStream(); foreach (var message in messages) { Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters public void WriteMessage(int chunkSize, string encoded, string payload) { var message = Encoding.UTF8.GetBytes(payload); - var output = new ArrayOutput(chunkSize); // Use small chunks to test Advance/Enlarge and partial payload writing + var output = new MemoryStream(); Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs index 6397a7ee17..ef2caaa829 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs @@ -21,35 +21,28 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters { var parser = new TextMessageParser(); var buffer = Encoding.UTF8.GetBytes(encoded); - var reader = new BytesReader(buffer.ToChunkedReadOnlyBytes(chunkSize)); + ReadOnlySpan span = buffer.AsSpan(); - Assert.True(parser.TryParseMessage(ref reader, out var message)); - Assert.Equal(reader.Index, buffer.Length); + Assert.True(parser.TryParseMessage(ref span, out var message)); + Assert.Equal(0, span.Length); Assert.Equal(Encoding.UTF8.GetBytes(payload), message.ToArray()); } - [Theory] - [InlineData(0)] // Not chunked - [InlineData(4)] - [InlineData(8)] - public void ReadMultipleMessages(int chunkSize) + [Fact] + public void ReadMultipleMessages() { const string encoded = "0:;14:Hello,\r\nWorld!;"; var parser = new TextMessageParser(); var data = Encoding.UTF8.GetBytes(encoded); - var buffer = chunkSize > 0 ? - data.ToChunkedReadOnlyBytes(chunkSize) : - new ReadOnlyBytes(data); - - var reader = new BytesReader(buffer); + ReadOnlySpan span = data.AsSpan(); var messages = new List(); - while (parser.TryParseMessage(ref reader, out var message)) + while (parser.TryParseMessage(ref span, out var message)) { messages.Add(message.ToArray()); } - Assert.Equal(reader.Index, Encoding.UTF8.GetByteCount(encoded)); + Assert.Equal(0, span.Length); Assert.Equal(2, messages.Count); Assert.Equal(new byte[0], messages[0]); @@ -68,8 +61,8 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters { var parser = new TextMessageParser(); var buffer = Encoding.UTF8.GetBytes(encoded); - var reader = new BytesReader(buffer); - Assert.False(parser.TryParseMessage(ref reader, out _)); + ReadOnlySpan span = buffer.AsSpan(); + Assert.False(parser.TryParseMessage(ref span, out _)); } [Theory] @@ -82,8 +75,11 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters { var parser = new TextMessageParser(); var buffer = Encoding.UTF8.GetBytes(encoded); - var reader = new BytesReader(buffer); - var ex = Assert.Throws(() => parser.TryParseMessage(ref reader, out _)); + var ex = Assert.Throws(() => + { + ReadOnlySpan span = buffer.AsSpan(); + parser.TryParseMessage(ref span, out _); + }); Assert.Equal(expectedMessage, ex.Message); } @@ -96,8 +92,12 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters // We need to include the ':' so that var buffer = new byte[] { 0x48, 0x65, 0x80, 0x6C, 0x6F, (byte)':' }; var reader = new BytesReader(buffer); - var ex = Assert.Throws(() => parser.TryParseMessage(ref reader, out _)); - Assert.Equal("Invalid length", ex.Message); + var ex = Assert.Throws(() => + { + ReadOnlySpan span = buffer.AsSpan(); + parser.TryParseMessage(ref span, out _); + }); + Assert.Equal("Invalid length: 'He�lo'", ex.Message); } } } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj b/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj index 25d5d021c4..852e041f78 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Microsoft.AspNetCore.SignalR.Tests.csproj @@ -16,8 +16,6 @@ - - diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs b/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs index 7e10ba2398..c21c90cdb2 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/TestClient.cs @@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests public async Task SendInvocationAsync(string methodName, params object[] args) { var invocationId = GetInvocationId(); - var payload = await _protocol.WriteToArrayAsync(new InvocationMessage(invocationId, nonBlocking: false, target: methodName, arguments: args)); + var payload = _protocol.WriteToArray(new InvocationMessage(invocationId, nonBlocking: false, target: methodName, arguments: args)); await Application.Output.WriteAsync(payload); diff --git a/test/Microsoft.AspNetCore.Sockets.Tests/Microsoft.AspNetCore.Sockets.Tests.csproj b/test/Microsoft.AspNetCore.Sockets.Tests/Microsoft.AspNetCore.Sockets.Tests.csproj index e17e197247..4e0dcb7d96 100644 --- a/test/Microsoft.AspNetCore.Sockets.Tests/Microsoft.AspNetCore.Sockets.Tests.csproj +++ b/test/Microsoft.AspNetCore.Sockets.Tests/Microsoft.AspNetCore.Sockets.Tests.csproj @@ -8,7 +8,6 @@ - diff --git a/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsMessageFormatterTests.cs b/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsMessageFormatterTests.cs index d5c7d739cc..44c7939720 100644 --- a/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsMessageFormatterTests.cs +++ b/test/Microsoft.AspNetCore.Sockets.Tests/ServerSentEventsMessageFormatterTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.Text; using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Xunit; @@ -19,7 +20,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters [InlineData("data: Hello\r\ndata: \r\n\r\n", "Hello\r\n")] public void WriteTextMessage(string encoded, string payload) { - var output = new ArrayOutput(chunkSize: 8); // Use small chunks to test Advance/Enlarge and partial payload writing + var output = new MemoryStream(); Assert.True(ServerSentEventsMessageFormatter.TryWriteMessage(Encoding.UTF8.GetBytes(payload), output)); Assert.Equal(encoded, Encoding.UTF8.GetString(output.ToArray()));