Swtich to MessagePack-CSharp (#1879)

This commit is contained in:
BrennanConroy 2018-04-09 17:04:32 -07:00 committed by GitHub
parent ecd665c471
commit 6d050140e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 540 additions and 425 deletions

View File

@ -57,7 +57,7 @@
<MicrosoftNETCoreApp21PackageVersion>2.1.0-preview2-26403-06</MicrosoftNETCoreApp21PackageVersion> <MicrosoftNETCoreApp21PackageVersion>2.1.0-preview2-26403-06</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion> <MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.7.49</MoqPackageVersion> <MoqPackageVersion>4.7.49</MoqPackageVersion>
<MsgPackCliPackageVersion>1.0.0-rc</MsgPackCliPackageVersion> <MessagePackPackageVersion>1.7.3.4</MessagePackPackageVersion>
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion> <NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
<StackExchangeRedisStrongNamePackageVersion>1.2.4</StackExchangeRedisStrongNamePackageVersion> <StackExchangeRedisStrongNamePackageVersion>1.2.4</StackExchangeRedisStrongNamePackageVersion>
<SystemBuffersPackageVersion>4.5.0-preview2-26403-05</SystemBuffersPackageVersion> <SystemBuffersPackageVersion>4.5.0-preview2-26403-05</SystemBuffersPackageVersion>

View File

@ -11,5 +11,6 @@ namespace FunctionalTests
public int[] IntArray { get; set; } public int[] IntArray { get; set; }
public byte[] ByteArray { get; set; } public byte[] ByteArray { get; set; }
public Guid GUID { get; set; } public Guid GUID { get; set; }
public DateTime DateTime { get;set; }
} }
} }

View File

@ -70,5 +70,17 @@ namespace FunctionalTests
{ {
return complexObject; return complexObject;
} }
public ComplexObject SendComplexObject()
{
return new ComplexObject
{
ByteArray = new byte[] { 0x1, 0x2, 0x3 },
DateTime = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc),
GUID = new Guid("00010203-0405-0607-0706-050403020100"),
IntArray = new int[] { 1, 2, 3 },
String = "hello world",
};
}
} }
} }

View File

@ -379,9 +379,10 @@ describe("hubConnection", () => {
ByteArray: protocol.name === "json" ByteArray: protocol.name === "json"
? "aGVsbG8=" ? "aGVsbG8="
: new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]), : new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]),
GUID: protocol.name === "json" DateTime: protocol.name === "json"
? "00010203-0405-0607-0706-050403020100" ? "2002-04-01T10:20:15Z"
: new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00]), : new Date(Date.UTC(2002, 3, 1, 10, 20, 15)), // Apr 1, 2002, 10:20:15am UTC
GUID: "00010203-0405-0607-0706-050403020100",
IntArray: [0x01, 0x02, 0x03, 0xff], IntArray: [0x01, 0x02, 0x03, 0xff],
String: "Hello, World!", String: "Hello, World!",
}; };
@ -395,9 +396,50 @@ describe("hubConnection", () => {
// msgpack creates a Buffer for byte arrays and jasmine fails to compare a Buffer // msgpack creates a Buffer for byte arrays and jasmine fails to compare a Buffer
// and a Uint8Array even though Buffer instances are also Uint8Array instances // and a Uint8Array even though Buffer instances are also Uint8Array instances
value.ByteArray = new Uint8Array(value.ByteArray); value.ByteArray = new Uint8Array(value.ByteArray);
}
expect(value).toEqual(complexObject);
})
.then(() => {
hubConnection.stop();
})
.catch((e) => {
fail(e);
done();
});
});
// GUIDs are serialized as Buffer as well. it("can receive different types", (done) => {
value.GUID = new Uint8Array(value.GUID); const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
...commonOptions,
protocol,
transport: transportType,
});
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
});
const complexObject = {
ByteArray: protocol.name === "json"
? "AQID"
: new Uint8Array([0x1, 0x2, 0x3]),
DateTime: protocol.name === "json"
? "2000-01-01T00:00:00Z"
: new Date(Date.UTC(2000, 0, 1)),
GUID: "00010203-0405-0607-0706-050403020100",
IntArray: [0x01, 0x02, 0x03],
String: "hello world",
};
hubConnection.start()
.then(() => {
return hubConnection.invoke("SendComplexObject");
})
.then((value) => {
if (protocol.name === "messagepack") {
// msgpack creates a Buffer for byte arrays and jasmine fails to compare a Buffer
// and a Uint8Array even though Buffer instances are also Uint8Array instances
value.ByteArray = new Uint8Array(value.ByteArray);
} }
expect(value).toEqual(complexObject); expect(value).toEqual(complexObject);
}) })

View File

@ -35,11 +35,12 @@
"dev": true "dev": true
}, },
"bl": { "bl": {
"version": "1.2.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"requires": { "requires": {
"readable-stream": "2.3.3" "readable-stream": "2.3.6",
"safe-buffer": "5.1.1"
} }
}, },
"buffer": { "buffer": {
@ -74,32 +75,32 @@
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
}, },
"msgpack5": { "msgpack5": {
"version": "4.0.1", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.0.1.tgz", "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.0.2.tgz",
"integrity": "sha512-Wx+c6YEE5UMrVrDQuFSIseprjgfeAvMYD35klSKm1xNCvtFI5KICfRmwKMc0Wmu7BknXUA3DUhFWugyCwTsRsg==", "integrity": "sha512-rEIx0/KFtWGtqlF5D/NIMzOHDhm7AhIFzHR3/PLqMrXXbMKoSitDE/IDuTactlTjxEc0ScmHx/5qoH015uL7xA==",
"requires": { "requires": {
"bl": "1.2.1", "bl": "1.2.2",
"inherits": "2.0.3", "inherits": "2.0.3",
"readable-stream": "2.3.3", "readable-stream": "2.3.6",
"safe-buffer": "5.1.1" "safe-buffer": "5.1.1"
} }
}, },
"process-nextick-args": { "process-nextick-args": {
"version": "1.0.7", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
}, },
"readable-stream": { "readable-stream": {
"version": "2.3.3", "version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": { "requires": {
"core-util-is": "1.0.2", "core-util-is": "1.0.2",
"inherits": "2.0.3", "inherits": "2.0.3",
"isarray": "1.0.0", "isarray": "1.0.0",
"process-nextick-args": "1.0.7", "process-nextick-args": "2.0.0",
"safe-buffer": "5.1.1", "safe-buffer": "5.1.1",
"string_decoder": "1.0.3", "string_decoder": "1.1.1",
"util-deprecate": "1.0.2" "util-deprecate": "1.0.2"
} }
}, },
@ -109,9 +110,9 @@
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
}, },
"string_decoder": { "string_decoder": {
"version": "1.0.3", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": { "requires": {
"safe-buffer": "5.1.1" "safe-buffer": "5.1.1"
} }

View File

@ -43,7 +43,7 @@
"@aspnet/signalr": "^1.0.0-preview3" "@aspnet/signalr": "^1.0.0-preview3"
}, },
"dependencies": { "dependencies": {
"msgpack5": "^4.0.1" "msgpack5": "^4.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/msgpack5": "^3.4.1", "@types/msgpack5": "^3.4.1",

View File

@ -5,7 +5,6 @@ using System;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MsgPack.Serialization;
using SignalRSamples.ConnectionHandlers; using SignalRSamples.ConnectionHandlers;
using SignalRSamples.Hubs; using SignalRSamples.Hubs;
@ -24,10 +23,7 @@ namespace SignalRSamples
// Faster pings for testing // Faster pings for testing
options.KeepAliveInterval = TimeSpan.FromSeconds(5); options.KeepAliveInterval = TimeSpan.FromSeconds(5);
}) })
.AddMessagePackProtocol(options => .AddMessagePackProtocol();
{
options.SerializationContext.DictionarySerlaizationOptions.KeyTransformer = DictionaryKeyTransformers.LowerCamel;
});
//.AddRedis(); //.AddRedis();
services.AddCors(o => services.AddCors(o =>

View File

@ -8,12 +8,12 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using MessagePack;
using MessagePack.Formatters;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Formatters; using Microsoft.AspNetCore.SignalR.Internal.Formatters;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using MsgPack;
using MsgPack.Serialization;
namespace Microsoft.AspNetCore.SignalR.Internal.Protocol namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
{ {
@ -23,11 +23,11 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
private const int VoidResult = 2; private const int VoidResult = 2;
private const int NonVoidResult = 3; private const int NonVoidResult = 3;
private IFormatterResolver _resolver;
public static readonly string ProtocolName = "messagepack"; public static readonly string ProtocolName = "messagepack";
public static readonly int ProtocolVersion = 1; public static readonly int ProtocolVersion = 1;
public SerializationContext SerializationContext { get; }
public string Name => ProtocolName; public string Name => ProtocolName;
public int Version => ProtocolVersion; public int Version => ProtocolVersion;
@ -40,7 +40,32 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
public MessagePackHubProtocol(IOptions<MessagePackHubProtocolOptions> options) public MessagePackHubProtocol(IOptions<MessagePackHubProtocolOptions> options)
{ {
SerializationContext = options.Value.SerializationContext; var msgPackOptions = options.Value;
SetupResolver(msgPackOptions);
}
private void SetupResolver(MessagePackHubProtocolOptions options)
{
// if counts don't match then we know users customized resolvers so we set up the options
// with the provided resolvers
if (options.FormatterResolvers.Count != SignalRResolver.Resolvers.Count)
{
_resolver = new CombinedResolvers(options.FormatterResolvers);
return;
}
for (var i = 0; i < options.FormatterResolvers.Count; i++)
{
// check if the user customized the resolvers
if (options.FormatterResolvers[i] != SignalRResolver.Resolvers[i])
{
_resolver = new CombinedResolvers(options.FormatterResolvers);
return;
}
}
// Use optimized cached resolver if the default is chosen
_resolver = SignalRResolver.Instance;
} }
public bool IsVersionSupported(int version) public bool IsVersionSupported(int version)
@ -58,9 +83,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
var arraySegment = GetArraySegment(payload); var arraySegment = GetArraySegment(payload);
message = ParseMessage(arraySegment.Array, arraySegment.Offset, binder); message = ParseMessage(arraySegment.Array, arraySegment.Offset, binder, _resolver);
return true;
return message != null;
} }
private static ArraySegment<byte> GetArraySegment(in ReadOnlySequence<byte> input) private static ArraySegment<byte> GetArraySegment(in ReadOnlySequence<byte> input)
@ -77,41 +101,39 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
return new ArraySegment<byte>(input.ToArray()); return new ArraySegment<byte>(input.ToArray());
} }
private static HubMessage ParseMessage(byte[] input, int startOffset, IInvocationBinder binder) private static HubMessage ParseMessage(byte[] input, int startOffset, IInvocationBinder binder, IFormatterResolver resolver)
{ {
using (var unpacker = Unpacker.Create(input, startOffset)) _ = MessagePackBinary.ReadArrayHeader(input, startOffset, out var readSize);
startOffset += readSize;
var messageType = ReadInt32(input, ref startOffset, "messageType");
switch (messageType)
{ {
_ = ReadArrayLength(unpacker, "elementCount"); case HubProtocolConstants.InvocationMessageType:
return CreateInvocationMessage(input, ref startOffset, binder, resolver);
var messageType = ReadInt32(unpacker, "messageType"); case HubProtocolConstants.StreamInvocationMessageType:
return CreateStreamInvocationMessage(input, ref startOffset, binder, resolver);
switch (messageType) case HubProtocolConstants.StreamItemMessageType:
{ return CreateStreamItemMessage(input, ref startOffset, binder, resolver);
case HubProtocolConstants.InvocationMessageType: case HubProtocolConstants.CompletionMessageType:
return CreateInvocationMessage(unpacker, binder); return CreateCompletionMessage(input, ref startOffset, binder, resolver);
case HubProtocolConstants.StreamInvocationMessageType: case HubProtocolConstants.CancelInvocationMessageType:
return CreateStreamInvocationMessage(unpacker, binder); return CreateCancelInvocationMessage(input, ref startOffset);
case HubProtocolConstants.StreamItemMessageType: case HubProtocolConstants.PingMessageType:
return CreateStreamItemMessage(unpacker, binder); return PingMessage.Instance;
case HubProtocolConstants.CompletionMessageType: case HubProtocolConstants.CloseMessageType:
return CreateCompletionMessage(unpacker, binder); return CreateCloseMessage(input, ref startOffset);
case HubProtocolConstants.CancelInvocationMessageType: default:
return CreateCancelInvocationMessage(unpacker); // Future protocol changes can add message types, old clients can ignore them
case HubProtocolConstants.PingMessageType: return null;
return PingMessage.Instance;
case HubProtocolConstants.CloseMessageType:
return CreateCloseMessage(unpacker);
default:
// Future protocol changes can add message types, old clients can ignore them
return null;
}
} }
} }
private static InvocationMessage CreateInvocationMessage(Unpacker unpacker, IInvocationBinder binder) private static InvocationMessage CreateInvocationMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver)
{ {
var headers = ReadHeaders(unpacker); var headers = ReadHeaders(input, ref offset);
var invocationId = ReadInvocationId(unpacker); var invocationId = ReadInvocationId(input, ref offset);
// For MsgPack, we represent an empty invocation ID as an empty string, // For MsgPack, we represent an empty invocation ID as an empty string,
// so we need to normalize that to "null", which is what indicates a non-blocking invocation. // so we need to normalize that to "null", which is what indicates a non-blocking invocation.
@ -120,12 +142,12 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
invocationId = null; invocationId = null;
} }
var target = ReadString(unpacker, "target"); var target = ReadString(input, ref offset, "target");
var parameterTypes = binder.GetParameterTypes(target); var parameterTypes = binder.GetParameterTypes(target);
try try
{ {
var arguments = BindArguments(unpacker, parameterTypes); var arguments = BindArguments(input, ref offset, parameterTypes, resolver);
return ApplyHeaders(headers, new InvocationMessage(invocationId, target, argumentBindingException: null, arguments: arguments)); return ApplyHeaders(headers, new InvocationMessage(invocationId, target, argumentBindingException: null, arguments: arguments));
} }
catch (Exception ex) catch (Exception ex)
@ -134,16 +156,16 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
} }
} }
private static StreamInvocationMessage CreateStreamInvocationMessage(Unpacker unpacker, IInvocationBinder binder) private static StreamInvocationMessage CreateStreamInvocationMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver)
{ {
var headers = ReadHeaders(unpacker); var headers = ReadHeaders(input, ref offset);
var invocationId = ReadInvocationId(unpacker); var invocationId = ReadInvocationId(input, ref offset);
var target = ReadString(unpacker, "target"); var target = ReadString(input, ref offset, "target");
var parameterTypes = binder.GetParameterTypes(target); var parameterTypes = binder.GetParameterTypes(target);
try try
{ {
var arguments = BindArguments(unpacker, parameterTypes); var arguments = BindArguments(input, ref offset, parameterTypes, resolver);
return ApplyHeaders(headers, new StreamInvocationMessage(invocationId, target, argumentBindingException: null, arguments: arguments)); return ApplyHeaders(headers, new StreamInvocationMessage(invocationId, target, argumentBindingException: null, arguments: arguments));
} }
catch (Exception ex) catch (Exception ex)
@ -152,20 +174,20 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
} }
} }
private static StreamItemMessage CreateStreamItemMessage(Unpacker unpacker, IInvocationBinder binder) private static StreamItemMessage CreateStreamItemMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver)
{ {
var headers = ReadHeaders(unpacker); var headers = ReadHeaders(input, ref offset);
var invocationId = ReadInvocationId(unpacker); var invocationId = ReadInvocationId(input, ref offset);
var itemType = binder.GetReturnType(invocationId); var itemType = binder.GetReturnType(invocationId);
var value = DeserializeObject(unpacker, itemType, "item"); var value = DeserializeObject(input, ref offset, itemType, "item", resolver);
return ApplyHeaders(headers, new StreamItemMessage(invocationId, value)); return ApplyHeaders(headers, new StreamItemMessage(invocationId, value));
} }
private static CompletionMessage CreateCompletionMessage(Unpacker unpacker, IInvocationBinder binder) private static CompletionMessage CreateCompletionMessage(byte[] input, ref int offset, IInvocationBinder binder, IFormatterResolver resolver)
{ {
var headers = ReadHeaders(unpacker); var headers = ReadHeaders(input, ref offset);
var invocationId = ReadInvocationId(unpacker); var invocationId = ReadInvocationId(input, ref offset);
var resultKind = ReadInt32(unpacker, "resultKind"); var resultKind = ReadInt32(input, ref offset, "resultKind");
string error = null; string error = null;
object result = null; object result = null;
@ -174,11 +196,11 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
switch (resultKind) switch (resultKind)
{ {
case ErrorResult: case ErrorResult:
error = ReadString(unpacker, "error"); error = ReadString(input, ref offset, "error");
break; break;
case NonVoidResult: case NonVoidResult:
var itemType = binder.GetReturnType(invocationId); var itemType = binder.GetReturnType(invocationId);
result = DeserializeObject(unpacker, itemType, "argument"); result = DeserializeObject(input, ref offset, itemType, "argument", resolver);
hasResult = true; hasResult = true;
break; break;
case VoidResult: case VoidResult:
@ -191,22 +213,22 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
return ApplyHeaders(headers, new CompletionMessage(invocationId, error, result, hasResult)); return ApplyHeaders(headers, new CompletionMessage(invocationId, error, result, hasResult));
} }
private static CancelInvocationMessage CreateCancelInvocationMessage(Unpacker unpacker) private static CancelInvocationMessage CreateCancelInvocationMessage(byte[] input, ref int offset)
{ {
var headers = ReadHeaders(unpacker); var headers = ReadHeaders(input, ref offset);
var invocationId = ReadInvocationId(unpacker); var invocationId = ReadInvocationId(input, ref offset);
return ApplyHeaders(headers, new CancelInvocationMessage(invocationId)); return ApplyHeaders(headers, new CancelInvocationMessage(invocationId));
} }
private static CloseMessage CreateCloseMessage(Unpacker unpacker) private static CloseMessage CreateCloseMessage(byte[] input, ref int offset)
{ {
var error = ReadString(unpacker, "error"); var error = ReadString(input, ref offset, "error");
return new CloseMessage(error); return new CloseMessage(error);
} }
private static Dictionary<string, string> ReadHeaders(Unpacker unpacker) private static Dictionary<string, string> ReadHeaders(byte[] input, ref int offset)
{ {
var headerCount = ReadMapLength(unpacker, "headers"); var headerCount = ReadMapLength(input, ref offset, "headers");
if (headerCount > 0) if (headerCount > 0)
{ {
// If headerCount is larger than int.MaxValue, things are going to go horribly wrong anyway :) // If headerCount is larger than int.MaxValue, things are going to go horribly wrong anyway :)
@ -214,8 +236,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
for (var i = 0; i < headerCount; i++) for (var i = 0; i < headerCount; i++)
{ {
var key = ReadString(unpacker, $"headers[{i}].Key"); var key = ReadString(input, ref offset, $"headers[{i}].Key");
var value = ReadString(unpacker, $"headers[{i}].Value"); var value = ReadString(input, ref offset, $"headers[{i}].Value");
headers[key] = value; headers[key] = value;
} }
return headers; return headers;
@ -226,9 +248,9 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
} }
} }
private static object[] BindArguments(Unpacker unpacker, IReadOnlyList<Type> parameterTypes) private static object[] BindArguments(byte[] input, ref int offset, IReadOnlyList<Type> parameterTypes, IFormatterResolver resolver)
{ {
var argumentCount = ReadArrayLength(unpacker, "arguments"); var argumentCount = ReadArrayLength(input, ref offset, "arguments");
if (parameterTypes.Count != argumentCount) if (parameterTypes.Count != argumentCount)
{ {
@ -241,7 +263,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
var arguments = new object[argumentCount]; var arguments = new object[argumentCount];
for (var i = 0; i < argumentCount; i++) for (var i = 0; i < argumentCount; i++)
{ {
arguments[i] = DeserializeObject(unpacker, parameterTypes[i], "argument"); arguments[i] = DeserializeObject(input, ref offset, parameterTypes[i], "argument", resolver);
} }
return arguments; return arguments;
@ -281,11 +303,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
} }
} }
private void WriteMessageCore(HubMessage message, Stream output) private void WriteMessageCore(HubMessage message, Stream packer)
{ {
// PackerCompatibilityOptions.None prevents from serializing byte[] as strings
// and allows extended objects
var packer = Packer.Create(output, PackerCompatibilityOptions.None);
switch (message) switch (message)
{ {
case InvocationMessage invocationMessage: case InvocationMessage invocationMessage:
@ -314,135 +333,147 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
} }
} }
private void WriteInvocationMessage(InvocationMessage message, Packer packer) private void WriteInvocationMessage(InvocationMessage message, Stream packer)
{ {
packer.PackArrayHeader(5); MessagePackBinary.WriteArrayHeader(packer, 5);
packer.Pack(HubProtocolConstants.InvocationMessageType); MessagePackBinary.WriteInt32(packer, HubProtocolConstants.InvocationMessageType);
PackHeaders(packer, message.Headers); PackHeaders(packer, message.Headers);
if (string.IsNullOrEmpty(message.InvocationId)) if (string.IsNullOrEmpty(message.InvocationId))
{ {
packer.PackNull(); MessagePackBinary.WriteNil(packer);
} }
else else
{ {
packer.PackString(message.InvocationId); MessagePackBinary.WriteString(packer, message.InvocationId);
} }
packer.PackString(message.Target); MessagePackBinary.WriteString(packer, message.Target);
packer.PackArrayHeader(message.Arguments.Length); MessagePackBinary.WriteArrayHeader(packer, message.Arguments.Length);
foreach (var arg in message.Arguments) foreach (var arg in message.Arguments)
{ {
packer.PackObject(arg, SerializationContext); WriteArgument(arg, packer);
} }
} }
private void WriteStreamInvocationMessage(StreamInvocationMessage message, Packer packer) private void WriteStreamInvocationMessage(StreamInvocationMessage message, Stream packer)
{ {
packer.PackArrayHeader(5); MessagePackBinary.WriteArrayHeader(packer, 5);
packer.Pack(HubProtocolConstants.StreamInvocationMessageType); MessagePackBinary.WriteInt16(packer, HubProtocolConstants.StreamInvocationMessageType);
PackHeaders(packer, message.Headers); PackHeaders(packer, message.Headers);
packer.PackString(message.InvocationId); MessagePackBinary.WriteString(packer, message.InvocationId);
packer.PackString(message.Target); MessagePackBinary.WriteString(packer, message.Target);
packer.PackArrayHeader(message.Arguments.Length);
MessagePackBinary.WriteArrayHeader(packer, message.Arguments.Length);
foreach (var arg in message.Arguments) foreach (var arg in message.Arguments)
{ {
packer.PackObject(arg, SerializationContext); WriteArgument(arg, packer);
} }
} }
private void WriteStreamingItemMessage(StreamItemMessage message, Packer packer) private void WriteStreamingItemMessage(StreamItemMessage message, Stream packer)
{ {
packer.PackArrayHeader(4); MessagePackBinary.WriteArrayHeader(packer, 4);
packer.Pack(HubProtocolConstants.StreamItemMessageType); MessagePackBinary.WriteInt16(packer, HubProtocolConstants.StreamItemMessageType);
PackHeaders(packer, message.Headers); PackHeaders(packer, message.Headers);
packer.PackString(message.InvocationId); MessagePackBinary.WriteString(packer, message.InvocationId);
packer.PackObject(message.Item, SerializationContext); WriteArgument(message.Item, packer);
} }
private void WriteCompletionMessage(CompletionMessage message, Packer packer) private void WriteArgument(object argument, Stream stream)
{
if (argument == null)
{
MessagePackBinary.WriteNil(stream);
}
else
{
MessagePackSerializer.NonGeneric.Serialize(argument.GetType(), stream, argument, _resolver);
}
}
private void WriteCompletionMessage(CompletionMessage message, Stream packer)
{ {
var resultKind = var resultKind =
message.Error != null ? ErrorResult : message.Error != null ? ErrorResult :
message.HasResult ? NonVoidResult : message.HasResult ? NonVoidResult :
VoidResult; VoidResult;
packer.PackArrayHeader(4 + (resultKind != VoidResult ? 1 : 0)); MessagePackBinary.WriteArrayHeader(packer, 4 + (resultKind != VoidResult ? 1 : 0));
packer.Pack(HubProtocolConstants.CompletionMessageType); MessagePackBinary.WriteInt32(packer, HubProtocolConstants.CompletionMessageType);
PackHeaders(packer, message.Headers); PackHeaders(packer, message.Headers);
packer.PackString(message.InvocationId); MessagePackBinary.WriteString(packer, message.InvocationId);
packer.Pack(resultKind); MessagePackBinary.WriteInt32(packer, resultKind);
switch (resultKind) switch (resultKind)
{ {
case ErrorResult: case ErrorResult:
packer.PackString(message.Error); MessagePackBinary.WriteString(packer, message.Error);
break; break;
case NonVoidResult: case NonVoidResult:
packer.PackObject(message.Result, SerializationContext); WriteArgument(message.Result, packer);
break; break;
} }
} }
private void WriteCancelInvocationMessage(CancelInvocationMessage message, Packer packer) private void WriteCancelInvocationMessage(CancelInvocationMessage message, Stream packer)
{ {
packer.PackArrayHeader(3); MessagePackBinary.WriteArrayHeader(packer, 3);
packer.Pack(HubProtocolConstants.CancelInvocationMessageType); MessagePackBinary.WriteInt16(packer, HubProtocolConstants.CancelInvocationMessageType);
PackHeaders(packer, message.Headers); PackHeaders(packer, message.Headers);
packer.PackString(message.InvocationId); MessagePackBinary.WriteString(packer, message.InvocationId);
} }
private void WriteCloseMessage(CloseMessage message, Packer packer) private void WriteCloseMessage(CloseMessage message, Stream packer)
{ {
packer.PackArrayHeader(2); MessagePackBinary.WriteArrayHeader(packer, 2);
packer.Pack(HubProtocolConstants.CloseMessageType); MessagePackBinary.WriteInt16(packer, HubProtocolConstants.CloseMessageType);
if (string.IsNullOrEmpty(message.Error)) if (string.IsNullOrEmpty(message.Error))
{ {
packer.PackNull(); MessagePackBinary.WriteNil(packer);
} }
else else
{ {
packer.PackString(message.Error); MessagePackBinary.WriteString(packer, message.Error);
} }
} }
private void WritePingMessage(PingMessage pingMessage, Packer packer) private void WritePingMessage(PingMessage pingMessage, Stream packer)
{ {
packer.PackArrayHeader(1); MessagePackBinary.WriteArrayHeader(packer, 1);
packer.Pack(HubProtocolConstants.PingMessageType); MessagePackBinary.WriteInt32(packer, HubProtocolConstants.PingMessageType);
} }
private void PackHeaders(Packer packer, IDictionary<string, string> headers) private void PackHeaders(Stream packer, IDictionary<string, string> headers)
{ {
if (headers != null) if (headers != null)
{ {
packer.PackMapHeader(headers.Count); MessagePackBinary.WriteMapHeader(packer, headers.Count);
if (headers.Count > 0) if (headers.Count > 0)
{ {
foreach (var header in headers) foreach (var header in headers)
{ {
packer.PackString(header.Key); MessagePackBinary.WriteString(packer, header.Key);
packer.PackString(header.Value); MessagePackBinary.WriteString(packer, header.Value);
} }
} }
} }
else else
{ {
packer.PackMapHeader(0); MessagePackBinary.WriteMapHeader(packer, 0);
} }
} }
private static string ReadInvocationId(Unpacker unpacker) private static string ReadInvocationId(byte[] input, ref int offset)
{ {
return ReadString(unpacker, "invocationId"); return ReadString(input, ref offset, "invocationId");
} }
private static int ReadInt32(Unpacker unpacker, string field) private static int ReadInt32(byte[] input, ref int offset, string field)
{ {
Exception msgPackException = null; Exception msgPackException = null;
try try
{ {
if (unpacker.ReadInt32(out var value)) var readInt = MessagePackBinary.ReadInt32(input, offset, out var readSize);
{ offset += readSize;
return value; return readInt;
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -452,22 +483,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
throw new InvalidDataException($"Reading '{field}' as Int32 failed.", msgPackException); throw new InvalidDataException($"Reading '{field}' as Int32 failed.", msgPackException);
} }
private static string ReadString(Unpacker unpacker, string field) private static string ReadString(byte[] input, ref int offset, string field)
{ {
Exception msgPackException = null; Exception msgPackException = null;
try try
{ {
if (unpacker.Read()) var readString = MessagePackBinary.ReadString(input, offset, out var readSize);
{ offset += readSize;
if (unpacker.LastReadData.IsNil) return readString;
{
return null;
}
else
{
return unpacker.LastReadData.AsString();
}
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -477,15 +500,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
throw new InvalidDataException($"Reading '{field}' as String failed.", msgPackException); throw new InvalidDataException($"Reading '{field}' as String failed.", msgPackException);
} }
private static bool ReadBoolean(Unpacker unpacker, string field) private static bool ReadBoolean(byte[] input, ref int offset, string field)
{ {
Exception msgPackException = null; Exception msgPackException = null;
try try
{ {
if (unpacker.ReadBoolean(out var value)) var readBool = MessagePackBinary.ReadBoolean(input, offset, out var readSize);
{ offset += readSize;
return value; return readBool;
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -495,15 +517,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
throw new InvalidDataException($"Reading '{field}' as Boolean failed.", msgPackException); throw new InvalidDataException($"Reading '{field}' as Boolean failed.", msgPackException);
} }
private static long ReadMapLength(Unpacker unpacker, string field) private static long ReadMapLength(byte[] input, ref int offset, string field)
{ {
Exception msgPackException = null; Exception msgPackException = null;
try try
{ {
if (unpacker.ReadMapLength(out var value)) var readMap = MessagePackBinary.ReadMapHeader(input, offset, out var readSize);
{ offset += readSize;
return value; return readMap;
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -513,15 +534,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
throw new InvalidDataException($"Reading map length for '{field}' failed.", msgPackException); throw new InvalidDataException($"Reading map length for '{field}' failed.", msgPackException);
} }
private static long ReadArrayLength(Unpacker unpacker, string field) private static long ReadArrayLength(byte[] input, ref int offset, string field)
{ {
Exception msgPackException = null; Exception msgPackException = null;
try try
{ {
if (unpacker.ReadArrayLength(out var value)) var readArray = MessagePackBinary.ReadArrayHeader(input, offset, out var readSize);
{ offset += readSize;
return value; return readArray;
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -531,16 +551,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
throw new InvalidDataException($"Reading array length for '{field}' failed.", msgPackException); throw new InvalidDataException($"Reading array length for '{field}' failed.", msgPackException);
} }
private static object DeserializeObject(Unpacker unpacker, Type type, string field) private static object DeserializeObject(byte[] input, ref int offset, Type type, string field, IFormatterResolver resolver)
{ {
Exception msgPackException = null; Exception msgPackException = null;
try try
{ {
if (unpacker.Read()) var obj = MessagePackSerializer.NonGeneric.Deserialize(type, new ArraySegment<byte>(input, offset, input.Length - offset), resolver);
{ offset += MessagePackBinary.ReadNextBlock(input, offset);
var serializer = MessagePackSerializer.Get(type); return obj;
return serializer.UnpackFrom(unpacker);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -550,14 +568,68 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
throw new InvalidDataException($"Deserializing object of the `{type.Name}` type for '{field}' failed.", msgPackException); throw new InvalidDataException($"Deserializing object of the `{type.Name}` type for '{field}' failed.", msgPackException);
} }
internal static SerializationContext CreateDefaultSerializationContext() internal static List<IFormatterResolver> CreateDefaultFormatterResolvers()
{ {
// serializes objects (here: arguments and results) as maps so that property names are preserved // Copy to allow users to add/remove resolvers without changing the static SignalRResolver list
var serializationContext = new SerializationContext { SerializationMethod = SerializationMethod.Map }; return new List<IFormatterResolver>(SignalRResolver.Resolvers);
}
// allows for serializing objects that cannot be deserialized due to the lack of the default ctor etc. internal class SignalRResolver : IFormatterResolver
serializationContext.CompatibilityOptions.AllowAsymmetricSerializer = true; {
return serializationContext; public static readonly IFormatterResolver Instance = new SignalRResolver();
public static readonly IList<IFormatterResolver> Resolvers = new[]
{
MessagePack.Resolvers.DynamicEnumAsStringResolver.Instance,
MessagePack.Resolvers.ContractlessStandardResolver.Instance,
};
public IMessagePackFormatter<T> GetFormatter<T>()
{
return Cache<T>.Formatter;
}
private static class Cache<T>
{
public static readonly IMessagePackFormatter<T> Formatter;
static Cache()
{
foreach (var resolver in Resolvers)
{
Formatter = resolver.GetFormatter<T>();
if (Formatter != null)
{
return;
}
}
}
}
}
// Support for users making their own Formatter lists
internal class CombinedResolvers : IFormatterResolver
{
private readonly IList<IFormatterResolver> _resolvers;
public CombinedResolvers(IList<IFormatterResolver> resolvers)
{
_resolvers = resolvers;
}
public IMessagePackFormatter<T> GetFormatter<T>()
{
foreach (var resolver in _resolvers)
{
var formatter = resolver.GetFormatter<T>();
if (formatter != null)
{
return formatter;
}
}
return null;
}
} }
} }
} }

View File

@ -1,13 +1,14 @@
// 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using MessagePack;
using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using MsgPack.Serialization;
namespace Microsoft.AspNetCore.SignalR namespace Microsoft.AspNetCore.SignalR
{ {
public class MessagePackHubProtocolOptions public class MessagePackHubProtocolOptions
{ {
public SerializationContext SerializationContext { get; set; } = MessagePackHubProtocol.CreateDefaultSerializationContext(); public IList<IFormatterResolver> FormatterResolvers { get; set; } = MessagePackHubProtocol.CreateDefaultFormatterResolvers();
} }
} }

View File

@ -12,7 +12,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MsgPack.Cli" Version="$(MsgPackCliPackageVersion)" /> <PackageReference Include="MessagePack" Version="$(MessagePackPackageVersion)" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -7,7 +7,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MsgPack.Serialization;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using Xunit; using Xunit;
@ -78,27 +77,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
} }
[Fact] [Fact]
public void AddMessagePackProtocolSetsHubProtocolToMsgPackWithDefaultOptions() public void AddMessagePackProtocolSetsHubProtocolToMsgPack()
{ {
var serviceProvider = new HubConnectionBuilder().AddMessagePackProtocol().Services.BuildServiceProvider(); var serviceProvider = new HubConnectionBuilder().AddMessagePackProtocol().Services.BuildServiceProvider();
var actualProtocol = Assert.IsType<MessagePackHubProtocol>(serviceProvider.GetService<IHubProtocol>()); Assert.IsType<MessagePackHubProtocol>(serviceProvider.GetService<IHubProtocol>());
Assert.Equal(SerializationMethod.Map, actualProtocol.SerializationContext.SerializationMethod);
}
[Fact]
public void AddMessagePackProtocolSetsHubProtocolToMsgPackWithProvidedOptions()
{
var serviceProvider = new HubConnectionBuilder().AddMessagePackProtocol(options =>
{
options.SerializationContext = new SerializationContext
{
SerializationMethod = SerializationMethod.Array
};
}).Services.BuildServiceProvider();
var actualProtocol = Assert.IsType<MessagePackHubProtocol>(serviceProvider.GetService<IHubProtocol>());
Assert.Equal(SerializationMethod.Array, actualProtocol.SerializationContext.SerializationMethod);
} }
} }
} }

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
public int IntProp { get; set; } = 42; public int IntProp { get; set; } = 42;
public DateTime DateTimeProp { get; set; } = new DateTime(2017, 4, 11); public DateTime DateTimeProp { get; set; } = new DateTime(2017, 4, 11, 0, 0, 0, DateTimeKind.Utc);
public object NullProp { get; set; } = null; public object NullProp { get; set; } = null;

View File

@ -37,10 +37,10 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", null, 1, "Foo", 2.0f), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), new JsonProtocolTestData("InvocationMessage_HasFloatArgument", new InvocationMessage(null, "Target", null, 1, "Foo", 2.0f), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"),
new JsonProtocolTestData("InvocationMessage_HasBoolArgument", new InvocationMessage(null, "Target", null, true), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[true]}"), new JsonProtocolTestData("InvocationMessage_HasBoolArgument", new InvocationMessage(null, "Target", null, true), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[true]}"),
new JsonProtocolTestData("InvocationMessage_HasNullArgument", new InvocationMessage(null, "Target", null, new object[] { null }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[null]}"), new JsonProtocolTestData("InvocationMessage_HasNullArgument", new InvocationMessage(null, "Target", null, new object[] { null }), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[null]}"),
new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", null, new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00\",\"ByteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNoCamelCase", new InvocationMessage(null, "Target", null, new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"),
new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", null, new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00\",\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnore", new InvocationMessage(null, "Target", null, new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"),
new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", null, new CustomObject()), false, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new InvocationMessage(null, "Target", null, new CustomObject()), false, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"),
new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", null, new CustomObject()), true, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("InvocationMessage_HasCustomArgumentWithNullValueInclude", new InvocationMessage(null, "Target", null, new CustomObject()), true, NullValueHandling.Include, "{\"type\":1,\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"),
new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", null, 1, "Foo", 2.0f)), true, NullValueHandling.Ignore, "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), new JsonProtocolTestData("InvocationMessage_HasHeaders", AddHeaders(TestHeaders, new InvocationMessage("123", "Target", null, 1, "Foo", 2.0f)), true, NullValueHandling.Ignore, "{\"type\":1," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"),
new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", null, "2016-05-10T13:51:20+12:34"), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), new JsonProtocolTestData("InvocationMessage_StringIsoDateArgument", new InvocationMessage("Method", null, "2016-05-10T13:51:20+12:34"), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"),
new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgument", new InvocationMessage("Method", null, DateTimeOffset.Parse("2016-05-10T13:51:20+12:34")), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"), new JsonProtocolTestData("InvocationMessage_DateTimeOffsetArgument", new InvocationMessage("Method", null, DateTimeOffset.Parse("2016-05-10T13:51:20+12:34")), true, NullValueHandling.Ignore, "{\"type\":1,\"target\":\"Method\",\"arguments\":[\"2016-05-10T13:51:20+12:34\"]}"),
@ -50,22 +50,22 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
new JsonProtocolTestData("StreamItemMessage_HasFloatItem", new StreamItemMessage("123", 2.0f), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":2.0}"), new JsonProtocolTestData("StreamItemMessage_HasFloatItem", new StreamItemMessage("123", 2.0f), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":2.0}"),
new JsonProtocolTestData("StreamItemMessage_HasBoolItem", new StreamItemMessage("123", true), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":true}"), new JsonProtocolTestData("StreamItemMessage_HasBoolItem", new StreamItemMessage("123", true), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":true}"),
new JsonProtocolTestData("StreamItemMessage_HasNullItem", new StreamItemMessage("123", null), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":null}"), new JsonProtocolTestData("StreamItemMessage_HasNullItem", new StreamItemMessage("123", null), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":null}"),
new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00\",\"ByteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"),
new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00\",\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnore", new StreamItemMessage("123", new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"),
new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, NullValueHandling.Include, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueIgnoreAndNoCamelCase", new StreamItemMessage("123", new CustomObject()), false, NullValueHandling.Include, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"),
new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new CustomObject()), true, NullValueHandling.Include, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("StreamItemMessage_HasCustomItemWithNullValueInclude", new StreamItemMessage("123", new CustomObject()), true, NullValueHandling.Include, "{\"type\":2,\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new CustomObject())), true, NullValueHandling.Include, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("StreamItemMessage_HasHeaders", AddHeaders(TestHeaders, new StreamItemMessage("123", new CustomObject())), true, NullValueHandling.Include, "{\"type\":2," + SerializedHeaders + ",\"invocationId\":\"123\",\"item\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
new JsonProtocolTestData("CompletionMessage_HasIntergerResult", CompletionMessage.WithResult("123", 1), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":1}"), new JsonProtocolTestData("CompletionMessage_HasIntergerResult", CompletionMessage.WithResult("123", 1), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":1}"),
new JsonProtocolTestData("CompletionMessage_HasStringResult", CompletionMessage.WithResult("123", "Foo"), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":\"Foo\"}"), new JsonProtocolTestData("CompletionMessage_HasStringResult", CompletionMessage.WithResult("123", "Foo"), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":\"Foo\"}"),
new JsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":2.0}"), new JsonProtocolTestData("CompletionMessage_HasFloatResult", CompletionMessage.WithResult("123", 2.0f), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":2.0}"),
new JsonProtocolTestData("CompletionMessage_HasBoolResult", CompletionMessage.WithResult("123", true), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":true}"), new JsonProtocolTestData("CompletionMessage_HasBoolResult", CompletionMessage.WithResult("123", true), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":true}"),
new JsonProtocolTestData("CompletionMessage_HasNullResult", CompletionMessage.WithResult("123", null), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":null}"), new JsonProtocolTestData("CompletionMessage_HasNullResult", CompletionMessage.WithResult("123", null), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":null}"),
new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00\",\"ByteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}}"),
new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00\",\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIgnore", CompletionMessage.WithResult("123", new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}}"),
new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, NullValueHandling.Include, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueIncludeAndNoCamelCase", CompletionMessage.WithResult("123", new CustomObject()), false, NullValueHandling.Include, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}}"),
new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new CustomObject()), true, NullValueHandling.Include, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("CompletionMessage_HasCustomResultWithNullValueInclude", CompletionMessage.WithResult("123", new CustomObject()), true, NullValueHandling.Include, "{\"type\":3,\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
new JsonProtocolTestData("CompletionMessage_HasTestHeadersAndCustomItemResult", AddHeaders(TestHeaders, CompletionMessage.WithResult("123", new CustomObject())), true, NullValueHandling.Include, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"), new JsonProtocolTestData("CompletionMessage_HasTestHeadersAndCustomItemResult", AddHeaders(TestHeaders, CompletionMessage.WithResult("123", new CustomObject())), true, NullValueHandling.Include, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"result\":{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}}"),
new JsonProtocolTestData("CompletionMessage_HasError", CompletionMessage.WithError("123", "Whoops!"), false, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"error\":\"Whoops!\"}"), new JsonProtocolTestData("CompletionMessage_HasError", CompletionMessage.WithError("123", "Whoops!"), false, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\",\"error\":\"Whoops!\"}"),
new JsonProtocolTestData("CompletionMessage_HasErrorAndHeaders", AddHeaders(TestHeaders, CompletionMessage.WithError("123", "Whoops!")), false, NullValueHandling.Ignore, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"error\":\"Whoops!\"}"), new JsonProtocolTestData("CompletionMessage_HasErrorAndHeaders", AddHeaders(TestHeaders, CompletionMessage.WithError("123", "Whoops!")), false, NullValueHandling.Ignore, "{\"type\":3," + SerializedHeaders + ",\"invocationId\":\"123\",\"error\":\"Whoops!\"}"),
new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\"}"), new JsonProtocolTestData("CompletionMessage_HasErrorAndCamelCase", CompletionMessage.Empty("123"), true, NullValueHandling.Ignore, "{\"type\":3,\"invocationId\":\"123\"}"),
@ -75,11 +75,11 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", null, 1, "Foo", 2.0f), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"), new JsonProtocolTestData("StreamInvocationMessage_HasFloatArgument", new StreamInvocationMessage("123", "Target", null, 1, "Foo", 2.0f), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[1,\"Foo\",2.0]}"),
new JsonProtocolTestData("StreamInvocationMessage_HasBoolArgument", new StreamInvocationMessage("123", "Target", null, true), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[true]}"), new JsonProtocolTestData("StreamInvocationMessage_HasBoolArgument", new StreamInvocationMessage("123", "Target", null, true), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[true]}"),
new JsonProtocolTestData("StreamInvocationMessage_HasNullArgument", new StreamInvocationMessage("123", "Target", null, new object[] { null }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[null]}"), new JsonProtocolTestData("StreamInvocationMessage_HasNullArgument", new StreamInvocationMessage("123", "Target", null, new object[] { null }), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[null]}"),
new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", null, new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00\",\"ByteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNoCamelCase", new StreamInvocationMessage("123", "Target", null, new CustomObject()), false, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"ByteArrProp\":\"AQID\"}]}"),
new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", null, new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00\",\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnore", new StreamInvocationMessage("123", "Target", null, new CustomObject()), true, NullValueHandling.Ignore, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"byteArrProp\":\"AQID\"}]}"),
new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", null, new CustomObject()), false, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueIgnoreAndNoCamelCase", new StreamInvocationMessage("123", "Target", null, new CustomObject()), false, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"StringProp\":\"SignalR!\",\"DoubleProp\":6.2831853071,\"IntProp\":42,\"DateTimeProp\":\"2017-04-11T00:00:00Z\",\"NullProp\":null,\"ByteArrProp\":\"AQID\"}]}"),
new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", null, new CustomObject()), true, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("StreamInvocationMessage_HasCustomArgumentWithNullValueInclude", new StreamInvocationMessage("123", "Target", null, new CustomObject()), true, NullValueHandling.Include, "{\"type\":4,\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"),
new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", null, new CustomObject())), true, NullValueHandling.Include, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), new JsonProtocolTestData("StreamInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new StreamInvocationMessage("123", "Target", null, new CustomObject())), true, NullValueHandling.Include, "{\"type\":4," + SerializedHeaders + ",\"invocationId\":\"123\",\"target\":\"Target\",\"arguments\":[{\"stringProp\":\"SignalR!\",\"doubleProp\":6.2831853071,\"intProp\":42,\"dateTimeProp\":\"2017-04-11T00:00:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"),
new JsonProtocolTestData("CancelInvocationMessage_HasInvocationId", new CancelInvocationMessage("123"), true, NullValueHandling.Ignore, "{\"type\":5,\"invocationId\":\"123\"}"), new JsonProtocolTestData("CancelInvocationMessage_HasInvocationId", new CancelInvocationMessage("123"), true, NullValueHandling.Ignore, "{\"type\":5,\"invocationId\":\"123\"}"),
new JsonProtocolTestData("CancelInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new CancelInvocationMessage("123")), true, NullValueHandling.Ignore, "{\"type\":5," + SerializedHeaders + ",\"invocationId\":\"123\"}"), new JsonProtocolTestData("CancelInvocationMessage_HasHeaders", AddHeaders(TestHeaders, new CancelInvocationMessage("123")), true, NullValueHandling.Ignore, "{\"type\":5," + SerializedHeaders + ",\"invocationId\":\"123\"}"),

View File

@ -1,15 +0,0 @@
using System;
using System.Linq;
using MsgPack;
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
{
public static class MessagePackHelpers
{
public static MessagePackObject Array(params MessagePackObject[] items) =>
new MessagePackObject(items);
public static MessagePackObject Map(params (MessagePackObject Key, MessagePackObject Value)[] items) =>
new MessagePackObject(new MessagePackObjectDictionary(items.ToDictionary(i => i.Key, i => i.Value)));
}
}

View File

@ -4,19 +4,17 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal.Formatters; using Microsoft.AspNetCore.SignalR.Internal.Formatters;
using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using MsgPack;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
{ {
using static HubMessageHelpers; using static HubMessageHelpers;
using static MessagePackHelpers;
public class MessagePackHubProtocolTests public class MessagePackHubProtocolTests
{ {
@ -27,36 +25,22 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
{ "ValueWithNewLines", "Also\nWorks\r\nFine" }, { "ValueWithNewLines", "Also\nWorks\r\nFine" },
}; };
private static readonly MessagePackObject TestHeadersSerialized = Map(
("Foo", "Bar"),
("KeyWith\nNew\r\nLines", "Still Works"),
("ValueWithNewLines", "Also\nWorks\r\nFine"));
private static readonly MessagePackHubProtocol _hubProtocol private static readonly MessagePackHubProtocol _hubProtocol
= new MessagePackHubProtocol(); = new MessagePackHubProtocol();
private static readonly MessagePackObject CustomObjectSerialized = Map( public enum TestEnum
("ByteArrProp", new MessagePackObject(new byte[] { 1, 2, 3 }, isBinary: true)), {
("DateTimeProp", new MessagePackObject(Timestamp.FromDateTime(new DateTime(2017, 4, 11)))), Zero = 0,
("DoubleProp", 6.2831853071), One
("IntProp", 42), }
("NullProp", MessagePackObject.Nil),
("StringProp", "SignalR!"));
// Test Data for Parse/WriteMessages: // Test Data for Parse/WriteMessages:
// * Name: A string name that is used when reporting the test (it's the ToString value for ProtocolTestData) // * Name: A string name that is used when reporting the test (it's the ToString value for ProtocolTestData)
// * Message: The HubMessage that is either expected (in Parse) or used as input (in Write) // * Message: The HubMessage that is either expected (in Parse) or used as input (in Write)
// * Encoded: Raw MessagePackObject values (using the MessagePackHelpers static "Arr" and "Map" helpers) describing the message // * Binary: Base64-encoded binary "baseline" to sanity-check MessagePack-CSharp behavior
// * Binary: Base64-encoded binary "baseline" to sanity-check MsgPack-Cli behavior
// //
// The Encoded value is used as input to "Parse" and as the expected output that is verified in "Write". So if our encoding changes, // When changing the tests/message pack parsing if you get test failures look at the base64 encoding and
// those values will change and the Assert will give you a useful error telling you how the MsgPack structure itself changed (rather than just // use a tool like https://sugendran.github.io/msgpack-visualizer/ to verify that the MsgPack is correct and then just replace the Base64 value.
// a bunch of random bytes). However, we want to be sure MsgPack-Cli doesn't change behavior, so we also verify that the binary encoding
// matches our expectation by comparing against a base64-string.
//
// If you change MsgPack encoding, you should update the 'encoded' values for these items, and then re-run the test. You'll get a failure which will
// provide a new Base64 binary string to replace in the 'binary' value. Use a tool like https://sugendran.github.io/msgpack-visualizer/ to verify
// that the MsgPack is correct and then just replace the Base64 value.
public static IEnumerable<object[]> TestDataNames public static IEnumerable<object[]> TestDataNames
{ {
@ -75,202 +59,180 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
new ProtocolTestData( new ProtocolTestData(
name: "InvocationWithNoHeadersAndNoArgs", name: "InvocationWithNoHeadersAndNoArgs",
message: new InvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null), message: new InvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null),
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), "xyz", "method", Array()),
binary: "lQGAo3h5eqZtZXRob2SQ"), binary: "lQGAo3h5eqZtZXRob2SQ"),
new ProtocolTestData( new ProtocolTestData(
name: "InvocationWithNoHeadersNoIdAndNoArgs", name: "InvocationWithNoHeadersNoIdAndNoArgs",
message: new InvocationMessage(target: "method", argumentBindingException: null), message: new InvocationMessage(target: "method", argumentBindingException: null),
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array()),
binary: "lQGAwKZtZXRob2SQ"), binary: "lQGAwKZtZXRob2SQ"),
new ProtocolTestData( new ProtocolTestData(
name: "InvocationWithNoHeadersNoIdAndSingleNullArg", name: "InvocationWithNoHeadersNoIdAndSingleNullArg",
message: new InvocationMessage(target: "method", argumentBindingException: null, new object[] { null }), message: new InvocationMessage(target: "method", argumentBindingException: null, new object[] { null }),
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(MessagePackObject.Nil)),
binary: "lQGAwKZtZXRob2SRwA=="), binary: "lQGAwKZtZXRob2SRwA=="),
new ProtocolTestData( new ProtocolTestData(
name: "InvocationWithNoHeadersNoIdAndSingleIntArg", name: "InvocationWithNoHeadersNoIdAndSingleIntArg",
message: new InvocationMessage(target: "method", argumentBindingException: null, 42), message: new InvocationMessage(target: "method", argumentBindingException: null, 42),
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(42)),
binary: "lQGAwKZtZXRob2SRKg=="), binary: "lQGAwKZtZXRob2SRKg=="),
new ProtocolTestData( new ProtocolTestData(
name: "InvocationWithNoHeadersNoIdIntAndStringArgs", name: "InvocationWithNoHeadersNoIdIntAndStringArgs",
message: new InvocationMessage(target: "method", argumentBindingException: null, 42, "string"), message: new InvocationMessage(target: "method", argumentBindingException: null, 42, "string"),
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(42, "string")),
binary: "lQGAwKZtZXRob2SSKqZzdHJpbmc="), binary: "lQGAwKZtZXRob2SSKqZzdHJpbmc="),
new ProtocolTestData(
name: "InvocationWithNoHeadersNoIdIntAndEnumArgs",
message: new InvocationMessage(target: "method", argumentBindingException: null, 42, TestEnum.One),
binary: "lQGAwKZtZXRob2SSKqNPbmU="),
new ProtocolTestData( new ProtocolTestData(
name: "InvocationWithNoHeadersNoIdAndCustomObjectArg", name: "InvocationWithNoHeadersNoIdAndCustomObjectArg",
message: new InvocationMessage(target: "method", argumentBindingException: null, 42, "string", new CustomObject()), message: new InvocationMessage(target: "method", argumentBindingException: null, 42, "string", new CustomObject()),
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(42, "string", CustomObjectSerialized)), binary: "lQGAwKZtZXRob2STKqZzdHJpbmeGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="),
binary: "lQGAwKZtZXRob2STKqZzdHJpbmeGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
new ProtocolTestData( new ProtocolTestData(
name: "InvocationWithNoHeadersNoIdAndArrayOfCustomObjectArgs", name: "InvocationWithNoHeadersNoIdAndArrayOfCustomObjectArgs",
message: new InvocationMessage(target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() }), message: new InvocationMessage(target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() }),
encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(CustomObjectSerialized, CustomObjectSerialized)), binary: "lQGAwKZtZXRob2SShqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQIDhqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQID"),
binary: "lQGAwKZtZXRob2SShqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIhhqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIh"),
new ProtocolTestData( new ProtocolTestData(
name: "InvocationWithHeadersNoIdAndArrayOfCustomObjectArgs", name: "InvocationWithHeadersNoIdAndArrayOfCustomObjectArgs",
message: AddHeaders(TestHeaders, new InvocationMessage(target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() })), message: AddHeaders(TestHeaders, new InvocationMessage(target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() })),
encoded: Array(HubProtocolConstants.InvocationMessageType, TestHeadersSerialized, MessagePackObject.Nil, "method", Array(CustomObjectSerialized, CustomObjectSerialized)), binary: "lQGDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmXApm1ldGhvZJKGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="),
binary: "lQGDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmXApm1ldGhvZJKGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
// StreamItem Messages // StreamItem Messages
new ProtocolTestData( new ProtocolTestData(
name: "StreamItemWithNoHeadersAndNullItem", name: "StreamItemWithNoHeadersAndNullItem",
message: new StreamItemMessage(invocationId: "xyz", item: null), message: new StreamItemMessage(invocationId: "xyz", item: null),
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", MessagePackObject.Nil),
binary: "lAKAo3h5esA="), binary: "lAKAo3h5esA="),
new ProtocolTestData( new ProtocolTestData(
name: "StreamItemWithNoHeadersAndIntItem", name: "StreamItemWithNoHeadersAndIntItem",
message: new StreamItemMessage(invocationId: "xyz", item: 42), message: new StreamItemMessage(invocationId: "xyz", item: 42),
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", 42),
binary: "lAKAo3h5eio="), binary: "lAKAo3h5eio="),
new ProtocolTestData( new ProtocolTestData(
name: "StreamItemWithNoHeadersAndFloatItem", name: "StreamItemWithNoHeadersAndFloatItem",
message: new StreamItemMessage(invocationId: "xyz", item: 42.0f), message: new StreamItemMessage(invocationId: "xyz", item: 42.0f),
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", 42.0f),
binary: "lAKAo3h5espCKAAA"), binary: "lAKAo3h5espCKAAA"),
new ProtocolTestData( new ProtocolTestData(
name: "StreamItemWithNoHeadersAndStringItem", name: "StreamItemWithNoHeadersAndStringItem",
message: new StreamItemMessage(invocationId: "xyz", item: "string"), message: new StreamItemMessage(invocationId: "xyz", item: "string"),
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", "string"),
binary: "lAKAo3h5eqZzdHJpbmc="), binary: "lAKAo3h5eqZzdHJpbmc="),
new ProtocolTestData( new ProtocolTestData(
name: "StreamItemWithNoHeadersAndBoolItem", name: "StreamItemWithNoHeadersAndBoolItem",
message: new StreamItemMessage(invocationId: "xyz", item: true), message: new StreamItemMessage(invocationId: "xyz", item: true),
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", true),
binary: "lAKAo3h5esM="), binary: "lAKAo3h5esM="),
new ProtocolTestData(
name: "StreamItemWithNoHeadersAndEnumItem",
message: new StreamItemMessage(invocationId: "xyz", item: TestEnum.One),
binary: "lAKAo3h5eqNPbmU="),
new ProtocolTestData( new ProtocolTestData(
name: "StreamItemWithNoHeadersAndCustomObjectItem", name: "StreamItemWithNoHeadersAndCustomObjectItem",
message: new StreamItemMessage(invocationId: "xyz", item: new CustomObject()), message: new StreamItemMessage(invocationId: "xyz", item: new CustomObject()),
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", CustomObjectSerialized), binary: "lAKAo3h5eoaqU3RyaW5nUHJvcKhTaWduYWxSIapEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqrERhdGVUaW1lUHJvcNb/WOwcgKhOdWxsUHJvcMCrQnl0ZUFyclByb3DEAwECAw=="),
binary: "lAKAo3h5eoarQnl0ZUFyclByb3DEAwECA6xEYXRlVGltZVByb3DW/1jsHICqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqhOdWxsUHJvcMCqU3RyaW5nUHJvcKhTaWduYWxSIQ=="),
new ProtocolTestData( new ProtocolTestData(
name: "StreamItemWithNoHeadersAndCustomObjectArrayItem", name: "StreamItemWithNoHeadersAndCustomObjectArrayItem",
message: new StreamItemMessage(invocationId: "xyz", item: new[] { new CustomObject(), new CustomObject() }), message: new StreamItemMessage(invocationId: "xyz", item: new[] { new CustomObject(), new CustomObject() }),
encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", Array(CustomObjectSerialized, CustomObjectSerialized)), binary: "lAKAo3h5epKGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="),
binary: "lAKAo3h5epKGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
new ProtocolTestData( new ProtocolTestData(
name: "StreamItemWithHeadersAndCustomObjectArrayItem", name: "StreamItemWithHeadersAndCustomObjectArrayItem",
message: AddHeaders(TestHeaders, new StreamItemMessage(invocationId: "xyz", item: new[] { new CustomObject(), new CustomObject() })), message: AddHeaders(TestHeaders, new StreamItemMessage(invocationId: "xyz", item: new[] { new CustomObject(), new CustomObject() })),
encoded: Array(HubProtocolConstants.StreamItemMessageType, TestHeadersSerialized, "xyz", Array(CustomObjectSerialized, CustomObjectSerialized)), binary: "lAKDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6koaqU3RyaW5nUHJvcKhTaWduYWxSIapEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqrERhdGVUaW1lUHJvcNb/WOwcgKhOdWxsUHJvcMCrQnl0ZUFyclByb3DEAwECA4aqU3RyaW5nUHJvcKhTaWduYWxSIapEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqrERhdGVUaW1lUHJvcNb/WOwcgKhOdWxsUHJvcMCrQnl0ZUFyclByb3DEAwECAw=="),
binary: "lAKDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6koarQnl0ZUFyclByb3DEAwECA6xEYXRlVGltZVByb3DW/1jsHICqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqhOdWxsUHJvcMCqU3RyaW5nUHJvcKhTaWduYWxSIYarQnl0ZUFyclByb3DEAwECA6xEYXRlVGltZVByb3DW/1jsHICqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqhOdWxsUHJvcMCqU3RyaW5nUHJvcKhTaWduYWxSIQ=="),
// Completion Messages // Completion Messages
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithNoHeadersAndError", name: "CompletionWithNoHeadersAndError",
message: CompletionMessage.WithError(invocationId: "xyz", error: "Error not found!"), message: CompletionMessage.WithError(invocationId: "xyz", error: "Error not found!"),
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 1, "Error not found!"),
binary: "lQOAo3h5egGwRXJyb3Igbm90IGZvdW5kIQ=="), binary: "lQOAo3h5egGwRXJyb3Igbm90IGZvdW5kIQ=="),
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithHeadersAndError", name: "CompletionWithHeadersAndError",
message: AddHeaders(TestHeaders, CompletionMessage.WithError(invocationId: "xyz", error: "Error not found!")), message: AddHeaders(TestHeaders, CompletionMessage.WithError(invocationId: "xyz", error: "Error not found!")),
encoded: Array(HubProtocolConstants.CompletionMessageType, TestHeadersSerialized, "xyz", 1, "Error not found!"),
binary: "lQODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6AbBFcnJvciBub3QgZm91bmQh"), binary: "lQODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6AbBFcnJvciBub3QgZm91bmQh"),
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithNoHeadersAndNoResult", name: "CompletionWithNoHeadersAndNoResult",
message: CompletionMessage.Empty(invocationId: "xyz"), message: CompletionMessage.Empty(invocationId: "xyz"),
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 2),
binary: "lAOAo3h5egI="), binary: "lAOAo3h5egI="),
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithHeadersAndNoResult", name: "CompletionWithHeadersAndNoResult",
message: AddHeaders(TestHeaders, CompletionMessage.Empty(invocationId: "xyz")), message: AddHeaders(TestHeaders, CompletionMessage.Empty(invocationId: "xyz")),
encoded: Array(HubProtocolConstants.CompletionMessageType, TestHeadersSerialized, "xyz", 2),
binary: "lAODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6Ag=="), binary: "lAODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6Ag=="),
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithNoHeadersAndNullResult", name: "CompletionWithNoHeadersAndNullResult",
message: CompletionMessage.WithResult(invocationId: "xyz", payload: null), message: CompletionMessage.WithResult(invocationId: "xyz", payload: null),
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, MessagePackObject.Nil),
binary: "lQOAo3h5egPA"), binary: "lQOAo3h5egPA"),
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithNoHeadersAndIntResult", name: "CompletionWithNoHeadersAndIntResult",
message: CompletionMessage.WithResult(invocationId: "xyz", payload: 42), message: CompletionMessage.WithResult(invocationId: "xyz", payload: 42),
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, 42),
binary: "lQOAo3h5egMq"), binary: "lQOAo3h5egMq"),
new ProtocolTestData(
name: "CompletionWithNoHeadersAndEnumResult",
message: CompletionMessage.WithResult(invocationId: "xyz", payload: TestEnum.One),
binary: "lQOAo3h5egOjT25l"),
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithNoHeadersAndFloatResult", name: "CompletionWithNoHeadersAndFloatResult",
message: CompletionMessage.WithResult(invocationId: "xyz", payload: 42.0f), message: CompletionMessage.WithResult(invocationId: "xyz", payload: 42.0f),
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, 42.0f),
binary: "lQOAo3h5egPKQigAAA=="), binary: "lQOAo3h5egPKQigAAA=="),
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithNoHeadersAndStringResult", name: "CompletionWithNoHeadersAndStringResult",
message: CompletionMessage.WithResult(invocationId: "xyz", payload: "string"), message: CompletionMessage.WithResult(invocationId: "xyz", payload: "string"),
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, "string"),
binary: "lQOAo3h5egOmc3RyaW5n"), binary: "lQOAo3h5egOmc3RyaW5n"),
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithNoHeadersAndBooleanResult", name: "CompletionWithNoHeadersAndBooleanResult",
message: CompletionMessage.WithResult(invocationId: "xyz", payload: true), message: CompletionMessage.WithResult(invocationId: "xyz", payload: true),
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, true),
binary: "lQOAo3h5egPD"), binary: "lQOAo3h5egPD"),
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithNoHeadersAndCustomObjectResult", name: "CompletionWithNoHeadersAndCustomObjectResult",
message: CompletionMessage.WithResult(invocationId: "xyz", payload: new CustomObject()), message: CompletionMessage.WithResult(invocationId: "xyz", payload: new CustomObject()),
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, CustomObjectSerialized), binary: "lQOAo3h5egOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="),
binary: "lQOAo3h5egOGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithNoHeadersAndCustomObjectArrayResult", name: "CompletionWithNoHeadersAndCustomObjectArrayResult",
message: CompletionMessage.WithResult(invocationId: "xyz", payload: new[] { new CustomObject(), new CustomObject() }), message: CompletionMessage.WithResult(invocationId: "xyz", payload: new[] { new CustomObject(), new CustomObject() }),
encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, Array(CustomObjectSerialized, CustomObjectSerialized)), binary: "lQOAo3h5egOShqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQIDhqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQID"),
binary: "lQOAo3h5egOShqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIhhqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIh"),
new ProtocolTestData( new ProtocolTestData(
name: "CompletionWithHeadersAndCustomObjectArrayResult", name: "CompletionWithHeadersAndCustomObjectArrayResult",
message: AddHeaders(TestHeaders, CompletionMessage.WithResult(invocationId: "xyz", payload: new[] { new CustomObject(), new CustomObject() })), message: AddHeaders(TestHeaders, CompletionMessage.WithResult(invocationId: "xyz", payload: new[] { new CustomObject(), new CustomObject() })),
encoded: Array(HubProtocolConstants.CompletionMessageType, TestHeadersSerialized, "xyz", 3, Array(CustomObjectSerialized, CustomObjectSerialized)), binary: "lQODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6A5KGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="),
binary: "lQODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6A5KGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
// StreamInvocation Messages // StreamInvocation Messages
new ProtocolTestData( new ProtocolTestData(
name: "StreamInvocationWithNoHeadersAndNoArgs", name: "StreamInvocationWithNoHeadersAndNoArgs",
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null), message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null),
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array()),
binary: "lQSAo3h5eqZtZXRob2SQ"), binary: "lQSAo3h5eqZtZXRob2SQ"),
new ProtocolTestData( new ProtocolTestData(
name: "StreamInvocationWithNoHeadersAndNullArg", name: "StreamInvocationWithNoHeadersAndNullArg",
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, new object[] { null }), message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, new object[] { null }),
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(MessagePackObject.Nil)),
binary: "lQSAo3h5eqZtZXRob2SRwA=="), binary: "lQSAo3h5eqZtZXRob2SRwA=="),
new ProtocolTestData( new ProtocolTestData(
name: "StreamInvocationWithNoHeadersAndIntArg", name: "StreamInvocationWithNoHeadersAndIntArg",
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, 42), message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, 42),
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(42)),
binary: "lQSAo3h5eqZtZXRob2SRKg=="), binary: "lQSAo3h5eqZtZXRob2SRKg=="),
new ProtocolTestData(
name: "StreamInvocationWithNoHeadersAndEnumArg",
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, TestEnum.One),
binary: "lQSAo3h5eqZtZXRob2SRo09uZQ=="),
new ProtocolTestData( new ProtocolTestData(
name: "StreamInvocationWithNoHeadersAndIntAndStringArgs", name: "StreamInvocationWithNoHeadersAndIntAndStringArgs",
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, 42, "string"), message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, 42, "string"),
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(42, "string")),
binary: "lQSAo3h5eqZtZXRob2SSKqZzdHJpbmc="), binary: "lQSAo3h5eqZtZXRob2SSKqZzdHJpbmc="),
new ProtocolTestData( new ProtocolTestData(
name: "StreamInvocationWithNoHeadersAndIntStringAndCustomObjectArgs", name: "StreamInvocationWithNoHeadersAndIntStringAndCustomObjectArgs",
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, 42, "string", new CustomObject()), message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, 42, "string", new CustomObject()),
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(42, "string", CustomObjectSerialized)), binary: "lQSAo3h5eqZtZXRob2STKqZzdHJpbmeGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="),
binary: "lQSAo3h5eqZtZXRob2STKqZzdHJpbmeGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
new ProtocolTestData( new ProtocolTestData(
name: "StreamInvocationWithNoHeadersAndCustomObjectArrayArg", name: "StreamInvocationWithNoHeadersAndCustomObjectArrayArg",
message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() }), message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() }),
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(CustomObjectSerialized, CustomObjectSerialized)), binary: "lQSAo3h5eqZtZXRob2SShqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQIDhqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQID"),
binary: "lQSAo3h5eqZtZXRob2SShqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIhhqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIh"),
new ProtocolTestData( new ProtocolTestData(
name: "StreamInvocationWithHeadersAndCustomObjectArrayArg", name: "StreamInvocationWithHeadersAndCustomObjectArrayArg",
message: AddHeaders(TestHeaders, new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() })), message: AddHeaders(TestHeaders, new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() })),
encoded: Array(HubProtocolConstants.StreamInvocationMessageType, TestHeadersSerialized, "xyz", "method", Array(CustomObjectSerialized, CustomObjectSerialized)), binary: "lQSDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6pm1ldGhvZJKGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="),
binary: "lQSDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6pm1ldGhvZJKGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="),
// CancelInvocation Messages // CancelInvocation Messages
new ProtocolTestData( new ProtocolTestData(
name: "CancelInvocationWithNoHeaders", name: "CancelInvocationWithNoHeaders",
message: new CancelInvocationMessage(invocationId: "xyz"), message: new CancelInvocationMessage(invocationId: "xyz"),
encoded: Array(HubProtocolConstants.CancelInvocationMessageType, Map(), "xyz"),
binary: "kwWAo3h5eg=="), binary: "kwWAo3h5eg=="),
new ProtocolTestData( new ProtocolTestData(
name: "CancelInvocationWithHeaders", name: "CancelInvocationWithHeaders",
message: AddHeaders(TestHeaders, new CancelInvocationMessage(invocationId: "xyz")), message: AddHeaders(TestHeaders, new CancelInvocationMessage(invocationId: "xyz")),
encoded: Array(HubProtocolConstants.CancelInvocationMessageType, TestHeadersSerialized, "xyz"),
binary: "kwWDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6"), binary: "kwWDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6"),
// Ping Messages // Ping Messages
new ProtocolTestData( new ProtocolTestData(
name: "Ping", name: "Ping",
message: PingMessage.Instance, message: PingMessage.Instance,
encoded: Array(HubProtocolConstants.PingMessageType),
binary: "kQY="), binary: "kQY="),
}.ToDictionary(t => t.Name); }.ToDictionary(t => t.Name);
@ -282,14 +244,11 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
// Verify that the input binary string decodes to the expected MsgPack primitives // Verify that the input binary string decodes to the expected MsgPack primitives
var bytes = Convert.FromBase64String(testData.Binary); var bytes = Convert.FromBase64String(testData.Binary);
var obj = Unpack(bytes);
Assert.Equal(testData.Encoded, obj);
// Parse the input fully now. // Parse the input fully now.
bytes = Frame(bytes); bytes = Frame(bytes);
var protocol = new MessagePackHubProtocol();
var data = new ReadOnlySequence<byte>(bytes); var data = new ReadOnlySequence<byte>(bytes);
Assert.True(protocol.TryParseMessage(ref data, new TestBinder(testData.Message), out var message)); Assert.True(_hubProtocol.TryParseMessage(ref data, new TestBinder(testData.Message), out var message));
Assert.NotNull(message); Assert.NotNull(message);
Assert.Equal(testData.Message, message, TestHubMessageEqualityComparer.Instance); Assert.Equal(testData.Message, message, TestHubMessageEqualityComparer.Instance);
@ -299,19 +258,14 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
public void ParseMessageWithExtraData() public void ParseMessageWithExtraData()
{ {
var expectedMessage = new InvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null); var expectedMessage = new InvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null);
var encodedObj = Array(HubProtocolConstants.InvocationMessageType, Map(), "xyz", "method", Array(), "ex");
var binary = "lgGAo3h5eqZtZXRob2SQomV4";
// Verify that the input binary string decodes to the expected MsgPack primitives // Verify that the input binary string decodes to the expected MsgPack primitives
var bytes = Convert.FromBase64String(binary); var bytes = new byte[] { Array(6), 1, 0x80, String(3), (byte)'x', (byte)'y', (byte)'z', String(6), (byte)'m', (byte)'e', (byte)'t', (byte)'h', (byte)'o', (byte)'d', Array(0), String(2), (byte)'e', (byte)'x' };
var obj = Unpack(bytes);
Assert.Equal(encodedObj, obj);
// Parse the input fully now. // Parse the input fully now.
bytes = Frame(bytes); bytes = Frame(bytes);
var protocol = new MessagePackHubProtocol();
var data = new ReadOnlySequence<byte>(bytes); var data = new ReadOnlySequence<byte>(bytes);
Assert.True(protocol.TryParseMessage(ref data, new TestBinder(expectedMessage), out var message)); Assert.True(_hubProtocol.TryParseMessage(ref data, new TestBinder(expectedMessage), out var message));
Assert.NotNull(message); Assert.NotNull(message);
Assert.Equal(expectedMessage, message, TestHubMessageEqualityComparer.Instance); Assert.Equal(expectedMessage, message, TestHubMessageEqualityComparer.Instance);
@ -324,7 +278,6 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
var testData = TestData[testDataName]; var testData = TestData[testDataName];
var bytes = Write(testData.Message); var bytes = Write(testData.Message);
AssertMessages(testData.Encoded, bytes);
// Unframe the message to check the binary encoding // Unframe the message to check the binary encoding
var byteSpan = new ReadOnlySequence<byte>(bytes); var byteSpan = new ReadOnlySequence<byte>(bytes);
@ -335,45 +288,94 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
Assert.True(string.Equals(actual, testData.Binary, StringComparison.Ordinal), $"Binary encoding changed from{Environment.NewLine} [{testData.Binary}]{Environment.NewLine} to{Environment.NewLine} [{actual}]{Environment.NewLine}Please verify the MsgPack output and update the baseline"); Assert.True(string.Equals(actual, testData.Binary, StringComparison.Ordinal), $"Binary encoding changed from{Environment.NewLine} [{testData.Binary}]{Environment.NewLine} to{Environment.NewLine} [{actual}]{Environment.NewLine}Please verify the MsgPack output and update the baseline");
} }
[Fact]
public void WriteAndParseDateTimeConvertsToUTC()
{
var dateTime = new DateTime(2018, 4, 9);
var writer = MemoryBufferWriter.Get();
try
{
_hubProtocol.WriteMessage(CompletionMessage.WithResult("xyz", dateTime), writer);
var bytes = new ReadOnlySequence<byte>(writer.ToArray());
_hubProtocol.TryParseMessage(ref bytes, new TestBinder(typeof(DateTime)), out var hubMessage);
var completionMessage = Assert.IsType<CompletionMessage>(hubMessage);
var resultDateTime = (DateTime)completionMessage.Result;
// The messagepack Timestamp format specifies that time is stored as seconds since 1970-01-01 UTC
// so the library has no choice but to store the time as UTC
// https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
Assert.Equal(dateTime.ToUniversalTime(), resultDateTime);
}
finally
{
MemoryBufferWriter.Return(writer);
}
}
[Fact]
public void WriteAndParseDateTimeOffset()
{
var dateTimeOffset = new DateTimeOffset(new DateTime(2018, 4, 9), TimeSpan.FromHours(10));
var writer = MemoryBufferWriter.Get();
try
{
_hubProtocol.WriteMessage(CompletionMessage.WithResult("xyz", dateTimeOffset), writer);
var bytes = new ReadOnlySequence<byte>(writer.ToArray());
_hubProtocol.TryParseMessage(ref bytes, new TestBinder(typeof(DateTimeOffset)), out var hubMessage);
var completionMessage = Assert.IsType<CompletionMessage>(hubMessage);
var resultDateTimeOffset = (DateTimeOffset)completionMessage.Result;
Assert.Equal(dateTimeOffset, resultDateTimeOffset);
}
finally
{
MemoryBufferWriter.Return(writer);
}
}
public static IDictionary<string, InvalidMessageData> InvalidPayloads => new[] public static IDictionary<string, InvalidMessageData> InvalidPayloads => new[]
{ {
// Message Type // Message Type
new InvalidMessageData("MessageTypeString", Array("foo"), "Reading 'messageType' as Int32 failed."), new InvalidMessageData("MessageTypeString", new byte[] { 0x91, 0xa3, (byte)'f', (byte)'o', (byte)'o' }, "Reading 'messageType' as Int32 failed."),
// Headers // Headers
new InvalidMessageData("HeadersNotAMap", Array(HubProtocolConstants.InvocationMessageType, "foo"), "Reading map length for 'headers' failed."), new InvalidMessageData("HeadersNotAMap", new byte[] { 0x92, 1, 0xa3, (byte)'f', (byte)'o', (byte)'o' }, "Reading map length for 'headers' failed."),
new InvalidMessageData("HeaderKeyInt", Array(HubProtocolConstants.InvocationMessageType, Map((42, "foo"))), "Reading 'headers[0].Key' as String failed."), new InvalidMessageData("HeaderKeyInt", new byte[] { 0x92, 1, 0x82, 0x2a, 0xa3, (byte)'f', (byte)'o', (byte)'o' }, "Reading 'headers[0].Key' as String failed."),
new InvalidMessageData("HeaderValueInt", Array(HubProtocolConstants.InvocationMessageType, Map(("foo", 42))), "Reading 'headers[0].Value' as String failed."), new InvalidMessageData("HeaderValueInt", new byte[] { 0x92, 1, 0x82, 0xa3, (byte)'f', (byte)'o', (byte)'o', 42 }, "Reading 'headers[0].Value' as String failed."),
new InvalidMessageData("HeaderKeyArray", Array(HubProtocolConstants.InvocationMessageType, Map(("biz", "boz"), (Array(), "foo"))), "Reading 'headers[1].Key' as String failed."), new InvalidMessageData("HeaderKeyArray", new byte[] { 0x92, 1, 0x84, 0xa3, (byte)'f', (byte)'o', (byte)'o', 0xa3, (byte)'f', (byte)'o', (byte)'o', 0x90, 0xa3, (byte)'f', (byte)'o', (byte)'o' }, "Reading 'headers[1].Key' as String failed."),
new InvalidMessageData("HeaderValueArray", Array(HubProtocolConstants.InvocationMessageType, Map(("biz", "boz"), ("foo", Array()))), "Reading 'headers[1].Value' as String failed."), new InvalidMessageData("HeaderValueArray", new byte[] { 0x92, 1, 0x84, 0xa3, (byte)'f', (byte)'o', (byte)'o', 0xa3, (byte)'f', (byte)'o', (byte)'o', 0xa3, (byte)'f', (byte)'o', (byte)'o', 0x90 }, "Reading 'headers[1].Value' as String failed."),
// InvocationMessage // InvocationMessage
new InvalidMessageData("InvocationMissingId", Array(HubProtocolConstants.InvocationMessageType, Map()), "Reading 'invocationId' as String failed."), new InvalidMessageData("InvocationMissingId", new byte[] { 0x92, 1, 0x80 }, "Reading 'invocationId' as String failed."),
new InvalidMessageData("InvocationIdBoolean", Array(HubProtocolConstants.InvocationMessageType, Map(), false), "Reading 'invocationId' as String failed."), new InvalidMessageData("InvocationIdBoolean", new byte[] { 0x91, 1, 0x80, 0xc2 }, "Reading 'invocationId' as String failed."),
new InvalidMessageData("InvocationTargetMissing", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc"), "Reading 'target' as String failed."), new InvalidMessageData("InvocationTargetMissing", new byte[] { 0x93, 1, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c' }, "Reading 'target' as String failed."),
new InvalidMessageData("InvocationTargetInt", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", 42), "Reading 'target' as String failed."), new InvalidMessageData("InvocationTargetInt", new byte[] { 0x94, 1, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 42 }, "Reading 'target' as String failed."),
// StreamInvocationMessage // StreamInvocationMessage
new InvalidMessageData("StreamInvocationMissingId", Array(HubProtocolConstants.StreamInvocationMessageType, Map()), "Reading 'invocationId' as String failed."), new InvalidMessageData("StreamInvocationMissingId", new byte[] { 0x92, 4, 0x80 }, "Reading 'invocationId' as String failed."),
new InvalidMessageData("StreamInvocationIdBoolean", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), false), "Reading 'invocationId' as String failed."), new InvalidMessageData("StreamInvocationIdBoolean", new byte[] { 0x93, 4, 0x80, 0xc2 }, "Reading 'invocationId' as String failed."),
new InvalidMessageData("StreamInvocationTargetMissing", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc"), "Reading 'target' as String failed."), new InvalidMessageData("StreamInvocationTargetMissing", new byte[] { 0x93, 4, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c' }, "Reading 'target' as String failed."),
new InvalidMessageData("StreamInvocationTargetInt", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", 42), "Reading 'target' as String failed."), new InvalidMessageData("StreamInvocationTargetInt", new byte[] { 0x94, 4, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 42 }, "Reading 'target' as String failed."),
// StreamItemMessage // StreamItemMessage
new InvalidMessageData("StreamItemMissingId", Array(HubProtocolConstants.StreamItemMessageType, Map()), "Reading 'invocationId' as String failed."), new InvalidMessageData("StreamItemMissingId", new byte[] { 0x92, 2, 0x80 }, "Reading 'invocationId' as String failed."),
new InvalidMessageData("StreamItemInvocationIdBoolean", Array(HubProtocolConstants.StreamItemMessageType, Map(), false), "Reading 'invocationId' as String failed."), new InvalidMessageData("StreamItemInvocationIdBoolean", new byte[] { 0x93, 2, 0x80, 0xc2 }, "Reading 'invocationId' as String failed."),
new InvalidMessageData("StreamItemMissing", Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz"), "Deserializing object of the `String` type for 'item' failed."), new InvalidMessageData("StreamItemMissing", new byte[] { 0x93, 2, 0x80, 0xa3, (byte)'x', (byte)'y', (byte)'z' }, "Deserializing object of the `String` type for 'item' failed."),
new InvalidMessageData("StreamItemTypeMismatch", Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", 42), "Deserializing object of the `String` type for 'item' failed."), new InvalidMessageData("StreamItemTypeMismatch", new byte[] { 0x94, 2, 0x80, 0xa3, (byte)'x', (byte)'y', (byte)'z', 42 }, "Deserializing object of the `String` type for 'item' failed."),
// CompletionMessage // CompletionMessage
new InvalidMessageData("CompletionMissingId", Array(HubProtocolConstants.CompletionMessageType, Map()), "Reading 'invocationId' as String failed."), new InvalidMessageData("CompletionMissingId", new byte[] { 0x92, 3, 0x80 }, "Reading 'invocationId' as String failed."),
new InvalidMessageData("CompletionIdBoolean", Array(HubProtocolConstants.CompletionMessageType, Map(), false), "Reading 'invocationId' as String failed."), new InvalidMessageData("CompletionIdBoolean", new byte[] { 0x93, 3, 0x80, 0xc2 }, "Reading 'invocationId' as String failed."),
new InvalidMessageData("CompletionResultKindString", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", "abc"), "Reading 'resultKind' as Int32 failed."), new InvalidMessageData("CompletionResultKindString", new byte[] { 0x94, 3, 0x80, 0xa3, (byte)'x', (byte)'y', (byte)'z', 0xa3, (byte)'x', (byte)'y', (byte)'z' }, "Reading 'resultKind' as Int32 failed."),
new InvalidMessageData("CompletionResultKindOutOfRange", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 42), "Invalid invocation result kind."), new InvalidMessageData("CompletionResultKindOutOfRange", new byte[] { 0x94, 3, 0x80, 0xa3, (byte)'x', (byte)'y', (byte)'z', 42 }, "Invalid invocation result kind."),
new InvalidMessageData("CompletionErrorMissing", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 1), "Reading 'error' as String failed."), new InvalidMessageData("CompletionErrorMissing", new byte[] { 0x94, 3, 0x80, 0xa3, (byte)'x', (byte)'y', (byte)'z', 0x01 }, "Reading 'error' as String failed."),
new InvalidMessageData("CompletionErrorInt", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 1, 42), "Reading 'error' as String failed."), new InvalidMessageData("CompletionErrorInt", new byte[] { 0x95, 3, 0x80, 0xa3, (byte)'x', (byte)'y', (byte)'z', 0x01, 42 }, "Reading 'error' as String failed."),
new InvalidMessageData("CompletionResultMissing", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3), "Deserializing object of the `String` type for 'argument' failed."), new InvalidMessageData("CompletionResultMissing", new byte[] { 0x94, 3, 0x80, 0xa3, (byte)'x', (byte)'y', (byte)'z', 0x03 }, "Deserializing object of the `String` type for 'argument' failed."),
new InvalidMessageData("CompletionResultTypeMismatch", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, 42), "Deserializing object of the `String` type for 'argument' failed."), new InvalidMessageData("CompletionResultTypeMismatch", new byte[] { 0x95, 3, 0x80, 0xa3, (byte)'x', (byte)'y', (byte)'z', 0x03, 42 }, "Deserializing object of the `String` type for 'argument' failed."),
}.ToDictionary(t => t.Name); }.ToDictionary(t => t.Name);
public static IEnumerable<object[]> InvalidPayloadNames => InvalidPayloads.Keys.Select(name => new object[] { name }); public static IEnumerable<object[]> InvalidPayloadNames => InvalidPayloads.Keys.Select(name => new object[] { name });
@ -384,7 +386,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
{ {
var testData = InvalidPayloads[invalidPayloadName]; var testData = InvalidPayloads[invalidPayloadName];
var buffer = Frame(Pack(testData.Encoded)); var buffer = Frame(testData.Encoded);
var binder = new TestBinder(new[] { typeof(string) }, typeof(string)); var binder = new TestBinder(new[] { typeof(string) }, typeof(string));
var data = new ReadOnlySequence<byte>(buffer); var data = new ReadOnlySequence<byte>(buffer);
var exception = Assert.Throws<InvalidDataException>(() => _hubProtocol.TryParseMessage(ref data, binder, out _)); var exception = Assert.Throws<InvalidDataException>(() => _hubProtocol.TryParseMessage(ref data, binder, out _));
@ -395,18 +397,18 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
public static IDictionary<string, InvalidMessageData> ArgumentBindingErrors => new[] public static IDictionary<string, InvalidMessageData> ArgumentBindingErrors => new[]
{ {
// InvocationMessage // InvocationMessage
new InvalidMessageData("InvocationArgumentArrayMissing", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz"), "Reading array length for 'arguments' failed."), new InvalidMessageData("InvocationArgumentArrayMissing", new byte[] { 0x94, 1, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 0xa3, (byte)'x', (byte)'y', (byte)'z' }, "Reading array length for 'arguments' failed."),
new InvalidMessageData("InvocationArgumentArrayNotAnArray", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz", 42), "Reading array length for 'arguments' failed."), new InvalidMessageData("InvocationArgumentArrayNotAnArray", new byte[] { 0x95, 1, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 0xa3, (byte)'x', (byte)'y', (byte)'z', 42 }, "Reading array length for 'arguments' failed."),
new InvalidMessageData("InvocationArgumentArraySizeMismatchEmpty", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz", Array()), "Invocation provides 0 argument(s) but target expects 1."), new InvalidMessageData("InvocationArgumentArraySizeMismatchEmpty", new byte[] { 0x95, 1, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 0xa3, (byte)'x', (byte)'y', (byte)'z', 0x90 }, "Invocation provides 0 argument(s) but target expects 1."),
new InvalidMessageData("InvocationArgumentArraySizeMismatchTooLarge", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz", Array("a", "b")), "Invocation provides 2 argument(s) but target expects 1."), new InvalidMessageData("InvocationArgumentArraySizeMismatchTooLarge", new byte[] { 0x95, 1, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 0xa3, (byte)'x', (byte)'y', (byte)'z', 0x92, 0xa1, (byte)'a', 0xa1, (byte)'b' }, "Invocation provides 2 argument(s) but target expects 1."),
new InvalidMessageData("InvocationArgumentTypeMismatch", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz", Array(42)), "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked."), new InvalidMessageData("InvocationArgumentTypeMismatch", new byte[] { 0x95, 1, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 0xa3, (byte)'x', (byte)'y', (byte)'z', 0x91, 42 }, "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked."),
// StreamInvocationMessage // StreamInvocationMessage
new InvalidMessageData("StreamInvocationArgumentArrayMissing", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz"), "Reading array length for 'arguments' failed."), // array is missing new InvalidMessageData("StreamInvocationArgumentArrayMissing", new byte[] { 0x94, 4, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 0xa3, (byte)'x', (byte)'y', (byte)'z' }, "Reading array length for 'arguments' failed."), // array is missing
new InvalidMessageData("StreamInvocationArgumentArrayNotAnArray", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz", 42), "Reading array length for 'arguments' failed."), // arguments isn't an array new InvalidMessageData("StreamInvocationArgumentArrayNotAnArray", new byte[] { 0x95, 4, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 0xa3, (byte)'x', (byte)'y', (byte)'z', 42 }, "Reading array length for 'arguments' failed."), // arguments isn't an array
new InvalidMessageData("StreamInvocationArgumentArraySizeMismatchEmpty", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz", Array()), "Invocation provides 0 argument(s) but target expects 1."), // array is missing elements new InvalidMessageData("StreamInvocationArgumentArraySizeMismatchEmpty", new byte[] { 0x95, 4, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 0xa3, (byte)'x', (byte)'y', (byte)'z', 0x90 }, "Invocation provides 0 argument(s) but target expects 1."), // array is missing elements
new InvalidMessageData("StreamInvocationArgumentArraySizeMismatchTooLarge", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz", Array("a", "b")), "Invocation provides 2 argument(s) but target expects 1."), // argument count does not match binder argument count new InvalidMessageData("StreamInvocationArgumentArraySizeMismatchTooLarge", new byte[] { 0x95, 4, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 0xa3, (byte)'x', (byte)'y', (byte)'z', 0x92, 0xa1, (byte)'a', 0xa1, (byte)'b' }, "Invocation provides 2 argument(s) but target expects 1."), // argument count does not match binder argument count
new InvalidMessageData("StreamInvocationArgumentTypeMismatch", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz", Array(42)), "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked."), // argument type mismatch new InvalidMessageData("StreamInvocationArgumentTypeMismatch", new byte[] { 0x95, 4, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 0xa3, (byte)'x', (byte)'y', (byte)'z', 0x91, 42 }, "Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked."), // argument type mismatch
}.ToDictionary(t => t.Name); }.ToDictionary(t => t.Name);
public static IEnumerable<object[]> ArgumentBindingErrorNames => ArgumentBindingErrors.Keys.Select(name => new object[] { name }); public static IEnumerable<object[]> ArgumentBindingErrorNames => ArgumentBindingErrors.Keys.Select(name => new object[] { name });
@ -417,7 +419,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
{ {
var testData = ArgumentBindingErrors[argumentBindingErrorName]; var testData = ArgumentBindingErrors[argumentBindingErrorName];
var buffer = Frame(Pack(testData.Encoded)); var buffer = Frame(testData.Encoded);
var binder = new TestBinder(new[] { typeof(string) }, typeof(string)); var binder = new TestBinder(new[] { typeof(string) }, typeof(string));
var data = new ReadOnlySequence<byte>(buffer); var data = new ReadOnlySequence<byte>(buffer);
_hubProtocol.TryParseMessage(ref data, binder, out var message); _hubProtocol.TryParseMessage(ref data, binder, out var message);
@ -440,15 +442,28 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
public void SerializerCanSerializeTypesWithNoDefaultCtor() public void SerializerCanSerializeTypesWithNoDefaultCtor()
{ {
var result = Write(CompletionMessage.WithResult("0", new List<int> { 42 }.AsReadOnly())); var result = Write(CompletionMessage.WithResult("0", new List<int> { 42 }.AsReadOnly()));
AssertMessages(Array(HubProtocolConstants.CompletionMessageType, Map(), "0", 3, Array(42)), result); AssertMessages(new byte[] { Array(5), 3, 0x80, String(1), (byte)'0', 0x03, Array(1), 42 }, result);
} }
private static void AssertMessages(MessagePackObject expectedOutput, byte[] bytes) private byte Array(int size)
{
Debug.Assert(size < 16, "Test code doesn't support array sizes greater than 15");
return (byte)(0x90 | size);
}
private byte String(int size)
{
Debug.Assert(size < 16, "Test code doesn't support string sizes greater than 15");
return (byte)(0xa0 | size);
}
private static void AssertMessages(byte[] expectedOutput, ReadOnlyMemory<byte> bytes)
{ {
var data = new ReadOnlySequence<byte>(bytes); var data = new ReadOnlySequence<byte>(bytes);
Assert.True(BinaryMessageParser.TryParseMessage(ref data, out var message)); Assert.True(BinaryMessageParser.TryParseMessage(ref data, out var message));
var obj = Unpack(message.ToArray()); Assert.Equal(expectedOutput, message.ToArray());
Assert.Equal(expectedOutput, obj);
} }
private static byte[] Frame(byte[] input) private static byte[] Frame(byte[] input)
@ -466,44 +481,12 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
} }
} }
private static MessagePackObject Unpack(byte[] input)
{
using (var stream = new MemoryStream(input))
{
using (var unpacker = Unpacker.Create(stream))
{
Assert.True(unpacker.ReadObject(out var obj));
return obj;
}
}
}
private static byte[] Pack(MessagePackObject input)
{
var options = new PackingOptions()
{
StringEncoding = Encoding.UTF8
};
using (var stream = new MemoryStream())
{
using (var packer = Packer.Create(stream))
{
input.PackToMessage(packer, options);
packer.Flush();
}
stream.Flush();
return stream.ToArray();
}
}
private static byte[] Write(HubMessage message) private static byte[] Write(HubMessage message)
{ {
var protocol = new MessagePackHubProtocol();
var writer = MemoryBufferWriter.Get(); var writer = MemoryBufferWriter.Get();
try try
{ {
protocol.WriteMessage(message, writer); _hubProtocol.WriteMessage(message, writer);
return writer.ToArray(); return writer.ToArray();
} }
finally finally
@ -515,10 +498,10 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
public class InvalidMessageData public class InvalidMessageData
{ {
public string Name { get; private set; } public string Name { get; private set; }
public MessagePackObject Encoded { get; private set; } public byte[] Encoded { get; private set; }
public string ErrorMessage { get; private set; } public string ErrorMessage { get; private set; }
public InvalidMessageData(string name, MessagePackObject encoded, string errorMessage) public InvalidMessageData(string name, byte[] encoded, string errorMessage)
{ {
Name = name; Name = name;
Encoded = encoded; Encoded = encoded;
@ -532,14 +515,12 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol
{ {
public string Name { get; } public string Name { get; }
public string Binary { get; } public string Binary { get; }
public MessagePackObject Encoded { get; }
public HubMessage Message { get; } public HubMessage Message { get; }
public ProtocolTestData(string name, HubMessage message, MessagePackObject encoded, string binary) public ProtocolTestData(string name, HubMessage message, string binary)
{ {
Name = name; Name = name;
Message = message; Message = message;
Encoded = encoded;
Binary = binary; Binary = binary;
} }

View File

@ -11,7 +11,6 @@ using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Moq; using Moq;
using MsgPack.Serialization;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using Xunit; using Xunit;
@ -506,7 +505,6 @@ namespace Microsoft.AspNetCore.SignalR.Redis.Tests
var server = new TestRedisServer(); var server = new TestRedisServer();
var messagePackOptions = new MessagePackHubProtocolOptions(); var messagePackOptions = new MessagePackHubProtocolOptions();
messagePackOptions.SerializationContext.DictionarySerlaizationOptions.KeyTransformer = DictionaryKeyTransformers.LowerCamel;
var jsonOptions = new JsonHubProtocolOptions(); var jsonOptions = new JsonHubProtocolOptions();
jsonOptions.PayloadSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); jsonOptions.PayloadSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

View File

@ -8,6 +8,8 @@ using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MessagePack;
using MessagePack.Formatters;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Http.Connections;
@ -16,8 +18,6 @@ using Microsoft.AspNetCore.SignalR.Internal.Protocol;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Moq; using Moq;
using MsgPack;
using MsgPack.Serialization;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
@ -1679,7 +1679,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests
services.AddSignalR() services.AddSignalR()
.AddMessagePackProtocol(options => .AddMessagePackProtocol(options =>
{ {
options.SerializationContext.SerializationMethod = SerializationMethod.Array; options.FormatterResolvers.Insert(0, new CustomFormatter());
}); });
}); });
@ -1697,10 +1697,9 @@ namespace Microsoft.AspNetCore.SignalR.Tests
var message = Assert.IsType<InvocationMessage>(await client.ReadAsync().OrTimeout()); var message = Assert.IsType<InvocationMessage>(await client.ReadAsync().OrTimeout());
var msgPackObject = Assert.IsType<MessagePackObject>(message.Arguments[0]); var result = message.Arguments[0] as Dictionary<object, object>;
// Custom serialization - object was serialized as an array and not a map Assert.Equal("formattedString", result["Message"]);
Assert.True(msgPackObject.IsArray); Assert.Equal("formattedString", result["paramName"]);
Assert.Equal(new[] { "test", "param" }, ((MessagePackObject[])msgPackObject.ToObject()).Select(o => o.AsString()));
client.Dispose(); client.Dispose();
@ -1708,6 +1707,50 @@ namespace Microsoft.AspNetCore.SignalR.Tests
} }
} }
private class CustomFormatter : IFormatterResolver
{
public IMessagePackFormatter<T> GetFormatter<T>()
{
if (typeof(T) == typeof(string))
{
return new StringFormatter<T>();
}
return null;
}
private class StringFormatter<T> : IMessagePackFormatter<T>
{
public T Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize)
{
// this method isn't used in our tests
readSize = 0;
return default;
}
public int Serialize(ref byte[] bytes, int offset, T value, IFormatterResolver formatterResolver)
{
// string of size 15
bytes[offset] = 0xAF;
bytes[offset + 1] = (byte)'f';
bytes[offset + 2] = (byte)'o';
bytes[offset + 3] = (byte)'r';
bytes[offset + 4] = (byte)'m';
bytes[offset + 5] = (byte)'a';
bytes[offset + 6] = (byte)'t';
bytes[offset + 7] = (byte)'t';
bytes[offset + 8] = (byte)'e';
bytes[offset + 9] = (byte)'d';
bytes[offset + 10] = (byte)'S';
bytes[offset + 11] = (byte)'t';
bytes[offset + 12] = (byte)'r';
bytes[offset + 13] = (byte)'i';
bytes[offset + 14] = (byte)'n';
bytes[offset + 15] = (byte)'g';
return 16;
}
}
}
[Fact] [Fact]
public async Task CanGetHttpContextFromHubConnectionContext() public async Task CanGetHttpContextFromHubConnectionContext()
{ {