Changing length prefixing to separator for JSON C#
This commit is contained in:
parent
b12451025f
commit
5ad5f36f88
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Internal.Encoders
|
||||
{
|
||||
|
|
@ -13,7 +12,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Encoders
|
|||
public byte[] Decode(byte[] payload)
|
||||
{
|
||||
var buffer = new ReadOnlyBuffer<byte>(payload);
|
||||
TextMessageParser.TryParseMessage(ref buffer, out var message);
|
||||
LengthPrefixedTextMessageParser.TryParseMessage(ref buffer, out var message);
|
||||
|
||||
return Convert.FromBase64String(Encoding.UTF8.GetString(message.ToArray()));
|
||||
}
|
||||
|
|
@ -23,7 +22,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Encoders
|
|||
var buffer = Encoding.UTF8.GetBytes(Convert.ToBase64String(payload));
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
TextMessageFormatter.WriteMessage(buffer, stream);
|
||||
LengthPrefixedTextMessageWriter.WriteMessage(buffer, stream);
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,185 @@
|
|||
// 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.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Internal.Encoders
|
||||
{
|
||||
public static class LengthPrefixedTextMessageParser
|
||||
{
|
||||
private const int Int32OverflowLength = 10;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static bool TryParseMessage(ref ReadOnlyBuffer<byte> buffer, out ReadOnlyBuffer<byte> payload)
|
||||
{
|
||||
payload = default(ReadOnlyBuffer<byte>);
|
||||
var span = buffer.Span;
|
||||
|
||||
if (!TryReadLength(span, out var index, out var length))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var remaining = buffer.Slice(index);
|
||||
span = remaining.Span;
|
||||
|
||||
if (!TryReadDelimiter(span, LengthPrefixedTextMessageWriter.FieldDelimiter, "length"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the delimeter
|
||||
remaining = remaining.Slice(1);
|
||||
|
||||
if (remaining.Length < length + 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
payload = remaining.Slice(0, length);
|
||||
|
||||
remaining = remaining.Slice(length);
|
||||
|
||||
if (!TryReadDelimiter(remaining.Span, LengthPrefixedTextMessageWriter.MessageDelimiter, "payload"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the delimeter
|
||||
buffer = remaining.Slice(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadLength(ReadOnlySpan<byte> buffer, out int index, out int length)
|
||||
{
|
||||
length = 0;
|
||||
// Read until the first ':' to find the length
|
||||
index = buffer.IndexOf((byte)LengthPrefixedTextMessageWriter.FieldDelimiter);
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
// Insufficient data
|
||||
return false;
|
||||
}
|
||||
|
||||
var lengthSpan = buffer.Slice(0, index);
|
||||
|
||||
if (!TryParseInt32(lengthSpan, out length, out var bytesConsumed) || bytesConsumed < lengthSpan.Length)
|
||||
{
|
||||
throw new FormatException($"Invalid length: '{Encoding.UTF8.GetString(lengthSpan.ToArray())}'");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadDelimiter(ReadOnlySpan<byte> buffer, char delimiter, string field)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer[0] != delimiter)
|
||||
{
|
||||
throw new FormatException($"Missing delimiter '{delimiter}' after {field}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryParseInt32(ReadOnlySpan<byte> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// 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.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Internal.Encoders
|
||||
{
|
||||
public static class LengthPrefixedTextMessageWriter
|
||||
{
|
||||
private const int Int32OverflowLength = 10;
|
||||
|
||||
internal const char FieldDelimiter = ':';
|
||||
internal const char MessageDelimiter = ';';
|
||||
|
||||
public static void WriteMessage(ReadOnlySpan<byte> payload, Stream output)
|
||||
{
|
||||
// Calculate the length, it's the number of characters for text messages, but number of base64 characters for binary
|
||||
|
||||
// Write the length as a string
|
||||
|
||||
// Super inefficient...
|
||||
var lengthString = payload.Length.ToString(CultureInfo.InvariantCulture);
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(Int32OverflowLength);
|
||||
var encodedLength = Encoding.UTF8.GetBytes(lengthString, 0, lengthString.Length, buffer, 0);
|
||||
output.Write(buffer, 0, encodedLength);
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
|
||||
// Write the field delimiter ':'
|
||||
output.WriteByte((byte)FieldDelimiter);
|
||||
|
||||
buffer = ArrayPool<byte>.Shared.Rent(payload.Length);
|
||||
payload.CopyTo(buffer);
|
||||
output.Write(buffer, 0, payload.Length);
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
|
||||
// Terminator
|
||||
output.WriteByte((byte)MessageDelimiter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ using System.Binary;
|
|||
using System.Buffers;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.AspNetCore.Sockets.Internal.Formatters
|
||||
namespace Microsoft.AspNetCore.SignalR.Internal.Formatters
|
||||
{
|
||||
public static class BinaryMessageFormatter
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using System.Binary;
|
||||
|
||||
namespace Microsoft.AspNetCore.Sockets.Internal.Formatters
|
||||
namespace Microsoft.AspNetCore.SignalR.Internal.Formatters
|
||||
{
|
||||
public static class BinaryMessageParser
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,44 +1,24 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Sockets.Internal.Formatters
|
||||
namespace Microsoft.AspNetCore.SignalR.Internal.Formatters
|
||||
{
|
||||
public static class TextMessageFormatter
|
||||
{
|
||||
private const int Int32OverflowLength = 10;
|
||||
|
||||
internal const char FieldDelimiter = ':';
|
||||
internal const char MessageDelimiter = ';';
|
||||
// This record separator is supposed to be used only for JSON payloads where 0x1e character
|
||||
// will not occur (is not a valid character) and therefore it is safe to not escape it
|
||||
internal static readonly byte RecordSeparator = 0x1e;
|
||||
|
||||
public static void WriteMessage(ReadOnlySpan<byte> payload, Stream output)
|
||||
{
|
||||
// Calculate the length, it's the number of characters for text messages, but number of base64 characters for binary
|
||||
|
||||
// Write the length as a string
|
||||
|
||||
// Super inefficient...
|
||||
var lengthString = payload.Length.ToString(CultureInfo.InvariantCulture);
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(Int32OverflowLength);
|
||||
var encodedLength = Encoding.UTF8.GetBytes(lengthString, 0, lengthString.Length, buffer, 0);
|
||||
output.Write(buffer, 0, encodedLength);
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
|
||||
// Write the field delimiter ':'
|
||||
output.WriteByte((byte)FieldDelimiter);
|
||||
|
||||
buffer = ArrayPool<byte>.Shared.Rent(payload.Length);
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(payload.Length);
|
||||
payload.CopyTo(buffer);
|
||||
output.Write(buffer, 0, payload.Length);
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
|
||||
// Terminator
|
||||
output.WriteByte((byte)MessageDelimiter);
|
||||
output.WriteByte(RecordSeparator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,184 +2,27 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Sockets.Internal.Formatters
|
||||
namespace Microsoft.AspNetCore.SignalR.Internal.Formatters
|
||||
{
|
||||
public static class TextMessageParser
|
||||
{
|
||||
private const int Int32OverflowLength = 10;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static bool TryParseMessage(ref ReadOnlyBuffer<byte> buffer, out ReadOnlyBuffer<byte> payload)
|
||||
{
|
||||
payload = default(ReadOnlyBuffer<byte>);
|
||||
var span = buffer.Span;
|
||||
|
||||
if (!TryReadLength(span, out var index, out var length))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var remaining = buffer.Slice(index);
|
||||
span = remaining.Span;
|
||||
|
||||
if (!TryReadDelimiter(span, TextMessageFormatter.FieldDelimiter, "length"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the delimeter
|
||||
remaining = remaining.Slice(1);
|
||||
|
||||
if (remaining.Length < length + 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
payload = remaining.Slice(0, length);
|
||||
|
||||
remaining = remaining.Slice(length);
|
||||
|
||||
if (!TryReadDelimiter(remaining.Span, TextMessageFormatter.MessageDelimiter, "payload"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the delimeter
|
||||
buffer = remaining.Slice(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadLength(ReadOnlySpan<byte> buffer, out int index, out int length)
|
||||
{
|
||||
length = 0;
|
||||
// Read until the first ':' to find the length
|
||||
index = buffer.IndexOf((byte)TextMessageFormatter.FieldDelimiter);
|
||||
|
||||
var index = buffer.Span.IndexOf(TextMessageFormatter.RecordSeparator);
|
||||
if (index == -1)
|
||||
{
|
||||
// Insufficient data
|
||||
return false;
|
||||
}
|
||||
|
||||
var lengthSpan = buffer.Slice(0, index);
|
||||
payload = buffer.Slice(0, index);
|
||||
|
||||
if (!TryParseInt32(lengthSpan, out length, out var bytesConsumed) || bytesConsumed < lengthSpan.Length)
|
||||
{
|
||||
throw new FormatException($"Invalid length: '{Encoding.UTF8.GetString(lengthSpan.ToArray())}'");
|
||||
}
|
||||
// Skip record separator
|
||||
buffer = buffer.Slice(index + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadDelimiter(ReadOnlySpan<byte> buffer, char delimiter, string field)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer[0] != delimiter)
|
||||
{
|
||||
throw new FormatException($"Missing delimiter '{delimiter}' after {field}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryParseInt32(ReadOnlySpan<byte> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||
using MsgPack;
|
||||
using MsgPack.Serialization;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
|||
await connection.ReadSentTextMessageAsync().OrTimeout();
|
||||
var invokeMessage = await connection.ReadSentTextMessageAsync().OrTimeout();
|
||||
|
||||
Assert.Equal("78:{\"invocationId\":\"1\",\"type\":1,\"target\":\"Foo\",\"nonBlocking\":true,\"arguments\":[]};", invokeMessage);
|
||||
Assert.Equal("{\"invocationId\":\"1\",\"type\":1,\"target\":\"Foo\",\"nonBlocking\":true,\"arguments\":[]}\u001e", invokeMessage);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
|||
await hubConnection.StartAsync();
|
||||
var negotiationMessage = await connection.ReadSentTextMessageAsync().OrTimeout();
|
||||
|
||||
Assert.Equal("19:{\"protocol\":\"json\"};", negotiationMessage);
|
||||
Assert.Equal("{\"protocol\":\"json\"}\u001e", negotiationMessage);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
|||
await connection.ReadSentTextMessageAsync().OrTimeout();
|
||||
var invokeMessage = await connection.ReadSentTextMessageAsync().OrTimeout();
|
||||
|
||||
Assert.Equal("59:{\"invocationId\":\"1\",\"type\":1,\"target\":\"Foo\",\"arguments\":[]};", invokeMessage);
|
||||
Assert.Equal("{\"invocationId\":\"1\",\"type\":1,\"target\":\"Foo\",\"arguments\":[]}\u001e", invokeMessage);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
|||
await connection.ReadSentTextMessageAsync().OrTimeout();
|
||||
var invokeMessage = await connection.ReadSentTextMessageAsync().OrTimeout();
|
||||
|
||||
Assert.Equal("59:{\"invocationId\":\"1\",\"type\":1,\"target\":\"Foo\",\"arguments\":[]};", invokeMessage);
|
||||
Assert.Equal("{\"invocationId\":\"1\",\"type\":1,\"target\":\"Foo\",\"arguments\":[]}\u001e", invokeMessage);
|
||||
|
||||
// Complete the channel
|
||||
await connection.ReceiveJsonMessage(new { invocationId = "1", type = 3 }).OrTimeout();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
|
||||
namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||
{
|
||||
public class ServerSentEventsParserTests
|
||||
{
|
||||
|
|
@ -8,10 +8,10 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Channels;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||
using Microsoft.AspNetCore.Sockets;
|
||||
using Microsoft.AspNetCore.Sockets.Client;
|
||||
using Microsoft.AspNetCore.Sockets.Features;
|
||||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Client.Tests
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
// 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.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Encoders;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters
|
||||
namespace Microsoft.AspNetCore.SignalR.Tests.Internal.Encoders
|
||||
{
|
||||
public class TextMessageFormatterTests
|
||||
public class LengthPrefixedTextMessageFormatterTests
|
||||
{
|
||||
[Fact]
|
||||
public void WriteMultipleMessages()
|
||||
|
|
@ -24,12 +23,12 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters
|
|||
var output = new MemoryStream();
|
||||
foreach (var message in messages)
|
||||
{
|
||||
TextMessageFormatter.WriteMessage(message, output);
|
||||
LengthPrefixedTextMessageWriter.WriteMessage(message, output);
|
||||
}
|
||||
|
||||
Assert.Equal(expectedEncoding, Encoding.UTF8.GetString(output.ToArray()));
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData(8, "0:;", "")]
|
||||
[InlineData(8, "3:ABC;", "ABC")]
|
||||
|
|
@ -40,7 +39,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters
|
|||
var message = Encoding.UTF8.GetBytes(payload);
|
||||
var output = new MemoryStream();
|
||||
|
||||
TextMessageFormatter.WriteMessage(message, output);
|
||||
LengthPrefixedTextMessageWriter.WriteMessage(message, output);
|
||||
|
||||
Assert.Equal(encoded, Encoding.UTF8.GetString(output.ToArray()));
|
||||
}
|
||||
|
|
@ -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.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Encoders;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
|
||||
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Encoders
|
||||
{
|
||||
public class TextMessageParserTests
|
||||
public class LengthPrefixedTextMessageParserTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, "0:;", "")]
|
||||
|
|
@ -21,7 +20,7 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
|
|||
{
|
||||
ReadOnlyBuffer<byte> buffer = Encoding.UTF8.GetBytes(encoded);
|
||||
|
||||
Assert.True(TextMessageParser.TryParseMessage(ref buffer, out var message));
|
||||
Assert.True(LengthPrefixedTextMessageParser.TryParseMessage(ref buffer, out var message));
|
||||
Assert.Equal(0, buffer.Length);
|
||||
Assert.Equal(Encoding.UTF8.GetBytes(payload), message.ToArray());
|
||||
}
|
||||
|
|
@ -33,7 +32,7 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
|
|||
ReadOnlyBuffer<byte> buffer = Encoding.UTF8.GetBytes(encoded);
|
||||
|
||||
var messages = new List<byte[]>();
|
||||
while (TextMessageParser.TryParseMessage(ref buffer, out var message))
|
||||
while (LengthPrefixedTextMessageParser.TryParseMessage(ref buffer, out var message))
|
||||
{
|
||||
messages.Add(message.ToArray());
|
||||
}
|
||||
|
|
@ -56,7 +55,7 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
|
|||
public void ReadIncompleteMessages(string encoded)
|
||||
{
|
||||
ReadOnlyBuffer<byte> buffer = Encoding.UTF8.GetBytes(encoded);
|
||||
Assert.False(TextMessageParser.TryParseMessage(ref buffer, out _));
|
||||
Assert.False(LengthPrefixedTextMessageParser.TryParseMessage(ref buffer, out _));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -70,7 +69,7 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
|
|||
ReadOnlyBuffer<byte> buffer = Encoding.UTF8.GetBytes(encoded);
|
||||
var ex = Assert.Throws<FormatException>(() =>
|
||||
{
|
||||
TextMessageParser.TryParseMessage(ref buffer, out _);
|
||||
LengthPrefixedTextMessageParser.TryParseMessage(ref buffer, out _);
|
||||
});
|
||||
Assert.Equal(expectedMessage, ex.Message);
|
||||
}
|
||||
|
|
@ -83,7 +82,7 @@ namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
|
|||
ReadOnlyBuffer<byte> buffer = new byte[] { 0x48, 0x65, 0x80, 0x6C, 0x6F, (byte)':' };
|
||||
var ex = Assert.Throws<FormatException>(() =>
|
||||
{
|
||||
TextMessageParser.TryParseMessage(ref buffer, out _);
|
||||
LengthPrefixedTextMessageParser.TryParseMessage(ref buffer, out _);
|
||||
});
|
||||
Assert.Equal("Invalid length: 'He<48>lo'", ex.Message);
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters
|
||||
|
|
@ -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.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Sockets.Common.Tests.Internal.Formatters
|
||||
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Formatters
|
||||
{
|
||||
public class BinaryMessageParserTests
|
||||
{
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// 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.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Formatters
|
||||
{
|
||||
public class TextMessageFormatterTests
|
||||
{
|
||||
[Fact]
|
||||
public void WriteMessage()
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
TextMessageFormatter.WriteMessage(new ReadOnlySpan<byte>(Encoding.UTF8.GetBytes("ABC")), ms);
|
||||
Assert.Equal("ABC\u001e", Encoding.UTF8.GetString(ms.ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// 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.Text;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Formatters
|
||||
{
|
||||
public class TextMessageParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReadMessage()
|
||||
{
|
||||
var message = new ReadOnlyBuffer<byte>(Encoding.UTF8.GetBytes("ABC\u001e"));
|
||||
|
||||
Assert.True(TextMessageParser.TryParseMessage(ref message, out var payload));
|
||||
Assert.Equal("ABC", Encoding.UTF8.GetString(payload.ToArray()));
|
||||
Assert.False(TextMessageParser.TryParseMessage(ref message, out payload));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReadingIncompleteMessage()
|
||||
{
|
||||
var message = new ReadOnlyBuffer<byte>(Encoding.UTF8.GetBytes("ABC"));
|
||||
Assert.False(TextMessageParser.TryParseMessage(ref message, out var payload));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReadingMultipleMessages()
|
||||
{
|
||||
var message = new ReadOnlyBuffer<byte>(Encoding.UTF8.GetBytes("ABC\u001eXYZ\u001e"));
|
||||
Assert.True(TextMessageParser.TryParseMessage(ref message, out var payload));
|
||||
Assert.Equal("ABC", Encoding.UTF8.GetString(payload.ToArray()));
|
||||
Assert.True(TextMessageParser.TryParseMessage(ref message, out payload));
|
||||
Assert.Equal("XYZ", Encoding.UTF8.GetString(payload.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IncompleteTrailingMessage()
|
||||
{
|
||||
var message = new ReadOnlyBuffer<byte>(Encoding.UTF8.GetBytes("ABC\u001eXYZ\u001e123"));
|
||||
Assert.True(TextMessageParser.TryParseMessage(ref message, out var payload));
|
||||
Assert.Equal("ABC", Encoding.UTF8.GetString(payload.ToArray()));
|
||||
Assert.True(TextMessageParser.TryParseMessage(ref message, out payload));
|
||||
Assert.Equal("XYZ", Encoding.UTF8.GetString(payload.ToArray()));
|
||||
Assert.False(TextMessageParser.TryParseMessage(ref message, out payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Protocol;
|
||||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("2:", "Unable to parse payload as a negotiation message.")]
|
||||
[InlineData("2:42;", "Unexpected JSON Token Type 'Integer'. Expected a JSON Object.")]
|
||||
[InlineData("4:\"42\";", "Unexpected JSON Token Type 'String'. Expected a JSON Object.")]
|
||||
[InlineData("4:null;", "Unexpected JSON Token Type 'Null'. Expected a JSON Object.")]
|
||||
[InlineData("2:{};", "Missing required property 'protocol'.")]
|
||||
[InlineData("2:[];", "Unexpected JSON Token Type 'Array'. Expected a JSON Object.")]
|
||||
[InlineData("", "Unable to parse payload as a negotiation message.")]
|
||||
[InlineData("42\u001e", "Unexpected JSON Token Type 'Integer'. Expected a JSON Object.")]
|
||||
[InlineData("\"42\"\u001e", "Unexpected JSON Token Type 'String'. Expected a JSON Object.")]
|
||||
[InlineData("null\u001e", "Unexpected JSON Token Type 'Null'. Expected a JSON Object.")]
|
||||
[InlineData("{}\u001e", "Missing required property 'protocol'.")]
|
||||
[InlineData("[]\u001e", "Unexpected JSON Token Type 'Array'. Expected a JSON Object.")]
|
||||
public void ParsingNegotiationMessageThrowsForInvalidMessages(string payload, string expectedMessage)
|
||||
{
|
||||
var message = Encoding.UTF8.GetBytes(payload);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Sockets.Internal.Formatters;
|
||||
using Microsoft.AspNetCore.SignalR.Internal.Formatters;
|
||||
|
||||
namespace Microsoft.AspNetCore.SignalR.Microbenchmarks
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue