From 59a8f5f3b58f3f3c7b33898bc3fbe8158a0ddf9b Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 6 Jun 2017 07:18:23 -1000 Subject: [PATCH] Simplify the JSON and protobuf protocols (#524) - Remove message type and message format - Updated tests --- .../Formatters.spec.ts | 24 +++--- .../HubConnection.spec.ts | 4 +- .../Formatters.ts | 41 +++------- .../HubConnection.ts | 17 +++-- .../Message.ts | 14 ---- specs/HubProtocol.md | 33 +++----- .../HubConnection.cs | 8 -- .../Formatters/BinaryMessageFormatter.cs | 31 +------- .../Formatters/BinaryMessageParser.cs | 48 ++---------- .../Internal/Formatters/MessageFormatter.cs | 31 -------- .../Internal/Formatters/MessageParser.cs | 43 ----------- .../Formatters/TextMessageFormatter.cs | 52 ++----------- .../Internal/Formatters/TextMessageParser.cs | 75 ++----------------- .../Internal/Protocol/JsonHubProtocol.cs | 28 +------ .../Message.cs | 28 ------- .../MessageType.cs | 11 --- .../LongPollingTransport.cs | 1 - .../SendUtils.cs | 1 - .../ContentTypes.cs | 22 ------ .../MessageFormat.cs | 11 --- .../Transports/LongPollingTransport.cs | 6 +- .../ConnectionTests.cs | 14 +--- .../HubConnectionProtocolTests.cs | 4 +- .../HubConnectionTests.cs | 2 - .../LongPollingTransportTests.cs | 6 +- .../ResponseUtils.cs | 14 ++-- .../TestConnection.cs | 11 ++- .../Internal/Protocol/JsonHubProtocolTests.cs | 12 ++- ...oft.AspNetCore.SignalR.Common.Tests.csproj | 4 +- .../MessageParserBenchmark.cs | 45 +++++++---- .../Formatters/BinaryMessageFormatterTests.cs | 43 +++++------ .../Formatters/BinaryMessageParserTests.cs | 58 ++++++-------- .../Formatters/MessageTestUtils.cs | 38 ---------- .../Formatters/ServerSentEventsParserTests.cs | 6 +- .../Formatters/TextMessageFormatterTests.cs | 40 +++------- .../Formatters/TextMessageParserTests.cs | 75 +++++++------------ 36 files changed, 215 insertions(+), 686 deletions(-) delete mode 100644 client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Message.ts delete mode 100644 src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/MessageFormatter.cs delete mode 100644 src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/MessageParser.cs delete mode 100644 src/Microsoft.AspNetCore.SignalR.Common/Message.cs delete mode 100644 src/Microsoft.AspNetCore.SignalR.Common/MessageType.cs delete mode 100644 src/Microsoft.AspNetCore.Sockets.Common/ContentTypes.cs delete mode 100644 src/Microsoft.AspNetCore.Sockets.Common/MessageFormat.cs delete mode 100644 test/Microsoft.AspNetCore.SignalR.Tests/Formatters/MessageTestUtils.cs diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/Formatters.spec.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/Formatters.spec.ts index 784fb50256..9c7501461f 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/Formatters.spec.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/Formatters.spec.ts @@ -1,5 +1,4 @@ import { TextMessageFormat } from "../Microsoft.AspNetCore.SignalR.Client.TS/Formatters" -import { Message, MessageType } from "../Microsoft.AspNetCore.SignalR.Client.TS/Message"; describe("Text Message Formatter", () => { it("should return empty array on empty input", () => { @@ -7,9 +6,9 @@ describe("Text Message Formatter", () => { expect(messages).toEqual([]); }); ([ - ["0:T:;", [new Message(MessageType.Text, "")]], - ["5:T:Hello;", [new Message(MessageType.Text, "Hello")]], - ] as [[string, Message[]]]).forEach(([payload, expected_messages]) => { + ["0:;", [""]], + ["5:Hello;", ["Hello"]], + ] as [[string, string[]]]).forEach(([payload, expected_messages]) => { it(`should parse '${payload}' correctly`, () => { let messages = TextMessageFormat.parse(payload); expect(messages).toEqual(expected_messages); @@ -18,16 +17,13 @@ describe("Text Message Formatter", () => { ([ ["ABC", new Error("Invalid length: 'ABC'")], - ["1:X:A;", new Error("Unknown type value: 'X'")], - ["1:T:A;12ab34:", new Error("Invalid length: '12ab34'")], - ["1:T:A;1:asdf:", new Error("Unknown type value: 'asdf'")], - ["1:T:A;1::", new Error("Message is incomplete")], - ["1:T:A;1:AB:", new Error("Message is incomplete")], - ["1:T:A;5:T:A", new Error("Message is incomplete")], - ["1:T:A;5:T:AB", new Error("Message is incomplete")], - ["1:T:A;5:T:ABCDE", new Error("Message is incomplete")], - ["1:T:A;5:X:ABCDE", new Error("Message is incomplete")], - ["1:T:A;5:T:ABCDEF", new Error("Message missing trailer character")], + ["1:A;12ab34:", new Error("Invalid length: '12ab34'")], + ["1:A;1:", new Error("Message is incomplete")], + ["1:A;1:AB:", new Error("Message missing trailer character")], + ["1:A;5:A", new Error("Message is incomplete")], + ["1:A;5:AB", new Error("Message is incomplete")], + ["1:A;5:ABCDE", new Error("Message is incomplete")], + ["1:A;5:ABCDEF", new Error("Message missing trailer character")], ] as [[string, Error]]).forEach(([payload, expected_error]) => { it(`should fail to parse '${payload}'`, () => { expect(() => TextMessageFormat.parse(payload)).toThrow(expected_error); diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/HubConnection.spec.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/HubConnection.spec.ts index 27447b4410..b37b8dfbd5 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/HubConnection.spec.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/HubConnection.spec.ts @@ -216,7 +216,7 @@ class TestConnection implements IConnection { }; send(data: any): Promise { - var invocation = TextMessageFormat.parse(data)[0].content.toString(); + var invocation = TextMessageFormat.parse(data)[0]; this.lastInvocationId = JSON.parse(invocation).invocationId; if (this.sentData) { this.sentData.push(invocation); @@ -235,7 +235,7 @@ class TestConnection implements IConnection { receive(data: any): void { var payload = JSON.stringify(data); - this.onDataReceived(`${payload.length}:T:${payload};`); + this.onDataReceived(TextMessageFormat.write(payload)); } onDataReceived: DataReceived; diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Formatters.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Formatters.ts index 246c188c16..4dd91b5c81 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Formatters.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Formatters.ts @@ -1,10 +1,4 @@ -import { Message, MessageType } from './Message'; - -let knownTypes = { - "T": MessageType.Text, - "B": MessageType.Binary -}; - + function splitAt(input: string, searchString: string, position: number): [string, number] { let index = input.indexOf(searchString, position); if (index < 0) { @@ -23,7 +17,7 @@ export namespace TextMessageFormat { return input.length >= requiredLength; } - function parseMessage(input: string, position: number): [number, Message] { + function parseMessage(input: string, position: number): [number, string] { var offset = position; // Read the length @@ -36,20 +30,11 @@ export namespace TextMessageFormat { } let length = Number.parseInt(lenStr); - // Required space is: 3 (type flag, ":", ";") + length (payload len) - if (!hasSpace(input, offset, 3 + length)) { + // Required space is: (";") + length (payload len) + if (!hasSpace(input, offset, 1 + length)) { throw new Error("Message is incomplete"); } - - // Read the type - var [typeStr, offset] = splitAt(input, ":", offset); - - // Parse the type - var messageType = knownTypes[typeStr]; - if (messageType === undefined) { - throw new Error(`Unknown type value: '${typeStr}'`); - } - + // Read the payload var payload = input.substr(offset, length); offset += length; @@ -60,18 +45,14 @@ export namespace TextMessageFormat { } offset += 1; - if (messageType == MessageType.Binary) { - // We need to decode and put in an ArrayBuffer. Throw for now - // This will require our own Base64-decoder because the browser - // built-in one only decodes to strings and throws if invalid UTF-8 - // characters are found. - throw new Error("TODO: Support for binary messages"); - } - - return [offset, new Message(messageType, payload)]; + return [offset, payload]; } - export function parse(input: string): Message[] { + export function write(output: string): string { + return `${output.length}:${output};`; + } + + export function parse(input: string): string[] { if (input.length == 0) { return [] } diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts index 31f8ad12db..749b5dfadc 100644 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts +++ b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/HubConnection.ts @@ -72,9 +72,9 @@ export class HubConnection { let messages = Formatters.TextMessageFormat.parse(data); for (var i = 0; i < messages.length; ++i) { - console.log(`Received message: ${messages[i].content}`); + console.log(`Received message: ${messages[i]}`); - var message = JSON.parse(messages[i].content.toString()); + var message = JSON.parse(messages[i]); switch (message.type) { case MessageType.Invocation: this.InvokeClientMethod(message); @@ -159,9 +159,8 @@ export class HubConnection { } }); - //TODO: separate conversion to enable different data formats - let data = JSON.stringify(invocationDescriptor); - let message = `${data.length}:T:${data};`; + // TODO: separate conversion to enable different data formats + let message = this.framePayload(invocationDescriptor); this.connection.send(message) .catch(e => { @@ -192,8 +191,7 @@ export class HubConnection { }); // TODO: separate conversion to enable different data formats - let data = JSON.stringify(invocationDescriptor); - let message = `${data.length}:T:${data};`; + let message = this.framePayload(invocationDescriptor); this.connection.send(message) .catch(e => { @@ -213,6 +211,11 @@ export class HubConnection { this.connectionClosedCallback = callback; } + private framePayload(invocationDescriptor: InvocationMessage): string { + let data = JSON.stringify(invocationDescriptor); + return Formatters.TextMessageFormat.write(data); + } + private createInvocation(methodName: string, args: any[]): InvocationMessage { let id = this.id; this.id++; diff --git a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Message.ts b/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Message.ts deleted file mode 100644 index d8fb907f7d..0000000000 --- a/client-ts/Microsoft.AspNetCore.SignalR.Client.TS/Message.ts +++ /dev/null @@ -1,14 +0,0 @@ -export enum MessageType { - Text, - Binary, -} - -export class Message { - public type: MessageType; - public content: ArrayBuffer | string; - - constructor(type: MessageType, content: ArrayBuffer | string) { - this.type = type; - this.content = content; - } -} \ No newline at end of file diff --git a/specs/HubProtocol.md b/specs/HubProtocol.md index 2f3a82f570..06161ff2e5 100644 --- a/specs/HubProtocol.md +++ b/specs/HubProtocol.md @@ -328,31 +328,24 @@ JSON payloads are wrapped in an outer message framing to support batching over v The body will be formatted as below and encoded in UTF-8. Identifiers in square brackets `[]` indicate fields defined below, and parenthesis `()` indicate grouping. ``` -([Length]:[Type]:[Body];)([Length]:[Type]:[Body];)... continues until end of the connection ... +([Length]:[Body];)([Length]:[Body];)... continues until end of the connection ... ``` * `[Length]` - Length of the `[Body]` field in bytes, specified as UTF-8 digits (`0`-`9`, terminated by `:`). If the body is a binary frame, this length indicates the number of Base64-encoded characters, not the number of bytes in the final decoded message! -* `[Type]` - A single-byte UTF-8 character indicating the type of the frame, see the list of frame Types below * `[Body]` - The body of the message, the content of which depends upon the value of `[Type]` -The following values are valid for `[Type]`: - -* `T` - Indicates a text frame, the `[Body]` contains UTF-8 encoded text data. -* `B` - Indicates a binary frame, the `[Body]` contains Base64 encoded binary data. - -Note: If there is no `[Body]` for a frame, there does still need to be a `:` and `;` delimiting the body. So, for example, the following is an encoding of a single text frame `A`: `T1:T:A;` +Note: If there is no `[Body]` for a frame, there does still need to be a `:` and `;` delimiting the body. So, for example, the following is an encoding of a single text frame `A`: `1:A;` For example, when sending the following frames (`\n` indicates the actual Line Feed character, not an escape sequence): -* Type=`Text`, "Hello\nWorld" -* Type=`Binary`, `0x01 0x02` -* Type=`Text`, `<>` +* "Hello\nWorld" +* `<>` The encoding will be as follows ``` -T11:T:Hello -World;4:B:AQI=;0:T:; +11:Hello +World;0:; ``` Note that the final frame still ends with the `;` terminator, and that since the body may contain `;`, newlines, etc., the length is specified in order to know exactly where the body ends. @@ -456,27 +449,21 @@ Protobuf payloads are wrapped in an outer message framing described below. #### Binary encoding ``` -([Length][Type][Body])([Length][Type][Body])... continues until end of the connection ... +([Length][Body])([Length][Body])... continues until end of the connection ... ``` * `[Length]` - A 64-bit integer in Network Byte Order (Big-endian) representing the length of the body in bytes -* `[Type]` - An 8-bit integer indicating the type of the message. - * `0x00` => `Text` - `[Body]` is UTF-8 encoded text data - * `0x01` => `Binary` - `[Body]` is raw binary data - * All other values are reserved and must **not** be used. An endpoint may reject a frame using any other value and terminate the connection. -* `[Body]` - The body of the message, exactly `[Length]` bytes in length. `Text` frames are always encoded in UTF-8. +* `[Body]` - The body of the message, exactly `[Length]` bytes in length. For example, when sending the following frames (`\n` indicates the actual Line Feed character, not an escape sequence): -* Type=`Text`, "Hello\nWorld" -* Type=`Binary`, `0x01 0x02` +* "Hello\nWorld" +* `0x01 0x02` The encoding will be as follows, as a list of binary digits in hex (text in parentheses `()` are comments). Whitespace and newlines are irrelevant and for illustration only. ``` 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x0B (start of frame; 64-bit integer value: 11) -0x00 (Type = Text) 0x68 0x65 0x6C 0x6C 0x6F 0x0A 0x77 0x6F 0x72 0x6C 0x64 (UTF-8 encoding of 'Hello\nWorld') 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 (start of frame; 64-bit integer value: 2) -0x01 (Type = Binary) 0x01 0x02 (body) ``` diff --git a/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs b/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs index 7449f1b1c2..fb3fdbfb6f 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client/HubConnection.cs @@ -2,16 +2,10 @@ // 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.Concurrent; using System.Collections.Generic; -using System.IO; -using System.IO.Pipelines; -using System.IO.Pipelines.Text.Primitives; using System.Linq; using System.Net.Http; -using System.Text; -using System.Text.Formatting; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Channels; @@ -19,7 +13,6 @@ using Microsoft.AspNetCore.SignalR.Internal; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.Sockets; using Microsoft.AspNetCore.Sockets.Client; -using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Newtonsoft.Json; @@ -40,7 +33,6 @@ namespace Microsoft.AspNetCore.SignalR.Client private readonly CancellationTokenSource _connectionActive = new CancellationTokenSource(); private readonly Dictionary _pendingCalls = new Dictionary(); private readonly ConcurrentDictionary _handlers = new ConcurrentDictionary(); - private readonly MessageParser _parser = new MessageParser(); private int _nextId = 0; diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs index a417100ca5..3d8df0ca8c 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageFormatter.cs @@ -6,45 +6,22 @@ using System.Buffers; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { - internal static class BinaryMessageFormatter + public static class BinaryMessageFormatter { - internal const byte TextTypeFlag = 0x00; - internal const byte BinaryTypeFlag = 0x01; - - public static bool TryWriteMessage(Message message, IOutput output) + public static bool TryWriteMessage(ReadOnlySpan payload, IOutput output) { - var typeIndicator = GetTypeIndicator(message.Type); - // Try to write the data - if (!output.TryWriteBigEndian((long)message.Payload.Length)) + if (!output.TryWriteBigEndian((long)payload.Length)) { return false; } - if (!output.TryWriteBigEndian(typeIndicator)) - { - return false; - } - - if (!output.TryWrite(message.Payload)) + if (!output.TryWrite(payload)) { return false; } return true; } - - private static byte GetTypeIndicator(MessageType type) - { - switch (type) - { - case MessageType.Text: - return TextTypeFlag; - case MessageType.Binary: - return BinaryTypeFlag; - default: - throw new FormatException($"Invalid Message Type: {type}"); - } - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs index 25bead8cea..8cc02cfa3e 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/BinaryMessageParser.cs @@ -7,7 +7,7 @@ using System.Buffers; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { - internal class BinaryMessageParser + public class BinaryMessageParser { private ParserState _state; @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters _state = default(ParserState); } - public bool TryParseMessage(ref BytesReader buffer, out Message message) + public bool TryParseMessage(ref BytesReader buffer, out ReadOnlyBuffer payload) { if (_state.Length == null) { @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters if (lengthBuffer == null) { - message = default(Message); + payload = default(ReadOnlyBuffer); return false; } @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters if (length.Length < sizeof(long)) { - message = default(Message); + payload = default(ReadOnlyBuffer); return false; } @@ -45,25 +45,6 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters _state.Length = (int)longLength; } - if (_state.MessageType == null) - { - if (buffer.Unread.Length == 0) - { - message = default(Message); - return false; - } - - var typeByte = buffer.Unread[0]; - - if (!TryParseType(typeByte, out var messageType)) - { - throw new FormatException($"Unknown type value: 0x{typeByte:X}"); - } - - buffer.Advance(1); - _state.MessageType = messageType; - } - if (_state.Payload == null) { _state.Payload = new byte[_state.Length.Value]; @@ -80,36 +61,19 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters if (_state.Read == _state.Payload.Length) { - message = new Message(_state.Payload, _state.MessageType.Value); + payload = _state.Payload; Reset(); return true; } // There's still more to read. - message = default(Message); + payload = default(ReadOnlyBuffer); return false; } - private static bool TryParseType(byte type, out MessageType messageType) - { - switch (type) - { - case BinaryMessageFormatter.TextTypeFlag: - messageType = MessageType.Text; - return true; - case BinaryMessageFormatter.BinaryTypeFlag: - messageType = MessageType.Binary; - return true; - default: - messageType = default(MessageType); - return false; - } - } - private struct ParserState { public int? Length; - public MessageType? MessageType; public byte[] Payload; public int Read; } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/MessageFormatter.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/MessageFormatter.cs deleted file mode 100644 index 2703f88d6e..0000000000 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/MessageFormatter.cs +++ /dev/null @@ -1,31 +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; - -namespace Microsoft.AspNetCore.Sockets.Internal.Formatters -{ - public class MessageFormatter - { - public static readonly char TextFormatIndicator = 'T'; - public static readonly char BinaryFormatIndicator = 'B'; - - public static bool TryWriteMessage(Message message, IOutput output, MessageFormat format) - { - return format == MessageFormat.Text ? - TextMessageFormatter.TryWriteMessage(message, output) : - BinaryMessageFormatter.TryWriteMessage(message, output); - } - - public static char GetFormatIndicator(MessageFormat messageFormat) - { - switch (messageFormat) - { - case MessageFormat.Text: return TextFormatIndicator; - case MessageFormat.Binary: return BinaryFormatIndicator; - default: throw new ArgumentException($"Invalid message format: {messageFormat}", nameof(messageFormat)); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/MessageParser.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/MessageParser.cs deleted file mode 100644 index 01777b8ad4..0000000000 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/MessageParser.cs +++ /dev/null @@ -1,43 +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; - -namespace Microsoft.AspNetCore.Sockets.Internal.Formatters -{ - public class MessageParser - { - private TextMessageParser _textParser = new TextMessageParser(); - private BinaryMessageParser _binaryParser = new BinaryMessageParser(); - - public void Reset() - { - _textParser.Reset(); - _binaryParser.Reset(); - } - - public bool TryParseMessage(ref BytesReader buffer, MessageFormat format, out Message message) - { - return format == MessageFormat.Text ? - _textParser.TryParseMessage(ref buffer, out message) : - _binaryParser.TryParseMessage(ref buffer, out message); - } - - public static MessageFormat GetFormatFromIndicator(byte formatIndicator) - { - // Can't use switch because our "constants" are not consts, they're "static readonly" (which is good, because they are public) - if (formatIndicator == MessageFormatter.TextFormatIndicator) - { - return MessageFormat.Text; - } - - if (formatIndicator == MessageFormatter.BinaryFormatIndicator) - { - return MessageFormat.Binary; - } - - throw new ArgumentException($"Invalid message format: 0x{formatIndicator:X}", nameof(formatIndicator)); - } - } -} diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs index 6d18d25ef1..ed4a67580a 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageFormatter.cs @@ -3,31 +3,21 @@ using System; using System.Binary; -using System.Binary.Base64; using System.Buffers; using System.Text; using System.Text.Formatting; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { - internal static class TextMessageFormatter + public static class TextMessageFormatter { internal const char FieldDelimiter = ':'; internal const char MessageDelimiter = ';'; - internal const char TextTypeFlag = 'T'; - internal const char BinaryTypeFlag = 'B'; - - public static bool TryWriteMessage(Message message, IOutput output) + + public static bool TryWriteMessage(ReadOnlySpan payload, IOutput output) { // Calculate the length, it's the number of characters for text messages, but number of base64 characters for binary - var length = message.Payload.Length; - if (message.Type == MessageType.Binary) - { - length = Base64Encoder.ComputeEncodedLength(length); - } - - // Get the type indicator - var typeIndicator = GetTypeIndicator(message.Type); + var length = payload.Length; // Write the length as a string output.Append(length, TextEncoder.Utf8); @@ -35,14 +25,8 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters // Write the field delimiter ':' output.Append(FieldDelimiter, TextEncoder.Utf8); - // Write the type - output.Append(typeIndicator, TextEncoder.Utf8); - - // Write the field delimiter ':' - output.Append(FieldDelimiter, TextEncoder.Utf8); - // Write the payload - if (!TryWritePayload(message, output, length)) + if (!output.TryWrite(payload)) { return false; } @@ -51,31 +35,5 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters output.Append(MessageDelimiter, TextEncoder.Utf8); return true; } - - private static bool TryWritePayload(Message message, IOutput output, int length) - { - // Payload - if (message.Type == MessageType.Binary) - { - // TODO: Base64 writer that works with IOutput would be amazing! - var arr = new byte[Base64Encoder.ComputeEncodedLength(message.Payload.Length)]; - Base64.Encoder.Transform(message.Payload, arr, out _, out _); - return output.TryWrite(arr); - } - else - { - return output.TryWrite(message.Payload); - } - } - - private static char GetTypeIndicator(MessageType type) - { - switch (type) - { - case MessageType.Text: return TextTypeFlag; - case MessageType.Binary: return BinaryTypeFlag; - default: throw new FormatException($"Invalid message type: {type}"); - } - } } } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs index 8cf4b0430c..6e7357dce2 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Formatters/TextMessageParser.cs @@ -2,13 +2,12 @@ // 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.Text; namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { - internal class TextMessageParser + public class TextMessageParser { private ParserState _state; @@ -21,7 +20,7 @@ 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 Message message) + public bool TryParseMessage(ref BytesReader buffer, out ReadOnlyBuffer payload) { while (buffer.Unread.Length > 0) { @@ -30,34 +29,17 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters case ParsePhase.ReadingLength: if (!TryReadLength(ref buffer)) { - message = default(Message); + payload = default(ReadOnlyBuffer); return false; } break; case ParsePhase.LengthComplete: - if (!TryReadDelimiter(ref buffer, TextMessageFormatter.FieldDelimiter, ParsePhase.ReadingType, "length")) + if (!TryReadDelimiter(ref buffer, TextMessageFormatter.FieldDelimiter, ParsePhase.ReadingPayload, "length")) { - message = default(Message); + payload = default(ReadOnlyBuffer); return false; } - - break; - case ParsePhase.ReadingType: - if (!TryReadType(ref buffer)) - { - message = default(Message); - return false; - } - - break; - case ParsePhase.TypeComplete: - if (!TryReadDelimiter(ref buffer, TextMessageFormatter.FieldDelimiter, ParsePhase.ReadingPayload, "type")) - { - message = default(Message); - return false; - } - break; case ParsePhase.ReadingPayload: ReadPayload(ref buffer); @@ -66,12 +48,12 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters case ParsePhase.PayloadComplete: if (!TryReadDelimiter(ref buffer, TextMessageFormatter.MessageDelimiter, ParsePhase.ReadingPayload, "payload")) { - message = default(Message); + payload = default(ReadOnlyBuffer); return false; } // We're done! - message = new Message(_state.Payload, _state.MessageType); + payload = _state.Payload; Reset(); return true; default: @@ -79,7 +61,7 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters } } - message = default(Message); + payload = default(ReadOnlyBuffer); return false; } @@ -129,23 +111,6 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters return true; } - private bool TryReadType(ref BytesReader buffer) - { - if (buffer.Unread.Length == 0) - { - return false; - } - - if (!TryParseType(buffer.Unread[0], out _state.MessageType)) - { - throw new FormatException($"Unknown message type: '{(char)buffer.Unread[0]}'"); - } - - buffer.Advance(1); - _state.Phase = ParsePhase.TypeComplete; - return true; - } - private void ReadPayload(ref BytesReader buffer) { if (_state.Payload == null) @@ -155,11 +120,6 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters if (_state.Read == _state.Length) { - if (_state.MessageType == MessageType.Binary) - { - _state.Payload = MessageFormatUtils.DecodePayload(_state.Payload); - } - _state.Phase = ParsePhase.PayloadComplete; } else @@ -172,27 +132,10 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters } } - private static bool TryParseType(byte type, out MessageType messageType) - { - switch ((char)type) - { - case TextMessageFormatter.TextTypeFlag: - messageType = MessageType.Text; - return true; - case TextMessageFormatter.BinaryTypeFlag: - messageType = MessageType.Binary; - return true; - default: - messageType = default(MessageType); - return false; - } - } - private struct ParserState { public ParsePhase Phase; public int Length; - public MessageType MessageType; public byte[] Payload; public int Read; } @@ -201,8 +144,6 @@ namespace Microsoft.AspNetCore.Sockets.Internal.Formatters { ReadingLength = 0, LengthComplete, - ReadingType, - TypeComplete, ReadingPayload, PayloadComplete } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs index 2f46e0d135..370f219010 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Internal/Protocol/JsonHubProtocol.cs @@ -5,7 +5,6 @@ using System; using System.Buffers; using System.Collections.Generic; using System.IO; -using Microsoft.AspNetCore.Sockets; using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -51,11 +50,11 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol var reader = new BytesReader(input.ToArray()); messages = new List(); - // This API has to change to return the amount consumed - foreach (var m in ParseSendBatch(ref reader, MessageFormat.Text)) + var parser = new TextMessageParser(); + while (parser.TryParseMessage(ref reader, out var payload)) { // TODO: Need a span-native JSON parser! - using (var memoryStream = new MemoryStream(m.Payload)) + using (var memoryStream = new MemoryStream(payload.ToArray())) { messages.Add(ParseMessage(memoryStream, binder)); } @@ -72,8 +71,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol WriteMessage(message, memoryStream); memoryStream.Flush(); - var frame = new Message(memoryStream.ToArray(), MessageType.Text); - return MessageFormatter.TryWriteMessage(frame, output, MessageFormat.Text); + return TextMessageFormatter.TryWriteMessage(memoryStream.ToArray(), output); } } @@ -288,23 +286,5 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol } return prop.Value(); } - - private List ParseSendBatch(ref BytesReader payload, MessageFormat messageFormat) - { - var messages = new List(); - - if (payload.Unread.Length == 0) - { - return messages; - } - - // REVIEW: This needs a little work. We could probably new up exactly the right parser, if we tinkered with the inheritance hierarchy a bit. - var parser = new MessageParser(); - while (parser.TryParseMessage(ref payload, messageFormat, out var message)) - { - messages.Add(message); - } - return messages; - } } } diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Message.cs b/src/Microsoft.AspNetCore.SignalR.Common/Message.cs deleted file mode 100644 index 64db6f454a..0000000000 --- a/src/Microsoft.AspNetCore.SignalR.Common/Message.cs +++ /dev/null @@ -1,28 +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; - -namespace Microsoft.AspNetCore.Sockets -{ - public struct Message - { - public MessageType Type { get; } - - // REVIEW: We need a better primitive to use here. Memory would be good, - // but @davidfowl has concerns about allocating OwnedMemory and how to dispose - // it properly - public byte[] Payload { get; } - - public Message(byte[] payload, MessageType type) - { - if (payload == null) - { - throw new ArgumentNullException(nameof(payload)); - } - - Type = type; - Payload = payload; - } - } -} diff --git a/src/Microsoft.AspNetCore.SignalR.Common/MessageType.cs b/src/Microsoft.AspNetCore.SignalR.Common/MessageType.cs deleted file mode 100644 index 1c1f5301ca..0000000000 --- a/src/Microsoft.AspNetCore.SignalR.Common/MessageType.cs +++ /dev/null @@ -1,11 +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 Microsoft.AspNetCore.Sockets -{ - public enum MessageType - { - Text, - Binary - } -} diff --git a/src/Microsoft.AspNetCore.Sockets.Client/LongPollingTransport.cs b/src/Microsoft.AspNetCore.Sockets.Client/LongPollingTransport.cs index 77b6eb30d1..a9c6ded97e 100644 --- a/src/Microsoft.AspNetCore.Sockets.Client/LongPollingTransport.cs +++ b/src/Microsoft.AspNetCore.Sockets.Client/LongPollingTransport.cs @@ -83,7 +83,6 @@ namespace Microsoft.AspNetCore.Sockets.Client { var request = new HttpRequestMessage(HttpMethod.Get, pollUrl); request.Headers.UserAgent.Add(SendUtils.DefaultUserAgentHeader); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ContentTypes.BinaryContentType)); var response = await _httpClient.SendAsync(request, cancellationToken); response.EnsureSuccessStatusCode(); diff --git a/src/Microsoft.AspNetCore.Sockets.Client/SendUtils.cs b/src/Microsoft.AspNetCore.Sockets.Client/SendUtils.cs index ca1d6d0ae7..98aa50d539 100644 --- a/src/Microsoft.AspNetCore.Sockets.Client/SendUtils.cs +++ b/src/Microsoft.AspNetCore.Sockets.Client/SendUtils.cs @@ -56,7 +56,6 @@ namespace Microsoft.AspNetCore.Sockets.Client // Set the, now filled, stream as the content request.Content = new StreamContent(memoryStream); - request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(ContentTypes.GetContentType(MessageFormat.Binary)); var response = await httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); diff --git a/src/Microsoft.AspNetCore.Sockets.Common/ContentTypes.cs b/src/Microsoft.AspNetCore.Sockets.Common/ContentTypes.cs deleted file mode 100644 index 36b36e875a..0000000000 --- a/src/Microsoft.AspNetCore.Sockets.Common/ContentTypes.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.AspNetCore.Sockets -{ - public static class ContentTypes - { - public static readonly string TextContentType = "application/vnd.microsoft.aspnetcore.endpoint-messages.v1+text"; - public static readonly string BinaryContentType = "application/vnd.microsoft.aspnetcore.endpoint-messages.v1+binary"; - - public static string GetContentType(MessageFormat messageFormat) - { - switch (messageFormat) - { - case MessageFormat.Text: return TextContentType; - case MessageFormat.Binary: return BinaryContentType; - default: throw new ArgumentException($"Invalid message format: {messageFormat}", nameof(messageFormat)); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Sockets.Common/MessageFormat.cs b/src/Microsoft.AspNetCore.Sockets.Common/MessageFormat.cs deleted file mode 100644 index 9840e8f108..0000000000 --- a/src/Microsoft.AspNetCore.Sockets.Common/MessageFormat.cs +++ /dev/null @@ -1,11 +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 Microsoft.AspNetCore.Sockets -{ - public enum MessageFormat - { - Text, - Binary - } -} diff --git a/src/Microsoft.AspNetCore.Sockets.Http/Transports/LongPollingTransport.cs b/src/Microsoft.AspNetCore.Sockets.Http/Transports/LongPollingTransport.cs index 1aff3a2fbf..398f864a0c 100644 --- a/src/Microsoft.AspNetCore.Sockets.Http/Transports/LongPollingTransport.cs +++ b/src/Microsoft.AspNetCore.Sockets.Http/Transports/LongPollingTransport.cs @@ -35,11 +35,7 @@ namespace Microsoft.AspNetCore.Sockets.Transports return; } - var headers = context.Request.GetTypedHeaders(); - var messageFormat = headers.Accept?.Contains(new Net.Http.Headers.MediaTypeHeaderValue(ContentTypes.BinaryContentType)) == true ? - MessageFormat.Binary : - MessageFormat.Text; - context.Response.ContentType = ContentTypes.GetContentType(messageFormat); + // REVIEW: What should the content type be? var contentLength = 0; var buffers = new List(); diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/ConnectionTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/ConnectionTests.cs index b481c4c737..0577fb320e 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/ConnectionTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/ConnectionTests.cs @@ -2,17 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; using System.Net; using System.Net.Http; using System.Text; -using System.Text.Formatting; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Client.Tests; using Microsoft.AspNetCore.SignalR.Tests.Common; -using Microsoft.AspNetCore.Sockets.Internal.Formatters; -using Microsoft.AspNetCore.Sockets.Tests.Internal; using Microsoft.Extensions.Logging; using Moq; using Moq.Protected; @@ -589,7 +585,7 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests { content = "42"; } - return ResponseUtils.CreateResponse(HttpStatusCode.OK, ContentTypes.TextContentType, content); + return ResponseUtils.CreateResponse(HttpStatusCode.OK, content); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) @@ -662,13 +658,5 @@ namespace Microsoft.AspNetCore.Sockets.Client.Tests } } } - - private byte[] FormatMessageToArray(Message message, MessageFormat binary, int bufferSize = 1024) - { - var output = new ArrayOutput(bufferSize); - output.Append('B', TextEncoder.Utf8); - Assert.True(MessageFormatter.TryWriteMessage(message, output, binary)); - return output.ToArray(); - } } } diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionProtocolTests.cs index a34bff402f..ea0d1d0ecd 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionProtocolTests.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests var invokeMessage = await connection.ReadSentTextMessageAsync().OrTimeout(); - Assert.Equal("59:T:{\"invocationId\":\"1\",\"type\":1,\"target\":\"Foo\",\"arguments\":[]};", invokeMessage); + Assert.Equal("59:{\"invocationId\":\"1\",\"type\":1,\"target\":\"Foo\",\"arguments\":[]};", invokeMessage); } finally { @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests var invokeMessage = await connection.ReadSentTextMessageAsync().OrTimeout(); - Assert.Equal("59:T:{\"invocationId\":\"1\",\"type\":1,\"target\":\"Foo\",\"arguments\":[]};", invokeMessage); + Assert.Equal("59:{\"invocationId\":\"1\",\"type\":1,\"target\":\"Foo\",\"arguments\":[]};", invokeMessage); // Complete the channel await connection.ReceiveJsonMessage(new { invocationId = "1", type = 3 }).OrTimeout(); diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs index dcc246b5a5..02889ebf03 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs @@ -268,8 +268,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests public int ParseCalls { get; private set; } = 0; public int WriteCalls { get; private set; } = 0; - public MessageType MessageType => MessageType.Text; - public static MockHubProtocol ReturnOnParse(HubMessage parsed) { return new MockHubProtocol diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/LongPollingTransportTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/LongPollingTransportTests.cs index f90bb72675..fa289afce2 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/LongPollingTransportTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/LongPollingTransportTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; using System.Net.Http; using System.Text; @@ -11,10 +10,8 @@ using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Channels; using Microsoft.AspNetCore.SignalR.Tests.Common; -using Microsoft.AspNetCore.Sockets; using Microsoft.AspNetCore.Sockets.Client; using Microsoft.AspNetCore.Sockets.Internal; -using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Microsoft.Extensions.Logging; using Moq; using Moq.Protected; @@ -227,7 +224,7 @@ namespace Microsoft.AspNetCore.Client.Tests if (firstCall) { firstCall = false; - return ResponseUtils.CreateResponse(HttpStatusCode.OK, ContentTypes.BinaryContentType, message1Payload); + return ResponseUtils.CreateResponse(HttpStatusCode.OK, message1Payload); } return ResponseUtils.CreateResponse(HttpStatusCode.NoContent); @@ -260,7 +257,6 @@ namespace Microsoft.AspNetCore.Client.Tests // Check the provided request Assert.Equal(2, sentRequests.Count); - Assert.Contains(ContentTypes.BinaryContentType, sentRequests[0].Headers.Accept.FirstOrDefault()?.ToString()); // Check the messages received Assert.Equal(1, messages.Count); diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/ResponseUtils.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/ResponseUtils.cs index 12f47c0072..06b3f1f87c 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/ResponseUtils.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/ResponseUtils.cs @@ -4,24 +4,22 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; -using Microsoft.AspNetCore.Sockets; namespace Microsoft.AspNetCore.Client.Tests { internal static class ResponseUtils { public static HttpResponseMessage CreateResponse(HttpStatusCode statusCode) => - CreateResponse(statusCode, ContentTypes.TextContentType, string.Empty); + CreateResponse(statusCode, string.Empty); - public static HttpResponseMessage CreateResponse(HttpStatusCode statusCode, string contentType, string payload) => - CreateResponse(statusCode, contentType, new StringContent(payload)); + public static HttpResponseMessage CreateResponse(HttpStatusCode statusCode, string payload) => + CreateResponse(statusCode, new StringContent(payload)); - public static HttpResponseMessage CreateResponse(HttpStatusCode statusCode, string contentType, byte[] payload) => - CreateResponse(statusCode, contentType, new ByteArrayContent(payload)); + public static HttpResponseMessage CreateResponse(HttpStatusCode statusCode, byte[] payload) => + CreateResponse(statusCode, new ByteArrayContent(payload)); - public static HttpResponseMessage CreateResponse(HttpStatusCode statusCode, string contentType, HttpContent payload) + public static HttpResponseMessage CreateResponse(HttpStatusCode statusCode, HttpContent payload) { - payload.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); return new HttpResponseMessage(statusCode) { Content = payload diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs index d018fbae22..abdf20b629 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs @@ -10,7 +10,9 @@ 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; namespace Microsoft.AspNetCore.SignalR.Client.Tests { @@ -79,11 +81,18 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests public Task ReceiveJsonMessage(object jsonObject) { var json = JsonConvert.SerializeObject(jsonObject, Formatting.None); - var bytes = Encoding.UTF8.GetBytes($"{json.Length}:T:{json};"); + var bytes = FormatMessageToArray(Encoding.UTF8.GetBytes(json)); return _receivedMessages.Out.WriteAsync(bytes); } + private byte[] FormatMessageToArray(byte[] message, int bufferSize = 1024) + { + var output = new ArrayOutput(1024); + Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); + return output.ToArray(); + } + private async Task ReceiveLoopAsync(CancellationToken token) { try 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 1216e7cf72..05c9d9faf2 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs @@ -5,6 +5,8 @@ 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; @@ -134,10 +136,16 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol private static string Frame(string input) { - input = $"{input.Length}:T:{input};"; - return input; + var data = Encoding.UTF8.GetBytes(input); + return Encoding.UTF8.GetString(FormatMessageToArray(data)); } + private static byte[] FormatMessageToArray(byte[] message, int bufferSize = 1024) + { + var output = new ArrayOutput(1024); + Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); + return output.ToArray(); + } private class CustomObject : IEquatable { 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 f103c2d4d0..c9fe4062b9 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 @@ -8,9 +8,7 @@ - - - + diff --git a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs index 443bd3e0f4..40443b692a 100644 --- a/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs +++ b/test/Microsoft.AspNetCore.SignalR.Microbenchmarks/MessageParserBenchmark.cs @@ -1,7 +1,6 @@ using System; using System.Buffers; using BenchmarkDotNet.Attributes; -using Microsoft.AspNetCore.Sockets; using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Microsoft.AspNetCore.Sockets.Tests.Internal; @@ -11,9 +10,10 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks public class MessageParserBenchmark { private static readonly Random Random = new Random(); - private readonly MessageParser _parser = new MessageParser(); - private ReadOnlyBytes _input; - private byte[] _buffer; + private readonly TextMessageParser _textMessageParser = new TextMessageParser(); + private readonly BinaryMessageParser _binaryMessageParser = new BinaryMessageParser(); + private ReadOnlyBytes _binaryInput; + private ReadOnlyBytes _textInput; [Params(32, 64)] public int ChunkSize { get; set; } @@ -21,28 +21,45 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks [Params(64, 128)] public int MessageLength { get; set; } - [Params(MessageFormat.Text, MessageFormat.Binary)] - public MessageFormat Format { get; set; } - [Setup] public void Setup() { - _buffer = new byte[MessageLength]; - Random.NextBytes(_buffer); - var message = new Message(_buffer, MessageType.Binary); + var buffer = new byte[MessageLength]; + Random.NextBytes(buffer); var output = new ArrayOutput(MessageLength + 32); - if (!MessageFormatter.TryWriteMessage(message, output, Format)) + if (!BinaryMessageFormatter.TryWriteMessage(buffer, output)) { throw new InvalidOperationException("Failed to format message"); } - _input = output.ToArray().ToChunkedReadOnlyBytes(ChunkSize); + + _binaryInput = output.ToArray().ToChunkedReadOnlyBytes(ChunkSize); + + buffer = new byte[MessageLength]; + Random.NextBytes(buffer); + output = new ArrayOutput(MessageLength + 32); + if (!TextMessageFormatter.TryWriteMessage(buffer, output)) + { + throw new InvalidOperationException("Failed to format message"); + } + + _textInput = output.ToArray().ToChunkedReadOnlyBytes(ChunkSize); } [Benchmark] public void SingleBinaryMessage() { - var reader = new BytesReader(_input); - if (!_parser.TryParseMessage(ref reader, Format, out _)) + var reader = new BytesReader(_binaryInput); + if (!_binaryMessageParser.TryParseMessage(ref reader, out _)) + { + throw new InvalidOperationException("Failed to parse"); + } + } + + [Benchmark] + public void SingleTextMessage() + { + var reader = new BytesReader(_textInput); + if (!_textMessageParser.TryParseMessage(ref reader, out _)) { throw new InvalidOperationException("Failed to parse"); } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs index e6cc1305f9..595cc5641f 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageFormatterTests.cs @@ -3,7 +3,7 @@ using System; using System.Buffers; -using System.Collections.Generic; +using System.Text; using Microsoft.AspNetCore.Sockets.Internal.Formatters; using Xunit; @@ -17,38 +17,35 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters var expectedEncoding = new byte[] { /* length: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* type: */ 0x01, // Binary /* body: */ /* length: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, - /* type: */ 0x00, // Text /* body: */ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2C, 0x0D, 0x0A, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, }; var messages = new[] { - MessageTestUtils.CreateMessage(new byte[0]), - MessageTestUtils.CreateMessage("Hello,\r\nWorld!",MessageType.Text) + new byte[0], + Encoding.UTF8.GetBytes("Hello,\r\nWorld!") }; var output = new ArrayOutput(chunkSize: 8); // Use small chunks to test Advance/Enlarge and partial payload writing foreach (var message in messages) { - Assert.True(MessageFormatter.TryWriteMessage(message, output, MessageFormat.Binary)); + Assert.True(BinaryMessageFormatter.TryWriteMessage(message, output)); } Assert.Equal(expectedEncoding, output.ToArray()); } [Theory] - [InlineData(0, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }, new byte[0])] - [InlineData(0, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })] - [InlineData(4, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }, new byte[0])] - [InlineData(4, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })] - [InlineData(0, 256, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }, new byte[0])] - [InlineData(0, 256, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })] + [InlineData(0, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new byte[0])] + [InlineData(0, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })] + [InlineData(4, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new byte[0])] + [InlineData(4, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })] + [InlineData(0, 256, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new byte[0])] + [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 message = MessageTestUtils.CreateMessage(payload); var output = new ArrayOutput(chunkSize); if (offset > 0) @@ -56,20 +53,19 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters output.Advance(offset); } - Assert.True(MessageFormatter.TryWriteMessage(message, output, MessageFormat.Binary)); - + Assert.True(BinaryMessageFormatter.TryWriteMessage(payload, output)); Assert.Equal(encoded, output.ToArray().Slice(offset).ToArray()); } [Theory] - [InlineData(0, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, MessageType.Text, "")] - [InlineData(0, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x41, 0x42, 0x43 }, MessageType.Text, "ABC")] - [InlineData(0, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x41, 0x0A, 0x52, 0x0D, 0x43, 0x0D, 0x0A, 0x3B, 0x44, 0x45, 0x46 }, MessageType.Text, "A\nR\rC\r\n;DEF")] - [InlineData(4, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, MessageType.Text, "")] - [InlineData(0, 256, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, MessageType.Text, "")] - public void WriteTextMessage(int offset, int chunkSize, byte[] encoded, MessageType messageType, string payload) + [InlineData(0, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "")] + [InlineData(0, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x42, 0x43 }, "ABC")] + [InlineData(0, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x41, 0x0A, 0x52, 0x0D, 0x43, 0x0D, 0x0A, 0x3B, 0x44, 0x45, 0x46 }, "A\nR\rC\r\n;DEF")] + [InlineData(4, 8, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "")] + [InlineData(0, 256, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "")] + public void WriteTextMessage(int offset, int chunkSize, byte[] encoded, string payload) { - var message = MessageTestUtils.CreateMessage(payload, messageType); + var message = Encoding.UTF8.GetBytes(payload); var output = new ArrayOutput(chunkSize); if (offset > 0) @@ -77,8 +73,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters output.Advance(offset); } - Assert.True(MessageFormatter.TryWriteMessage(message, output, MessageFormat.Binary)); - + Assert.True(BinaryMessageFormatter.TryWriteMessage(message, output)); Assert.Equal(encoded, output.ToArray().Slice(offset).ToArray()); } } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs index 3621239bef..9ae8d374bd 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/BinaryMessageParserTests.cs @@ -4,8 +4,8 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Text; using Microsoft.AspNetCore.Sockets.Internal.Formatters; -using Microsoft.AspNetCore.Sockets.Tests; using Xunit; namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters @@ -13,30 +13,29 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters public class BinaryMessageParserTests { [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, MessageType.Text, "")] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x41, 0x42, 0x43 }, MessageType.Text, "ABC")] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x41, 0x0A, 0x52, 0x0D, 0x43, 0x0D, 0x0A, 0x3B, 0x44, 0x45, 0x46 }, MessageType.Text, "A\nR\rC\r\n;DEF")] - public void ReadTextMessage(byte[] encoded, MessageType messageType, string payload) + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "")] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x42, 0x43 }, "ABC")] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x41, 0x0A, 0x52, 0x0D, 0x43, 0x0D, 0x0A, 0x3B, 0x44, 0x45, 0x46 }, "A\nR\rC\r\n;DEF")] + public void ReadMessage(byte[] encoded, string payload) { - var parser = new MessageParser(); + var parser = new BinaryMessageParser(); var reader = new BytesReader(encoded); - Assert.True(parser.TryParseMessage(ref reader, MessageFormat.Binary, out var message)); + Assert.True(parser.TryParseMessage(ref reader, out var message)); Assert.Equal(reader.Index, encoded.Length); - MessageTestUtils.AssertMessage(message, messageType, payload); + Assert.Equal(Encoding.UTF8.GetBytes(payload), message.ToArray()); } [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }, new byte[0])] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new byte[0])] + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })] public void ReadBinaryMessage(byte[] encoded, byte[] payload) { - var parser = new MessageParser(); + var parser = new BinaryMessageParser(); var reader = new BytesReader(encoded); - Assert.True(parser.TryParseMessage(ref reader, MessageFormat.Binary, out var message)); + Assert.True(parser.TryParseMessage(ref reader, out var message)); Assert.Equal(reader.Index, encoded.Length); - - MessageTestUtils.AssertMessage(message, MessageType.Binary, payload); + Assert.Equal(payload, message.ToArray()); } [Theory] @@ -49,48 +48,35 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters var encoded = new byte[] { /* length: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* type: */ 0x01, // Binary /* body: */ /* length: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, - /* type: */ 0x00, // Text /* body: */ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2C, 0x0D, 0x0A, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, }; - var parser = new MessageParser(); + var parser = new BinaryMessageParser(); var buffer = encoded.ToChunkedReadOnlyBytes(chunkSize); var reader = new BytesReader(buffer); - var messages = new List(); - while (parser.TryParseMessage(ref reader, MessageFormat.Binary, out var message)) + var messages = new List(); + while (parser.TryParseMessage(ref reader, out var message)) { - messages.Add(message); + messages.Add(message.ToArray()); } Assert.Equal(encoded.Length, reader.Index); Assert.Equal(2, messages.Count); - MessageTestUtils.AssertMessage(messages[0], MessageType.Binary, new byte[0]); - MessageTestUtils.AssertMessage(messages[1], MessageType.Text, "Hello,\r\nWorld!"); - } - - [Theory] - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04 }, "Unknown type value: 0x4")] // Invalid Type - public void ReadInvalidMessages(byte[] encoded, string message) - { - var parser = new MessageParser(); - var reader = new BytesReader(new ReadOnlyBytes(encoded)); - var ex = Assert.Throws(() => parser.TryParseMessage(ref reader, MessageFormat.Binary, out _)); - Assert.Equal(message, ex.Message); + Assert.Equal(new byte[0], messages[0]); + Assert.Equal(Encoding.UTF8.GetBytes("Hello,\r\nWorld!"), messages[1]); } [Theory] [InlineData(new byte[0])] // Empty - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 })] // Just length - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00 })] // Not enough data for payload + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00 })] // Not enough data for payload public void ReadIncompleteMessages(byte[] encoded) { - var parser = new MessageParser(); + var parser = new BinaryMessageParser(); var reader = new BytesReader(new ReadOnlyBytes(encoded)); - Assert.False(parser.TryParseMessage(ref reader, MessageFormat.Binary, out var message)); + Assert.False(parser.TryParseMessage(ref reader, out var message)); Assert.Equal(encoded.Length, reader.Index); } } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/MessageTestUtils.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/MessageTestUtils.cs deleted file mode 100644 index 83c96d4c53..0000000000 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/MessageTestUtils.cs +++ /dev/null @@ -1,38 +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.IO.Pipelines; -using System.Text; -using Xunit; - -namespace Microsoft.AspNetCore.Sockets.Tests -{ - internal static class MessageTestUtils - { - public static void AssertMessage(Message message, MessageType messageType, byte[] payload) - { - Assert.Equal(messageType, message.Type); - Assert.Equal(payload, message.Payload); - } - - public static void AssertMessage(Message message, MessageType messageType, string payload) - { - Assert.Equal(messageType, message.Type); - Assert.Equal(payload, Encoding.UTF8.GetString(message.Payload)); - } - - public static Message CreateMessage(byte[] payload, MessageType type = MessageType.Binary) - { - return new Message( - payload, - type); - } - - public static Message CreateMessage(string payload, MessageType type) - { - return new Message( - Encoding.UTF8.GetBytes(payload), - type); - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/ServerSentEventsParserTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/ServerSentEventsParserTests.cs index 6ed3dff795..7dcc88cade 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/ServerSentEventsParserTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/ServerSentEventsParserTests.cs @@ -214,14 +214,14 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters { get { - yield return new object[] { "data: Shaolin\r\ndata: Fantastic\r\n\r\n", "Shaolin" + Environment.NewLine + " Fantastic", MessageType.Text }; - yield return new object[] { "data: The\r\ndata: Get\r\ndata: Down\r\n\r\n", "The" + Environment.NewLine + "Get" + Environment.NewLine + "Down", MessageType.Text }; + yield return new object[] { "data: Shaolin\r\ndata: Fantastic\r\n\r\n", "Shaolin" + Environment.NewLine + " Fantastic" }; + yield return new object[] { "data: The\r\ndata: Get\r\ndata: Down\r\n\r\n", "The" + Environment.NewLine + "Get" + Environment.NewLine + "Down" }; } } [Theory] [MemberData(nameof(MultilineMessages))] - public void ParseMessagesWithMultipleDataLines(string encodedMessage, string expectedMessage, MessageType expectedMessageType) + public void ParseMessagesWithMultipleDataLines(string encodedMessage, string expectedMessage) { var buffer = Encoding.UTF8.GetBytes(encodedMessage); var readableBuffer = ReadableBuffer.Create(buffer); diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs index 7e7cb08c96..10677c2407 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageFormatterTests.cs @@ -14,49 +14,33 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters [Fact] public void WriteMultipleMessages() { - const string expectedEncoding = "0:B:;14:T:Hello,\r\nWorld!;"; + const string expectedEncoding = "0:;14:Hello,\r\nWorld!;"; var messages = new[] { - MessageTestUtils.CreateMessage(new byte[0]), - MessageTestUtils.CreateMessage("Hello,\r\nWorld!",MessageType.Text), + new byte[0], + Encoding.UTF8.GetBytes("Hello,\r\nWorld!") }; var output = new ArrayOutput(chunkSize: 8); // Use small chunks to test Advance/Enlarge and partial payload writing foreach (var message in messages) { - Assert.True(MessageFormatter.TryWriteMessage(message, output, MessageFormat.Text)); + Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); } Assert.Equal(expectedEncoding, Encoding.UTF8.GetString(output.ToArray())); } - + [Theory] - [InlineData(8, "0:B:;", new byte[0])] - [InlineData(8, "8:B:q83vEg==;", new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })] - [InlineData(8, "8:B:q83vEjQ=;", new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34 })] - [InlineData(8, "8:B:q83vEjRW;", new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56 })] - [InlineData(256, "8:B:q83vEjRW;", new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56 })] - public void WriteBinaryMessage(int chunkSize, string encoded, byte[] payload) + [InlineData(8, "0:;", "")] + [InlineData(8, "3:ABC;", "ABC")] + [InlineData(8, "11:A\nR\rC\r\n;DEF;", "A\nR\rC\r\n;DEF")] + [InlineData(256, "11:A\nR\rC\r\n;DEF;", "A\nR\rC\r\n;DEF")] + public void WriteMessage(int chunkSize, string encoded, string payload) { - var message = MessageTestUtils.CreateMessage(payload); - var output = new ArrayOutput(chunkSize); - - Assert.True(MessageFormatter.TryWriteMessage(message, output, MessageFormat.Text)); - - Assert.Equal(encoded, Encoding.UTF8.GetString(output.ToArray())); - } - - [Theory] - [InlineData(8, "0:T:;", MessageType.Text, "")] - [InlineData(8, "3:T:ABC;", MessageType.Text, "ABC")] - [InlineData(8, "11:T:A\nR\rC\r\n;DEF;", MessageType.Text, "A\nR\rC\r\n;DEF")] - [InlineData(256, "11:T:A\nR\rC\r\n;DEF;", MessageType.Text, "A\nR\rC\r\n;DEF")] - public void WriteTextMessage(int chunkSize, string encoded, MessageType messageType, string payload) - { - var message = MessageTestUtils.CreateMessage(payload, messageType); + var message = Encoding.UTF8.GetBytes(payload); var output = new ArrayOutput(chunkSize); // Use small chunks to test Advance/Enlarge and partial payload writing - Assert.True(MessageFormatter.TryWriteMessage(message, output, MessageFormat.Text)); + Assert.True(TextMessageFormatter.TryWriteMessage(message, output)); Assert.Equal(encoded, Encoding.UTF8.GetString(output.ToArray())); } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs index e9d57d3e83..6397a7ee17 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/Formatters/TextMessageParserTests.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Collections.Generic; using System.Text; using Microsoft.AspNetCore.Sockets.Internal.Formatters; -using Microsoft.AspNetCore.Sockets.Tests; using Xunit; namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters @@ -14,37 +13,19 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters public class TextMessageParserTests { [Theory] - [InlineData(0, "0:T:;", MessageType.Text, "")] - [InlineData(0, "3:T:ABC;", MessageType.Text, "ABC")] - [InlineData(0, "11:T:A\nR\rC\r\n;DEF;", MessageType.Text, "A\nR\rC\r\n;DEF")] - [InlineData(4, "12:T:Hello, World;", MessageType.Text, "Hello, World")] - public void ReadTextMessage(int chunkSize, string encoded, MessageType messageType, string payload) + [InlineData(0, "0:;", "")] + [InlineData(0, "3:ABC;", "ABC")] + [InlineData(0, "11:A\nR\rC\r\n;DEF;", "A\nR\rC\r\n;DEF")] + [InlineData(4, "12:Hello, World;", "Hello, World")] + public void ReadTextMessage(int chunkSize, string encoded, string payload) { - var parser = new MessageParser(); + var parser = new TextMessageParser(); var buffer = Encoding.UTF8.GetBytes(encoded); var reader = new BytesReader(buffer.ToChunkedReadOnlyBytes(chunkSize)); - Assert.True(parser.TryParseMessage(ref reader, MessageFormat.Text, out var message)); + Assert.True(parser.TryParseMessage(ref reader, out var message)); Assert.Equal(reader.Index, buffer.Length); - - MessageTestUtils.AssertMessage(message, messageType, payload); - } - - [Theory] - [InlineData("0:B:;", new byte[0])] - [InlineData("8:B:q83vEg==;", new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })] - [InlineData("8:B:q83vEjQ=;", new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34 })] - [InlineData("8:B:q83vEjRW;", new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56 })] - public void ReadBinaryMessage(string encoded, byte[] payload) - { - var parser = new MessageParser(); - var buffer = Encoding.UTF8.GetBytes(encoded); - var reader = new BytesReader(buffer); - - Assert.True(parser.TryParseMessage(ref reader, MessageFormat.Text, out var message)); - Assert.Equal(reader.Index, buffer.Length); - - MessageTestUtils.AssertMessage(message, MessageType.Binary, payload); + Assert.Equal(Encoding.UTF8.GetBytes(payload), message.ToArray()); } [Theory] @@ -53,8 +34,8 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters [InlineData(8)] public void ReadMultipleMessages(int chunkSize) { - const string encoded = "0:B:;14:T:Hello,\r\nWorld!;"; - var parser = new MessageParser(); + 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) : @@ -62,17 +43,17 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters var reader = new BytesReader(buffer); - var messages = new List(); - while (parser.TryParseMessage(ref reader, MessageFormat.Text, out var message)) + var messages = new List(); + while (parser.TryParseMessage(ref reader, out var message)) { - messages.Add(message); + messages.Add(message.ToArray()); } Assert.Equal(reader.Index, Encoding.UTF8.GetByteCount(encoded)); Assert.Equal(2, messages.Count); - MessageTestUtils.AssertMessage(messages[0], MessageType.Binary, new byte[0]); - MessageTestUtils.AssertMessage(messages[1], MessageType.Text, "Hello,\r\nWorld!"); + Assert.Equal(new byte[0], messages[0]); + Assert.Equal(Encoding.UTF8.GetBytes("Hello,\r\nWorld!"), messages[1]); } [Theory] @@ -81,45 +62,41 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters [InlineData("1230450945")] [InlineData("1:")] [InlineData("10")] - [InlineData("5:T:A")] - [InlineData("5:T:ABCDE")] + [InlineData("5:A")] + [InlineData("5:ABCDE")] public void ReadIncompleteMessages(string encoded) { - var parser = new MessageParser(); + var parser = new TextMessageParser(); var buffer = Encoding.UTF8.GetBytes(encoded); var reader = new BytesReader(buffer); - Assert.False(parser.TryParseMessage(ref reader, MessageFormat.Text, out _)); + Assert.False(parser.TryParseMessage(ref reader, out _)); } [Theory] [InlineData("X:", "Invalid length: 'X'")] - [InlineData("5:X:ABCDEF", "Unknown message type: 'X'")] - [InlineData("1:asdf", "Unknown message type: 'a'")] - [InlineData("1::", "Unknown message type: ':'")] - [InlineData("1:AB:", "Unknown message type: 'A'")] - [InlineData("1:TA", "Missing delimiter ':' after type")] - [InlineData("1029348109238412903849023841290834901283409128349018239048102394:X:ABCDEF", "Invalid length: '1029348109238412903849023841290834901283409128349018239048102394'")] + [InlineData("1:asdf", "Missing delimiter ';' after payload")] + [InlineData("1029348109238412903849023841290834901283409128349018239048102394:ABCDEF", "Invalid length: '1029348109238412903849023841290834901283409128349018239048102394'")] [InlineData("12ab34:", "Invalid length: '12ab34'")] - [InlineData("5:T:ABCDEF", "Missing delimiter ';' after payload")] + [InlineData("5:ABCDEF", "Missing delimiter ';' after payload")] public void ReadInvalidMessages(string encoded, string expectedMessage) { - var parser = new MessageParser(); + var parser = new TextMessageParser(); var buffer = Encoding.UTF8.GetBytes(encoded); var reader = new BytesReader(buffer); - var ex = Assert.Throws(() => parser.TryParseMessage(ref reader, MessageFormat.Text, out _)); + var ex = Assert.Throws(() => parser.TryParseMessage(ref reader, out _)); Assert.Equal(expectedMessage, ex.Message); } [Fact] public void ReadInvalidEncodedMessage() { - var parser = new MessageParser(); + var parser = new TextMessageParser(); // Invalid because first character is a UTF-8 "continuation" character // 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, MessageFormat.Text, out _)); + var ex = Assert.Throws(() => parser.TryParseMessage(ref reader, out _)); Assert.Equal("Invalid length", ex.Message); } }