Merge branch 'rel/1.0.0-alpha2' into dev

This commit is contained in:
Pawel Kadluczka 2017-09-28 17:52:25 -07:00
commit 05da66ada0
12 changed files with 320 additions and 119 deletions

22
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,22 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceRoot}/client-ts/node_modules/jasmine/bin/jasmine.js",
"args": ["JASMINE_CONFIG_PATH=${workspaceRoot}/client-ts/Microsoft.AspNetCore.SignalR.Client.TS.Tests/jasmine.json"],
"cwd": "${workspaceRoot}/client-ts",
"outFiles": []
},
{
"type": "node",
"request": "attach",
"name": "Attach to Port",
"address": "localhost",
"port": 5858,
"outFiles": []
}
]
}

View File

@ -14,6 +14,13 @@ describe("Connection", () => {
.toThrow(new Error("Cannot resolve '/test'."));
});
it("cannot be created with relative url if window object is not present", () => {
(<any>global).window = {};
expect(() => new HttpConnection("/test"))
.toThrow(new Error("Cannot resolve '/test'."));
delete (<any>global).window;
});
it("starting connection fails if getting id fails", async (done) => {
let options: IHttpConnectionOptions = {
httpClient: <IHttpClient>{

View File

@ -29,10 +29,11 @@ describe("Text Message Formatter", () => {
describe("Binary Message Formatter", () => {
([
[[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], <Uint8Array[]>[ new Uint8Array([])]],
[[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff], <Uint8Array[]>[ new Uint8Array([0xff])]],
[[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f], <Uint8Array[]>[ new Uint8Array([0xff]), new Uint8Array([0x7f])]],
[[], <Uint8Array[]>[]],
[[0x00], <Uint8Array[]>[ new Uint8Array([])]],
[[0x01, 0xff], <Uint8Array[]>[ new Uint8Array([0xff])]],
[[0x01, 0xff,
0x01, 0x7f], <Uint8Array[]>[ new Uint8Array([0xff]), new Uint8Array([0x7f])]],
] as [[number[], Uint8Array[]]]).forEach(([payload, expected_messages]) => {
it(`should parse '${payload}' correctly`, () => {
let messages = BinaryMessageFormat.parse(new Uint8Array(payload).buffer);
@ -41,14 +42,15 @@ describe("Binary Message Formatter", () => {
});
([
[[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], new Error("Cannot read message size")],
[[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x80, 0x00], new Error("Cannot read message size")],
[[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], new Error("Messages bigger than 2147483647 bytes are not supported")],
[[0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], new Error("Messages bigger than 2147483647 bytes are not supported")],
[[0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00], new Error("Messages bigger than 2147483647 bytes are not supported")],
[[0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00], new Error("Messages bigger than 2147483647 bytes are not supported")],
[[0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00], new Error("Messages bigger than 2147483647 bytes are not supported")],
[[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00], new Error("Incomplete message")],
[[0x80], new Error("Cannot read message size.")],
[[0x02, 0x01, 0x80, 0x80], new Error("Cannot read message size.")],
[[0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80], new Error("Cannot read message size.")], // the size of the second message is cut
[[0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], new Error("Incomplete message.")], // second message has only size
[[0xff, 0xff, 0xff, 0xff, 0xff], new Error("Messages bigger than 2GB are not supported.")],
[[0x80, 0x80, 0x80, 0x80, 0x08], new Error("Messages bigger than 2GB are not supported.")],
[[0x80, 0x80, 0x80, 0x80, 0x80], new Error("Messages bigger than 2GB are not supported.")],
[[0x02, 0x00], new Error("Incomplete message.")],
[[0xff, 0xff, 0xff, 0xff, 0x07], new Error("Incomplete message.")]
] as [[number[], Error]]).forEach(([payload, expected_error]) => {
it(`should fail to parse '${payload}'`, () => {
expect(() => BinaryMessageFormat.parse(new Uint8Array(payload).buffer)).toThrow(expected_error);
@ -56,8 +58,8 @@ describe("Binary Message Formatter", () => {
});
([
[[], [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]],
[[0x20], [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20]],
[[], [0x00]],
[[0x20], [0x01, 0x20]],
] as [[number[], number[]]]).forEach(([input, expected_payload]) => {
it(`should write '${input}'`, () => {
let actual = new Uint8Array(BinaryMessageFormat.write(new Uint8Array(input)));
@ -65,4 +67,18 @@ describe("Binary Message Formatter", () => {
expect(actual).toEqual(expected);
})
});
([0x0000, 0x0001, 0x007f, 0x0080, 0x3fff, 0x4000, 0xc0de] as number[]).forEach(size => {
it(`messages should be roundtrippable (message size: '${size}')`, () => {
const message = [];
for (let i = 0; i < size; i++) {
message.push(i & 0xff);
}
var payload = new Uint8Array(message);
expect(payload).toEqual(BinaryMessageFormat.parse(BinaryMessageFormat.write(payload))[0]);
})
});
});

View File

@ -20,24 +20,21 @@ describe("MessageHubProtocol", () => {
});
([
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b,
0x94, 0x03, 0xa3, 0x61, 0x62, 0x63, 0x01, 0xa3, 0x45, 0x72, 0x72],
[ [ 0x0b, 0x94, 0x03, 0xa3, 0x61, 0x62, 0x63, 0x01, 0xa3, 0x45, 0x72, 0x72],
{
type: MessageType.Completion,
invocationId: "abc",
error: "Err",
result: null
} as CompletionMessage ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
0x94, 0x03, 0xa3, 0x61, 0x62, 0x63, 0x03, 0xa2, 0x4f, 0x4b ],
[ [ 0x0a, 0x94, 0x03, 0xa3, 0x61, 0x62, 0x63, 0x03, 0xa2, 0x4f, 0x4b ],
{
type: MessageType.Completion,
invocationId: "abc",
error: null,
result: "OK"
} as CompletionMessage ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0x93, 0x03, 0xa3, 0x61, 0x62, 0x63, 0x02 ],
[ [ 0x07, 0x93, 0x03, 0xa3, 0x61, 0x62, 0x63, 0x02 ],
{
type: MessageType.Completion,
invocationId: "abc",
@ -51,8 +48,7 @@ describe("MessageHubProtocol", () => {
}));
([
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0x93, 0x02, 0xa3, 0x61, 0x62, 0x63, 0x08 ],
[ [ 0x07, 0x93, 0x02, 0xa3, 0x61, 0x62, 0x63, 0x08 ],
{
type: MessageType.Result,
invocationId: "abc",
@ -65,17 +61,17 @@ describe("MessageHubProtocol", () => {
}));
([
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ], new Error("Invalid payload.") ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x90 ], new Error("Invalid payload.") ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc2 ], new Error("Invalid payload.") ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x91, 0x05 ], new Error("Invalid message type.") ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x91, 0xa1, 0x78 ], new Error("Invalid message type.") ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x91, 0x01 ], new Error("Invalid payload for Invocation message.") ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x91, 0x02 ], new Error("Invalid payload for stream Result message.") ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x92, 0x03, 0xa0 ], new Error("Invalid payload for Completion message.") ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x94, 0x03, 0xa0, 0x02, 0x00 ], new Error("Invalid payload for Completion message.") ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x93, 0x03, 0xa0, 0x01 ], new Error("Invalid payload for Completion message.") ],
[ [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x93, 0x03, 0xa0, 0x03 ], new Error("Invalid payload for Completion message.") ]
[ [ 0x00 ], new Error("Invalid payload.") ],
[ [ 0x01, 0x90 ], new Error("Invalid payload.") ],
[ [ 0x01, 0xc2 ], new Error("Invalid payload.") ],
[ [ 0x02, 0x91, 0x05 ], new Error("Invalid message type.") ],
[ [ 0x03, 0x91, 0xa1, 0x78 ], new Error("Invalid message type.") ],
[ [ 0x02, 0x91, 0x01 ], new Error("Invalid payload for Invocation message.") ],
[ [ 0x02, 0x91, 0x02 ], new Error("Invalid payload for stream Result message.") ],
[ [ 0x03, 0x92, 0x03, 0xa0 ], new Error("Invalid payload for Completion message.") ],
[ [ 0x05, 0x94, 0x03, 0xa0, 0x02, 0x00 ], new Error("Invalid payload for Completion message.") ],
[ [ 0x04, 0x93, 0x03, 0xa0, 0x01 ], new Error("Invalid payload for Completion message.") ],
[ [ 0x04, 0x93, 0x03, 0xa0, 0x03 ], new Error("Invalid payload for Completion message.") ]
] as [[number[], Error]]).forEach(([payload, expected_error]) =>
it("throws for invalid messages", () => {
expect(() => new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer))
@ -84,10 +80,8 @@ describe("MessageHubProtocol", () => {
it("can read multiple messages", () => {
let payload = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0x93, 0x02, 0xa3, 0x61, 0x62, 0x63, 0x08,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
0x94, 0x03, 0xa3, 0x61, 0x62, 0x63, 0x03, 0xa2, 0x4f, 0x4b ];
0x07, 0x93, 0x02, 0xa3, 0x61, 0x62, 0x63, 0x08,
0x0a, 0x94, 0x03, 0xa3, 0x61, 0x62, 0x63, 0x03, 0xa2, 0x4f, 0x4b ];
let messages = new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer);
expect(messages).toEqual([
{

View File

@ -21,55 +21,73 @@ export namespace TextMessageFormat {
}
export namespace BinaryMessageFormat {
// The length prefix of binary messages is encoded as VarInt. Read the comment in
// the BinaryMessageParser.TryParseMessage for details.
export function write(output: Uint8Array): ArrayBuffer {
// .byteLength does is undefined in IE10
// msgpack5 uses returns Buffer instead of Uint8Array on IE10 and some other browser
// in which case .byteLength does will be undefined
let size = output.byteLength || output.length;
let buffer = new Uint8Array(size + 8);
// javascript bitwise operators only support 32-bit integers
for (let i = 7; i >= 4; i--) {
buffer[i] = size & 0xff;
size = size >> 8;
let lenBuffer = [];
do
{
let sizePart = size & 0x7f;
size = size >> 7;
if (size > 0) {
sizePart |= 0x80;
}
lenBuffer.push(sizePart);
}
while (size > 0);
buffer.set(output, 8);
// msgpack5 uses returns Buffer instead of Uint8Array on IE10 and some other browser
// in which case .byteLength does will be undefined
size = output.byteLength || output.length;
let buffer = new Uint8Array(lenBuffer.length + size);
buffer.set(lenBuffer, 0);
buffer.set(output, lenBuffer.length);
return buffer.buffer;
}
export function parse(input: ArrayBuffer): Uint8Array[] {
let result: Uint8Array[] = [];
let uint8Array = new Uint8Array(input);
// 8 - the length prefix size
const maxLengthPrefixSize = 5;
const numBitsToShift = [0, 7, 14, 21, 28 ];
for (let offset = 0; offset < input.byteLength;) {
if (input.byteLength < offset + 8) {
throw new Error("Cannot read message size")
}
// Note javascript bitwise operators only support 32-bit integers - for now cutting bigger messages.
// Tracking bug https://github.com/aspnet/SignalR/issues/613
if (!(uint8Array[offset] == 0 && uint8Array[offset + 1] == 0 && uint8Array[offset + 2] == 0
&& uint8Array[offset + 3] == 0 && (uint8Array[offset + 4] & 0x80) == 0)) {
throw new Error("Messages bigger than 2147483647 bytes are not supported");
}
let numBytes = 0;
let size = 0;
for (let i = 4; i < 8; i++) {
size = (size << 8) | uint8Array[offset + i];
let byteRead;
do
{
byteRead = uint8Array[offset + numBytes];
size = size | ((byteRead & 0x7f) << (numBitsToShift[numBytes]));
numBytes++;
}
while (numBytes < Math.min(maxLengthPrefixSize, input.byteLength - offset) && (byteRead & 0x80) != 0);
if ((byteRead & 0x80) !== 0 && numBytes < maxLengthPrefixSize) {
throw new Error("Cannot read message size.");
}
if (uint8Array.byteLength >= (offset + 8 + size)) {
if (numBytes === maxLengthPrefixSize && byteRead > 7) {
throw new Error("Messages bigger than 2GB are not supported.");
}
if (uint8Array.byteLength >= (offset + numBytes + size)) {
// IE does not support .slice() so use subarray
result.push(uint8Array.slice
? uint8Array.slice(offset + 8, offset + 8 + size)
: uint8Array.subarray(offset + 8, offset + 8 + size));
? uint8Array.slice(offset + numBytes, offset + numBytes + size)
: uint8Array.subarray(offset + numBytes, offset + numBytes + size));
}
else {
throw new Error("Incomplete message");
throw new Error("Incomplete message.");
}
offset = offset + 8 + size;
offset = offset + numBytes + size;
}
return result;

View File

@ -162,7 +162,7 @@ export class HttpConnection implements IConnection {
return url;
}
if (typeof window === 'undefined') {
if (typeof window === 'undefined' || !window || !window.document) {
throw new Error(`Cannot resolve '${url}'.`);
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Binary;
using System.Buffers;
using System.IO;
@ -10,18 +9,34 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Formatters
{
public static class BinaryMessageFormatter
{
public static void WriteMessage(ReadOnlySpan<byte> payload, Stream output)
public unsafe static void WriteMessage(ReadOnlySpan<byte> payload, Stream output)
{
// TODO: Optimize for size - (e.g. use Varints)
var length = sizeof(long);
var buffer = ArrayPool<byte>.Shared.Rent(length + payload.Length);
// This code writes length prefix of the message as a VarInt. Read the comment in
// the BinaryMessageParser.TryParseMessage for details.
var lenBuffer = stackalloc byte[5];
var lenNumBytes = 0;
var length = payload.Length;
do
{
ref var current = ref lenBuffer[lenNumBytes];
current = (byte)(length & 0x7f);
length >>= 7;
if (length > 0)
{
current |= 0x80;
}
lenNumBytes++;
}
while (length > 0);
var buffer = ArrayPool<byte>.Shared.Rent(lenNumBytes + payload.Length);
var bufferSpan = buffer.AsSpan();
BufferWriter.WriteBigEndian<long>(bufferSpan, payload.Length);
bufferSpan = bufferSpan.Slice(length);
new Span<byte>(lenBuffer, lenNumBytes).CopyTo(bufferSpan);
bufferSpan = bufferSpan.Slice(lenNumBytes);
payload.CopyTo(bufferSpan);
output.Write(buffer, 0, payload.Length + length);
output.Write(buffer, 0, lenNumBytes + payload.Length);
ArrayPool<byte>.Shared.Return(buffer);
}
}

View File

@ -2,41 +2,70 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Binary;
namespace Microsoft.AspNetCore.SignalR.Internal.Formatters
{
public static class BinaryMessageParser
{
private static int[] _numBitsToShift = new[] { 0, 7, 14, 21, 28 };
private const int MaxLengthPrefixSize = 5;
public static bool TryParseMessage(ref ReadOnlyBuffer<byte> buffer, out ReadOnlyBuffer<byte> payload)
{
long length = 0;
payload = default(ReadOnlyBuffer<byte>);
payload = default;
if (buffer.Length < sizeof(long))
if (buffer.IsEmpty)
{
return false;
}
// Read the length
length = buffer.Span.Slice(0, sizeof(long)).ReadBigEndian<long>();
// The payload starts with a length prefix encoded as a VarInt. VarInts use the most significant bit
// as a marker whether the byte is the last byte of the VarInt or if it spans to the next byte. Bytes
// appear in the reverse order - i.e. the first byte contains the least significant bits of the value
// Examples:
// VarInt: 0x35 - %00110101 - the most significant bit is 0 so the value is %x0110101 i.e. 0x35 (53)
// VarInt: 0x80 0x25 - %10000000 %00101001 - the most significant bit of the first byte is 1 so the
// remaining bits (%x0000000) are the lowest bits of the value. The most significant bit of the second
// byte is 0 meaning this is last byte of the VarInt. The actual value bits (%x0101001) need to be
// prepended to the bits we already read so the values is %01010010000000 i.e. 0x1480 (5248)
// We support paylads up to 2GB so the biggest number we support is 7fffffff which when encoded as
// VarInt is 0xFF 0xFF 0xFF 0xFF 0x7F - hence the maximum length prefix is 5 bytes.
if (length > Int32.MaxValue)
var length = 0U;
var numBytes = 0;
var lengthPrefixBuffer = buffer.Span.Slice(0, Math.Min(MaxLengthPrefixSize, buffer.Length));
byte byteRead;
do
{
throw new FormatException("Messages over 2GB in size are not supported");
byteRead = lengthPrefixBuffer[numBytes];
length = length | (((uint)(byteRead & 0x7f)) << _numBitsToShift[numBytes]);
numBytes++;
}
while (numBytes < lengthPrefixBuffer.Length && ((byteRead & 0x80) != 0));
// size bytes are missing
if ((byteRead & 0x80) != 0 && (numBytes < MaxLengthPrefixSize))
{
return false;
}
if ((byteRead & 0x80) != 0 || (numBytes == MaxLengthPrefixSize && byteRead > 7))
{
throw new FormatException("Messages over 2GB in size are not supported.");
}
// We don't have enough data
if (buffer.Length < (int)length + sizeof(long))
if (buffer.Length < length + numBytes)
{
return false;
}
// Get the payload
payload = buffer.Slice(sizeof(long), (int)length);
payload = buffer.Slice(numBytes, (int)length);
// Skip the payload
buffer = buffer.Slice((int)length + sizeof(long));
buffer = buffer.Slice(numBytes + (int)length);
return true;
}
}

View File

@ -4,6 +4,7 @@
<Description>Common serialiation primitives for SignalR Clients Servers</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Microsoft.AspNetCore.SignalR</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

View File

@ -1,6 +1,8 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
@ -16,9 +18,9 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters
{
var expectedEncoding = new byte[]
{
/* length: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* length: */ 0x00,
/* body: <empty> */
/* length: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E,
/* length: */ 0x0E,
/* body: */ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2C, 0x0D, 0x0A, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21,
};
@ -38,10 +40,34 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters
}
[Theory]
[InlineData(0, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new byte[0])]
[InlineData(0, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })]
[InlineData(4, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new byte[0])]
[InlineData(4, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })]
[InlineData(0, new byte[] { 0x00 }, new byte[0])]
[InlineData(0, new byte[] { 0x04, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })]
[InlineData(0, new byte[]
{
0x80, 0x01, // Size - 128
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
},
new byte[]
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
})]
[InlineData(4, new byte[] { 0x00 }, new byte[0])]
[InlineData(4, new byte[] { 0x04, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })]
public void WriteBinaryMessage(int offset, byte[] encoded, byte[] payload)
{
var output = new MemoryStream();
@ -57,10 +83,10 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters
}
[Theory]
[InlineData(0, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "")]
[InlineData(0, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x42, 0x43 }, "ABC")]
[InlineData(0, 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, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "")]
[InlineData(0, new byte[] { 0x00 }, "")]
[InlineData(0, new byte[] { 0x03, 0x41, 0x42, 0x43 }, "ABC")]
[InlineData(0, new byte[] { 0x0B, 0x41, 0x0A, 0x52, 0x0D, 0x43, 0x0D, 0x0A, 0x3B, 0x44, 0x45, 0x46 }, "A\nR\rC\r\n;DEF")]
[InlineData(4, new byte[] { 0x00 }, "")]
public void WriteTextMessage(int offset, byte[] encoded, string payload)
{
var message = Encoding.UTF8.GetBytes(payload);
@ -75,5 +101,36 @@ namespace Microsoft.AspNetCore.Sockets.Tests.Internal.Formatters
Assert.Equal(encoded, output.ToArray().Skip(offset));
}
[Theory]
[MemberData(nameof(RandomPayloads))]
public void RoundTrippingTest(byte[] payload)
{
using (var ms = new MemoryStream())
{
BinaryMessageFormatter.WriteMessage(payload, ms);
var buffer = new ReadOnlyBuffer<byte>(ms.ToArray());
Assert.True(BinaryMessageParser.TryParseMessage(ref buffer, out var roundtripped));
Assert.Equal(payload, roundtripped.ToArray());
}
}
public static IEnumerable<object[]> RandomPayloads()
{
// boundaries
yield return new[] { CreatePayload(0) };
yield return new[] { CreatePayload(1) };
yield return new[] { CreatePayload(0x7f) };
yield return new[] { CreatePayload(0x80) };
yield return new[] { CreatePayload(0x3fff) };
yield return new[] { CreatePayload(0x4000) };
// random
yield return new[] { CreatePayload(0xc0de) };
}
private static byte[] CreatePayload(int size) =>
Enumerable.Range(0, size).Select(n => (byte)(n & 0xff)).ToArray();
}
}

View File

@ -12,9 +12,9 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Formatters
public class BinaryMessageParserTests
{
[Theory]
[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")]
[InlineData(new byte[] { 0x00 }, "")]
[InlineData(new byte[] { 0x03, 0x41, 0x42, 0x43 }, "ABC")]
[InlineData(new byte[] { 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)
{
ReadOnlyBuffer<byte> span = encoded;
@ -25,8 +25,31 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Formatters
}
[Theory]
[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 })]
[InlineData(new byte[] { 0x00 }, new byte[0])]
[InlineData(new byte[] { 0x04, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })]
[InlineData(new byte[]
{
0x80, 0x01, // Size - 128
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
},
new byte[]
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
})]
public void ReadBinaryMessage(byte[] encoded, byte[] payload)
{
ReadOnlyBuffer<byte> span = encoded;
@ -35,14 +58,36 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Formatters
Assert.Equal(payload, message.ToArray());
}
[Theory]
[InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })]
[InlineData(new byte[] { 0x80, 0x80, 0x80, 0x80, 0x08 })] // 2GB + 1
[InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF })]
public void BinaryMessageParserThrowsForMessagesOver2GB(byte[] payload)
{
var buffer = new ReadOnlyBuffer<byte>(payload);
var ex = Assert.Throws<FormatException>(() => BinaryMessageParser.TryParseMessage(ref buffer, out var message));
Assert.Equal("Messages over 2GB in size are not supported.", ex.Message);
}
[Theory]
[InlineData(new byte[] { })]
[InlineData(new byte[] { 0x04, 0xAB, 0xCD, 0xEF })]
[InlineData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0x07 })] // 2GB
[InlineData(new byte[] { 0x80 })] // size is cut
public void BinaryMessageParserReturnsFalseForPartialPayloads(byte[] payload)
{
var buffer = new ReadOnlyBuffer<byte>(payload);
Assert.False(BinaryMessageParser.TryParseMessage(ref buffer, out var message));
}
[Fact]
public void ReadMultipleMessages()
{
var encoded = new byte[]
{
/* length: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* length: */ 0x00,
/* body: <empty> */
/* length: */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E,
/* length: */ 0x0E,
/* body: */ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2C, 0x0D, 0x0A, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21,
};
ReadOnlyBuffer<byte> buffer = encoded;
@ -62,7 +107,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Formatters
[Theory]
[InlineData(new byte[0])] // Empty
[InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00 })] // Not enough data for payload
[InlineData(new byte[] { 0x09, 0x00, 0x00 })] // Not enough data for payload
public void ReadIncompleteMessages(byte[] encoded)
{
ReadOnlyBuffer<byte> buffer = encoded;

View File

@ -117,12 +117,12 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
public void ParserThrowsForInvalidMessages(byte[] payload, string expectedExceptionMessage)
{
var payloadSize = payload.Length;
Debug.Assert(payloadSize <= 0xff, "This test does not support payloads larger than 255");
Debug.Assert(payloadSize <= 0x7f, "This test does not support payloads larger than 127 bytes");
// prefix payload with the size
var buffer = new byte[8 + payloadSize];
buffer[7] = (byte)(payloadSize & 0xff);
Array.Copy(payload, 0, buffer, 8, payloadSize);
var buffer = new byte[1 + payloadSize];
buffer[0] = (byte)(payloadSize & 0x7f);
Array.Copy(payload, 0, buffer, 1, payloadSize);
var binder = new TestBinder(new[] { typeof(string) }, typeof(string));
var exception = Assert.Throws<FormatException>(() => _hubProtocol.TryParseMessages(buffer, binder, out var messages));
@ -131,13 +131,13 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
}
[Theory]
[InlineData(new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x01 }, 0 })]
[InlineData(new object[] { new byte[] { 0x05, 0x01 }, 0 })]
[InlineData(new object[] {
new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x93, 0x03, 0xa1, 0x78, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x93, 0x03, 0xa1, 0x78, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x93, 0x03, 0xa1
0x05, 0x93, 0x03, 0xa1, 0x78, 0x02,
0x05, 0x93, 0x03, 0xa1, 0x78, 0x02,
0x05, 0x93, 0x03, 0xa1
}, 2 })]
public void ParserDoesNotConsumePartialData(byte[] payload, int expectedMessagesCount)
{
@ -154,7 +154,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
new InvocationMessage("0", false, "A", 1, new CustomObject()),
new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x95, 0x01, 0xa1, 0x30, 0xc2, 0xa1, 0x41,
0x5b, 0x95, 0x01, 0xa1, 0x30, 0xc2, 0xa1, 0x41,
0x92, // argument array
0x01, // 1 - first argument
// 0x85 - a map of 5 items (properties)
@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
CompletionMessage.WithResult("0", new CustomObject()),
new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x94, 0x03, 0xa1, 0x30, 0x03,
0x57, 0x94, 0x03, 0xa1, 0x30, 0x03,
// 0x85 - a map of 5 items (properties)
0x85, 0xac, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x50, 0x72, 0x6f, 0x70, 0xd3, 0x08,
0xd4, 0x80, 0x6d, 0xb2, 0x76, 0xc0, 0x00, 0xaa, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x50, 0x72,
@ -186,7 +186,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
new StreamItemMessage("0", new CustomObject()),
new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x93, 0x02, 0xa1, 0x30,
0x56, 0x93, 0x02, 0xa1, 0x30,
// 0x85 - a map of 5 items (properties)
0x85, 0xac, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x50, 0x72, 0x6f, 0x70, 0xd3, 0x08,
0xd4, 0x80, 0x6d, 0xb2, 0x76, 0xc0, 0x00, 0xaa, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x50, 0x72,
@ -212,10 +212,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
[Fact]
public void CanWriteObjectsWithoutDefaultCtors()
{
var expectedPayload = new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x94, 0x03, 0xa1, 0x30, 0x03, 0x91, 0x2a
};
var expectedPayload = new byte[] { 0x07, 0x94, 0x03, 0xa1, 0x30, 0x03, 0x91, 0x2a };
using (var memoryStream = new MemoryStream())
{