From 6d050140e5fa572014f6865fec739df3339fc1e0 Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Mon, 9 Apr 2018 17:04:32 -0700 Subject: [PATCH] Swtich to MessagePack-CSharp (#1879) --- build/dependencies.props | 2 +- clients/ts/FunctionalTests/ComplexObject.cs | 1 + clients/ts/FunctionalTests/TestHub.cs | 12 + .../FunctionalTests/ts/HubConnectionTests.ts | 52 ++- .../package-lock.json | 41 +- .../ts/signalr-protocol-msgpack/package.json | 2 +- samples/SignalRSamples/Startup.cs | 6 +- .../Protocol/MessagePackHubProtocol.cs | 392 +++++++++++------- .../MessagePackHubProtocolOptions.cs | 5 +- ...spNetCore.SignalR.Protocols.MsgPack.csproj | 2 +- .../HubConnectionBuilderTests.cs | 21 +- .../Internal/Protocol/CustomObject.cs | 2 +- .../Internal/Protocol/JsonHubProtocolTests.cs | 38 +- .../Internal/Protocol/MessagePackHelpers.cs | 15 - .../Protocol/MessagePackHubProtocolTests.cs | 315 +++++++------- .../RedisHubLifetimeManagerTests.cs | 2 - .../HubConnectionHandlerTests.cs | 57 ++- 17 files changed, 540 insertions(+), 425 deletions(-) delete mode 100644 test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHelpers.cs diff --git a/build/dependencies.props b/build/dependencies.props index 2be31c69e3..a73b8b05b6 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -57,7 +57,7 @@ 2.1.0-preview2-26403-06 15.6.1 4.7.49 - 1.0.0-rc + 1.7.3.4 11.0.2 1.2.4 4.5.0-preview2-26403-05 diff --git a/clients/ts/FunctionalTests/ComplexObject.cs b/clients/ts/FunctionalTests/ComplexObject.cs index 5d471e35e0..d830114fe1 100644 --- a/clients/ts/FunctionalTests/ComplexObject.cs +++ b/clients/ts/FunctionalTests/ComplexObject.cs @@ -11,5 +11,6 @@ namespace FunctionalTests public int[] IntArray { get; set; } public byte[] ByteArray { get; set; } public Guid GUID { get; set; } + public DateTime DateTime { get;set; } } } diff --git a/clients/ts/FunctionalTests/TestHub.cs b/clients/ts/FunctionalTests/TestHub.cs index e555408983..c996916277 100644 --- a/clients/ts/FunctionalTests/TestHub.cs +++ b/clients/ts/FunctionalTests/TestHub.cs @@ -70,5 +70,17 @@ namespace FunctionalTests { 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", + }; + } } } diff --git a/clients/ts/FunctionalTests/ts/HubConnectionTests.ts b/clients/ts/FunctionalTests/ts/HubConnectionTests.ts index 6f6bcc8a4d..4f6d82cf9c 100644 --- a/clients/ts/FunctionalTests/ts/HubConnectionTests.ts +++ b/clients/ts/FunctionalTests/ts/HubConnectionTests.ts @@ -379,9 +379,10 @@ describe("hubConnection", () => { ByteArray: protocol.name === "json" ? "aGVsbG8=" : new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]), - GUID: protocol.name === "json" - ? "00010203-0405-0607-0706-050403020100" - : new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00]), + DateTime: protocol.name === "json" + ? "2002-04-01T10:20:15Z" + : 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], String: "Hello, World!", }; @@ -395,9 +396,50 @@ describe("hubConnection", () => { // 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); + }) + .then(() => { + hubConnection.stop(); + }) + .catch((e) => { + fail(e); + done(); + }); + }); - // GUIDs are serialized as Buffer as well. - value.GUID = new Uint8Array(value.GUID); + it("can receive different types", (done) => { + 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); }) diff --git a/clients/ts/signalr-protocol-msgpack/package-lock.json b/clients/ts/signalr-protocol-msgpack/package-lock.json index 0c622fa8e1..28f546b661 100644 --- a/clients/ts/signalr-protocol-msgpack/package-lock.json +++ b/clients/ts/signalr-protocol-msgpack/package-lock.json @@ -35,11 +35,12 @@ "dev": true }, "bl": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", - "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { - "readable-stream": "2.3.3" + "readable-stream": "2.3.6", + "safe-buffer": "5.1.1" } }, "buffer": { @@ -74,32 +75,32 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "msgpack5": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.0.1.tgz", - "integrity": "sha512-Wx+c6YEE5UMrVrDQuFSIseprjgfeAvMYD35klSKm1xNCvtFI5KICfRmwKMc0Wmu7BknXUA3DUhFWugyCwTsRsg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.0.2.tgz", + "integrity": "sha512-rEIx0/KFtWGtqlF5D/NIMzOHDhm7AhIFzHR3/PLqMrXXbMKoSitDE/IDuTactlTjxEc0ScmHx/5qoH015uL7xA==", "requires": { - "bl": "1.2.1", + "bl": "1.2.2", "inherits": "2.0.3", - "readable-stream": "2.3.3", + "readable-stream": "2.3.6", "safe-buffer": "5.1.1" } }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", - "process-nextick-args": "1.0.7", + "process-nextick-args": "2.0.0", "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", + "string_decoder": "1.1.1", "util-deprecate": "1.0.2" } }, @@ -109,9 +110,9 @@ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "5.1.1" } diff --git a/clients/ts/signalr-protocol-msgpack/package.json b/clients/ts/signalr-protocol-msgpack/package.json index eb3347a70f..fe58caacd1 100644 --- a/clients/ts/signalr-protocol-msgpack/package.json +++ b/clients/ts/signalr-protocol-msgpack/package.json @@ -43,7 +43,7 @@ "@aspnet/signalr": "^1.0.0-preview3" }, "dependencies": { - "msgpack5": "^4.0.1" + "msgpack5": "^4.0.2" }, "devDependencies": { "@types/msgpack5": "^3.4.1", diff --git a/samples/SignalRSamples/Startup.cs b/samples/SignalRSamples/Startup.cs index 30de147776..2b7305f3c9 100644 --- a/samples/SignalRSamples/Startup.cs +++ b/samples/SignalRSamples/Startup.cs @@ -5,7 +5,6 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; -using MsgPack.Serialization; using SignalRSamples.ConnectionHandlers; using SignalRSamples.Hubs; @@ -24,10 +23,7 @@ namespace SignalRSamples // Faster pings for testing options.KeepAliveInterval = TimeSpan.FromSeconds(5); }) - .AddMessagePackProtocol(options => - { - options.SerializationContext.DictionarySerlaizationOptions.KeyTransformer = DictionaryKeyTransformers.LowerCamel; - }); + .AddMessagePackProtocol(); //.AddRedis(); services.AddCors(o => diff --git a/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/Internal/Protocol/MessagePackHubProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/Internal/Protocol/MessagePackHubProtocol.cs index 18eb32ac06..8bf93d7635 100644 --- a/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/Internal/Protocol/MessagePackHubProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/Internal/Protocol/MessagePackHubProtocol.cs @@ -8,12 +8,12 @@ using System.Diagnostics; using System.IO; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; +using MessagePack; +using MessagePack.Formatters; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal.Formatters; using Microsoft.Extensions.Options; -using MsgPack; -using MsgPack.Serialization; namespace Microsoft.AspNetCore.SignalR.Internal.Protocol { @@ -23,11 +23,11 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol private const int VoidResult = 2; private const int NonVoidResult = 3; + private IFormatterResolver _resolver; + public static readonly string ProtocolName = "messagepack"; public static readonly int ProtocolVersion = 1; - public SerializationContext SerializationContext { get; } - public string Name => ProtocolName; public int Version => ProtocolVersion; @@ -40,7 +40,32 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol public MessagePackHubProtocol(IOptions 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) @@ -58,9 +83,8 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol var arraySegment = GetArraySegment(payload); - message = ParseMessage(arraySegment.Array, arraySegment.Offset, binder); - - return message != null; + message = ParseMessage(arraySegment.Array, arraySegment.Offset, binder, _resolver); + return true; } private static ArraySegment GetArraySegment(in ReadOnlySequence input) @@ -77,41 +101,39 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol return new ArraySegment(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"); - - var messageType = ReadInt32(unpacker, "messageType"); - - switch (messageType) - { - case HubProtocolConstants.InvocationMessageType: - return CreateInvocationMessage(unpacker, binder); - case HubProtocolConstants.StreamInvocationMessageType: - return CreateStreamInvocationMessage(unpacker, binder); - case HubProtocolConstants.StreamItemMessageType: - return CreateStreamItemMessage(unpacker, binder); - case HubProtocolConstants.CompletionMessageType: - return CreateCompletionMessage(unpacker, binder); - case HubProtocolConstants.CancelInvocationMessageType: - return CreateCancelInvocationMessage(unpacker); - case HubProtocolConstants.PingMessageType: - return PingMessage.Instance; - case HubProtocolConstants.CloseMessageType: - return CreateCloseMessage(unpacker); - default: - // Future protocol changes can add message types, old clients can ignore them - return null; - } + case HubProtocolConstants.InvocationMessageType: + return CreateInvocationMessage(input, ref startOffset, binder, resolver); + case HubProtocolConstants.StreamInvocationMessageType: + return CreateStreamInvocationMessage(input, ref startOffset, binder, resolver); + case HubProtocolConstants.StreamItemMessageType: + return CreateStreamItemMessage(input, ref startOffset, binder, resolver); + case HubProtocolConstants.CompletionMessageType: + return CreateCompletionMessage(input, ref startOffset, binder, resolver); + case HubProtocolConstants.CancelInvocationMessageType: + return CreateCancelInvocationMessage(input, ref startOffset); + case HubProtocolConstants.PingMessageType: + return PingMessage.Instance; + case HubProtocolConstants.CloseMessageType: + return CreateCloseMessage(input, ref startOffset); + 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 invocationId = ReadInvocationId(unpacker); + var headers = ReadHeaders(input, ref offset); + var invocationId = ReadInvocationId(input, ref offset); // 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. @@ -120,12 +142,12 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol invocationId = null; } - var target = ReadString(unpacker, "target"); + var target = ReadString(input, ref offset, "target"); var parameterTypes = binder.GetParameterTypes(target); 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)); } 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 invocationId = ReadInvocationId(unpacker); - var target = ReadString(unpacker, "target"); + var headers = ReadHeaders(input, ref offset); + var invocationId = ReadInvocationId(input, ref offset); + var target = ReadString(input, ref offset, "target"); var parameterTypes = binder.GetParameterTypes(target); 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)); } 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 invocationId = ReadInvocationId(unpacker); + var headers = ReadHeaders(input, ref offset); + var invocationId = ReadInvocationId(input, ref offset); 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)); } - 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 invocationId = ReadInvocationId(unpacker); - var resultKind = ReadInt32(unpacker, "resultKind"); + var headers = ReadHeaders(input, ref offset); + var invocationId = ReadInvocationId(input, ref offset); + var resultKind = ReadInt32(input, ref offset, "resultKind"); string error = null; object result = null; @@ -174,11 +196,11 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol switch (resultKind) { case ErrorResult: - error = ReadString(unpacker, "error"); + error = ReadString(input, ref offset, "error"); break; case NonVoidResult: var itemType = binder.GetReturnType(invocationId); - result = DeserializeObject(unpacker, itemType, "argument"); + result = DeserializeObject(input, ref offset, itemType, "argument", resolver); hasResult = true; break; case VoidResult: @@ -191,22 +213,22 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol 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 invocationId = ReadInvocationId(unpacker); + var headers = ReadHeaders(input, ref offset); + var invocationId = ReadInvocationId(input, ref offset); 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); } - private static Dictionary ReadHeaders(Unpacker unpacker) + private static Dictionary ReadHeaders(byte[] input, ref int offset) { - var headerCount = ReadMapLength(unpacker, "headers"); + var headerCount = ReadMapLength(input, ref offset, "headers"); if (headerCount > 0) { // 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++) { - var key = ReadString(unpacker, $"headers[{i}].Key"); - var value = ReadString(unpacker, $"headers[{i}].Value"); + var key = ReadString(input, ref offset, $"headers[{i}].Key"); + var value = ReadString(input, ref offset, $"headers[{i}].Value"); headers[key] = value; } return headers; @@ -226,9 +248,9 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol } } - private static object[] BindArguments(Unpacker unpacker, IReadOnlyList parameterTypes) + private static object[] BindArguments(byte[] input, ref int offset, IReadOnlyList parameterTypes, IFormatterResolver resolver) { - var argumentCount = ReadArrayLength(unpacker, "arguments"); + var argumentCount = ReadArrayLength(input, ref offset, "arguments"); if (parameterTypes.Count != argumentCount) { @@ -241,7 +263,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol var arguments = new object[argumentCount]; 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; @@ -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) { 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); - packer.Pack(HubProtocolConstants.InvocationMessageType); + MessagePackBinary.WriteArrayHeader(packer, 5); + MessagePackBinary.WriteInt32(packer, HubProtocolConstants.InvocationMessageType); PackHeaders(packer, message.Headers); if (string.IsNullOrEmpty(message.InvocationId)) { - packer.PackNull(); + MessagePackBinary.WriteNil(packer); } else { - packer.PackString(message.InvocationId); + MessagePackBinary.WriteString(packer, message.InvocationId); } - packer.PackString(message.Target); - packer.PackArrayHeader(message.Arguments.Length); + MessagePackBinary.WriteString(packer, message.Target); + MessagePackBinary.WriteArrayHeader(packer, message.Arguments.Length); 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); - packer.Pack(HubProtocolConstants.StreamInvocationMessageType); + MessagePackBinary.WriteArrayHeader(packer, 5); + MessagePackBinary.WriteInt16(packer, HubProtocolConstants.StreamInvocationMessageType); PackHeaders(packer, message.Headers); - packer.PackString(message.InvocationId); - packer.PackString(message.Target); - packer.PackArrayHeader(message.Arguments.Length); + MessagePackBinary.WriteString(packer, message.InvocationId); + MessagePackBinary.WriteString(packer, message.Target); + + MessagePackBinary.WriteArrayHeader(packer, message.Arguments.Length); 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); - packer.Pack(HubProtocolConstants.StreamItemMessageType); + MessagePackBinary.WriteArrayHeader(packer, 4); + MessagePackBinary.WriteInt16(packer, HubProtocolConstants.StreamItemMessageType); PackHeaders(packer, message.Headers); - packer.PackString(message.InvocationId); - packer.PackObject(message.Item, SerializationContext); + MessagePackBinary.WriteString(packer, message.InvocationId); + 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 = message.Error != null ? ErrorResult : message.HasResult ? NonVoidResult : VoidResult; - packer.PackArrayHeader(4 + (resultKind != VoidResult ? 1 : 0)); - packer.Pack(HubProtocolConstants.CompletionMessageType); + MessagePackBinary.WriteArrayHeader(packer, 4 + (resultKind != VoidResult ? 1 : 0)); + MessagePackBinary.WriteInt32(packer, HubProtocolConstants.CompletionMessageType); PackHeaders(packer, message.Headers); - packer.PackString(message.InvocationId); - packer.Pack(resultKind); + MessagePackBinary.WriteString(packer, message.InvocationId); + MessagePackBinary.WriteInt32(packer, resultKind); switch (resultKind) { case ErrorResult: - packer.PackString(message.Error); + MessagePackBinary.WriteString(packer, message.Error); break; case NonVoidResult: - packer.PackObject(message.Result, SerializationContext); + WriteArgument(message.Result, packer); break; } } - private void WriteCancelInvocationMessage(CancelInvocationMessage message, Packer packer) + private void WriteCancelInvocationMessage(CancelInvocationMessage message, Stream packer) { - packer.PackArrayHeader(3); - packer.Pack(HubProtocolConstants.CancelInvocationMessageType); + MessagePackBinary.WriteArrayHeader(packer, 3); + MessagePackBinary.WriteInt16(packer, HubProtocolConstants.CancelInvocationMessageType); 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); - packer.Pack(HubProtocolConstants.CloseMessageType); + MessagePackBinary.WriteArrayHeader(packer, 2); + MessagePackBinary.WriteInt16(packer, HubProtocolConstants.CloseMessageType); if (string.IsNullOrEmpty(message.Error)) { - packer.PackNull(); + MessagePackBinary.WriteNil(packer); } 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); - packer.Pack(HubProtocolConstants.PingMessageType); + MessagePackBinary.WriteArrayHeader(packer, 1); + MessagePackBinary.WriteInt32(packer, HubProtocolConstants.PingMessageType); } - private void PackHeaders(Packer packer, IDictionary headers) + private void PackHeaders(Stream packer, IDictionary headers) { if (headers != null) { - packer.PackMapHeader(headers.Count); + MessagePackBinary.WriteMapHeader(packer, headers.Count); if (headers.Count > 0) { foreach (var header in headers) { - packer.PackString(header.Key); - packer.PackString(header.Value); + MessagePackBinary.WriteString(packer, header.Key); + MessagePackBinary.WriteString(packer, header.Value); } } } 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; try { - if (unpacker.ReadInt32(out var value)) - { - return value; - } + var readInt = MessagePackBinary.ReadInt32(input, offset, out var readSize); + offset += readSize; + return readInt; } catch (Exception e) { @@ -452,22 +483,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol 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; try { - if (unpacker.Read()) - { - if (unpacker.LastReadData.IsNil) - { - return null; - } - else - { - return unpacker.LastReadData.AsString(); - } - } + var readString = MessagePackBinary.ReadString(input, offset, out var readSize); + offset += readSize; + return readString; } catch (Exception e) { @@ -477,15 +500,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol 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; try { - if (unpacker.ReadBoolean(out var value)) - { - return value; - } + var readBool = MessagePackBinary.ReadBoolean(input, offset, out var readSize); + offset += readSize; + return readBool; } catch (Exception e) { @@ -495,15 +517,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol 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; try { - if (unpacker.ReadMapLength(out var value)) - { - return value; - } + var readMap = MessagePackBinary.ReadMapHeader(input, offset, out var readSize); + offset += readSize; + return readMap; } catch (Exception e) { @@ -513,15 +534,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol 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; try { - if (unpacker.ReadArrayLength(out var value)) - { - return value; - } + var readArray = MessagePackBinary.ReadArrayHeader(input, offset, out var readSize); + offset += readSize; + return readArray; } catch (Exception e) { @@ -531,16 +551,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol 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; try { - if (unpacker.Read()) - { - var serializer = MessagePackSerializer.Get(type); - return serializer.UnpackFrom(unpacker); - } + var obj = MessagePackSerializer.NonGeneric.Deserialize(type, new ArraySegment(input, offset, input.Length - offset), resolver); + offset += MessagePackBinary.ReadNextBlock(input, offset); + return obj; } 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); } - internal static SerializationContext CreateDefaultSerializationContext() + internal static List CreateDefaultFormatterResolvers() { - // serializes objects (here: arguments and results) as maps so that property names are preserved - var serializationContext = new SerializationContext { SerializationMethod = SerializationMethod.Map }; + // Copy to allow users to add/remove resolvers without changing the static SignalRResolver list + return new List(SignalRResolver.Resolvers); + } - // allows for serializing objects that cannot be deserialized due to the lack of the default ctor etc. - serializationContext.CompatibilityOptions.AllowAsymmetricSerializer = true; - return serializationContext; + internal class SignalRResolver : IFormatterResolver + { + public static readonly IFormatterResolver Instance = new SignalRResolver(); + + public static readonly IList Resolvers = new[] + { + MessagePack.Resolvers.DynamicEnumAsStringResolver.Instance, + MessagePack.Resolvers.ContractlessStandardResolver.Instance, + }; + + public IMessagePackFormatter GetFormatter() + { + return Cache.Formatter; + } + + private static class Cache + { + public static readonly IMessagePackFormatter Formatter; + + static Cache() + { + foreach (var resolver in Resolvers) + { + Formatter = resolver.GetFormatter(); + if (Formatter != null) + { + return; + } + } + } + } + } + + // Support for users making their own Formatter lists + internal class CombinedResolvers : IFormatterResolver + { + private readonly IList _resolvers; + + public CombinedResolvers(IList resolvers) + { + _resolvers = resolvers; + } + + public IMessagePackFormatter GetFormatter() + { + foreach (var resolver in _resolvers) + { + var formatter = resolver.GetFormatter(); + if (formatter != null) + { + return formatter; + } + } + + return null; + } } } } diff --git a/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/MessagePackHubProtocolOptions.cs b/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/MessagePackHubProtocolOptions.cs index de417ac1e1..cd4d509a9a 100644 --- a/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/MessagePackHubProtocolOptions.cs +++ b/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/MessagePackHubProtocolOptions.cs @@ -1,13 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; +using MessagePack; using Microsoft.AspNetCore.SignalR.Internal.Protocol; -using MsgPack.Serialization; namespace Microsoft.AspNetCore.SignalR { public class MessagePackHubProtocolOptions { - public SerializationContext SerializationContext { get; set; } = MessagePackHubProtocol.CreateDefaultSerializationContext(); + public IList FormatterResolvers { get; set; } = MessagePackHubProtocol.CreateDefaultFormatterResolvers(); } } diff --git a/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/Microsoft.AspNetCore.SignalR.Protocols.MsgPack.csproj b/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/Microsoft.AspNetCore.SignalR.Protocols.MsgPack.csproj index 1fb060bc20..a4c7cbc0be 100644 --- a/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/Microsoft.AspNetCore.SignalR.Protocols.MsgPack.csproj +++ b/src/Microsoft.AspNetCore.SignalR.Protocols.MsgPack/Microsoft.AspNetCore.SignalR.Protocols.MsgPack.csproj @@ -12,7 +12,7 @@ - + diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionBuilderTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionBuilderTests.cs index fbb1216c96..25187f61a0 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionBuilderTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionBuilderTests.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.Extensions.DependencyInjection; -using MsgPack.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Xunit; @@ -78,27 +77,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests } [Fact] - public void AddMessagePackProtocolSetsHubProtocolToMsgPackWithDefaultOptions() + public void AddMessagePackProtocolSetsHubProtocolToMsgPack() { var serviceProvider = new HubConnectionBuilder().AddMessagePackProtocol().Services.BuildServiceProvider(); - var actualProtocol = Assert.IsType(serviceProvider.GetService()); - 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(serviceProvider.GetService()); - Assert.Equal(SerializationMethod.Array, actualProtocol.SerializationContext.SerializationMethod); + Assert.IsType(serviceProvider.GetService()); } } } diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/CustomObject.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/CustomObject.cs index 123267d45d..fbc84022a1 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/CustomObject.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/CustomObject.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol 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; diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs index b8e7018298..81d72e7c3b 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/JsonHubProtocolTests.cs @@ -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_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_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_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_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_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_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: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: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: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_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\"]}"), @@ -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_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_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_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_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_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_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_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: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: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: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: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_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_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_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_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_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_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_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_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: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: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: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: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_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\"}"), @@ -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_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_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_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_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_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_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_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: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: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: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:00Z\",\"nullProp\":null,\"byteArrProp\":\"AQID\"}]}"), 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\"}"), diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHelpers.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHelpers.cs deleted file mode 100644 index 2c9eafb327..0000000000 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHelpers.cs +++ /dev/null @@ -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))); - } -} diff --git a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs index e6bdb6090d..2e3d7e9d31 100644 --- a/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Common.Tests/Internal/Protocol/MessagePackHubProtocolTests.cs @@ -4,19 +4,17 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.SignalR.Internal.Formatters; using Microsoft.AspNetCore.SignalR.Internal.Protocol; -using MsgPack; using Xunit; namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol { using static HubMessageHelpers; - using static MessagePackHelpers; public class MessagePackHubProtocolTests { @@ -27,36 +25,22 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol { "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 = new MessagePackHubProtocol(); - private static readonly MessagePackObject CustomObjectSerialized = Map( - ("ByteArrProp", new MessagePackObject(new byte[] { 1, 2, 3 }, isBinary: true)), - ("DateTimeProp", new MessagePackObject(Timestamp.FromDateTime(new DateTime(2017, 4, 11)))), - ("DoubleProp", 6.2831853071), - ("IntProp", 42), - ("NullProp", MessagePackObject.Nil), - ("StringProp", "SignalR!")); + public enum TestEnum + { + Zero = 0, + One + } // Test Data for Parse/WriteMessages: // * 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) - // * Encoded: Raw MessagePackObject values (using the MessagePackHelpers static "Arr" and "Map" helpers) describing the message - // * Binary: Base64-encoded binary "baseline" to sanity-check MsgPack-Cli behavior + // * Binary: Base64-encoded binary "baseline" to sanity-check MessagePack-CSharp 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, - // those values will change and the Assert will give you a useful error telling you how the MsgPack structure itself changed (rather than just - // 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. + // When changing the tests/message pack parsing if you get test failures look at the base64 encoding and + // 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 TestDataNames { @@ -75,202 +59,180 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol new ProtocolTestData( name: "InvocationWithNoHeadersAndNoArgs", message: new InvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null), - encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), "xyz", "method", Array()), binary: "lQGAo3h5eqZtZXRob2SQ"), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdAndNoArgs", message: new InvocationMessage(target: "method", argumentBindingException: null), - encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array()), binary: "lQGAwKZtZXRob2SQ"), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdAndSingleNullArg", message: new InvocationMessage(target: "method", argumentBindingException: null, new object[] { null }), - encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(MessagePackObject.Nil)), binary: "lQGAwKZtZXRob2SRwA=="), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdAndSingleIntArg", message: new InvocationMessage(target: "method", argumentBindingException: null, 42), - encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(42)), binary: "lQGAwKZtZXRob2SRKg=="), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdIntAndStringArgs", message: new InvocationMessage(target: "method", argumentBindingException: null, 42, "string"), - encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(42, "string")), binary: "lQGAwKZtZXRob2SSKqZzdHJpbmc="), + new ProtocolTestData( + name: "InvocationWithNoHeadersNoIdIntAndEnumArgs", + message: new InvocationMessage(target: "method", argumentBindingException: null, 42, TestEnum.One), + binary: "lQGAwKZtZXRob2SSKqNPbmU="), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdAndCustomObjectArg", message: new InvocationMessage(target: "method", argumentBindingException: null, 42, "string", new CustomObject()), - encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(42, "string", CustomObjectSerialized)), - binary: "lQGAwKZtZXRob2STKqZzdHJpbmeGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="), + binary: "lQGAwKZtZXRob2STKqZzdHJpbmeGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="), new ProtocolTestData( name: "InvocationWithNoHeadersNoIdAndArrayOfCustomObjectArgs", message: new InvocationMessage(target: "method", argumentBindingException: null, new[] { new CustomObject(), new CustomObject() }), - encoded: Array(HubProtocolConstants.InvocationMessageType, Map(), MessagePackObject.Nil, "method", Array(CustomObjectSerialized, CustomObjectSerialized)), - binary: "lQGAwKZtZXRob2SShqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIhhqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIh"), + binary: "lQGAwKZtZXRob2SShqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQIDhqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQID"), new ProtocolTestData( name: "InvocationWithHeadersNoIdAndArrayOfCustomObjectArgs", 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: "lQGDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmXApm1ldGhvZJKGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="), + binary: "lQGDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmXApm1ldGhvZJKGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="), // StreamItem Messages new ProtocolTestData( name: "StreamItemWithNoHeadersAndNullItem", message: new StreamItemMessage(invocationId: "xyz", item: null), - encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", MessagePackObject.Nil), binary: "lAKAo3h5esA="), new ProtocolTestData( name: "StreamItemWithNoHeadersAndIntItem", message: new StreamItemMessage(invocationId: "xyz", item: 42), - encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", 42), binary: "lAKAo3h5eio="), new ProtocolTestData( name: "StreamItemWithNoHeadersAndFloatItem", message: new StreamItemMessage(invocationId: "xyz", item: 42.0f), - encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", 42.0f), binary: "lAKAo3h5espCKAAA"), new ProtocolTestData( name: "StreamItemWithNoHeadersAndStringItem", message: new StreamItemMessage(invocationId: "xyz", item: "string"), - encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", "string"), binary: "lAKAo3h5eqZzdHJpbmc="), new ProtocolTestData( name: "StreamItemWithNoHeadersAndBoolItem", message: new StreamItemMessage(invocationId: "xyz", item: true), - encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", true), binary: "lAKAo3h5esM="), + new ProtocolTestData( + name: "StreamItemWithNoHeadersAndEnumItem", + message: new StreamItemMessage(invocationId: "xyz", item: TestEnum.One), + binary: "lAKAo3h5eqNPbmU="), new ProtocolTestData( name: "StreamItemWithNoHeadersAndCustomObjectItem", message: new StreamItemMessage(invocationId: "xyz", item: new CustomObject()), - encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", CustomObjectSerialized), - binary: "lAKAo3h5eoarQnl0ZUFyclByb3DEAwECA6xEYXRlVGltZVByb3DW/1jsHICqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqhOdWxsUHJvcMCqU3RyaW5nUHJvcKhTaWduYWxSIQ=="), + binary: "lAKAo3h5eoaqU3RyaW5nUHJvcKhTaWduYWxSIapEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqrERhdGVUaW1lUHJvcNb/WOwcgKhOdWxsUHJvcMCrQnl0ZUFyclByb3DEAwECAw=="), new ProtocolTestData( name: "StreamItemWithNoHeadersAndCustomObjectArrayItem", message: new StreamItemMessage(invocationId: "xyz", item: new[] { new CustomObject(), new CustomObject() }), - encoded: Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz", Array(CustomObjectSerialized, CustomObjectSerialized)), - binary: "lAKAo3h5epKGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="), + binary: "lAKAo3h5epKGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="), new ProtocolTestData( name: "StreamItemWithHeadersAndCustomObjectArrayItem", message: AddHeaders(TestHeaders, new StreamItemMessage(invocationId: "xyz", item: new[] { new CustomObject(), new CustomObject() })), - encoded: Array(HubProtocolConstants.StreamItemMessageType, TestHeadersSerialized, "xyz", Array(CustomObjectSerialized, CustomObjectSerialized)), - binary: "lAKDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6koarQnl0ZUFyclByb3DEAwECA6xEYXRlVGltZVByb3DW/1jsHICqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqhOdWxsUHJvcMCqU3RyaW5nUHJvcKhTaWduYWxSIYarQnl0ZUFyclByb3DEAwECA6xEYXRlVGltZVByb3DW/1jsHICqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqhOdWxsUHJvcMCqU3RyaW5nUHJvcKhTaWduYWxSIQ=="), + binary: "lAKDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6koaqU3RyaW5nUHJvcKhTaWduYWxSIapEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqrERhdGVUaW1lUHJvcNb/WOwcgKhOdWxsUHJvcMCrQnl0ZUFyclByb3DEAwECA4aqU3RyaW5nUHJvcKhTaWduYWxSIapEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqrERhdGVUaW1lUHJvcNb/WOwcgKhOdWxsUHJvcMCrQnl0ZUFyclByb3DEAwECAw=="), // Completion Messages new ProtocolTestData( name: "CompletionWithNoHeadersAndError", message: CompletionMessage.WithError(invocationId: "xyz", error: "Error not found!"), - encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 1, "Error not found!"), binary: "lQOAo3h5egGwRXJyb3Igbm90IGZvdW5kIQ=="), new ProtocolTestData( name: "CompletionWithHeadersAndError", message: AddHeaders(TestHeaders, CompletionMessage.WithError(invocationId: "xyz", error: "Error not found!")), - encoded: Array(HubProtocolConstants.CompletionMessageType, TestHeadersSerialized, "xyz", 1, "Error not found!"), binary: "lQODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6AbBFcnJvciBub3QgZm91bmQh"), new ProtocolTestData( name: "CompletionWithNoHeadersAndNoResult", message: CompletionMessage.Empty(invocationId: "xyz"), - encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 2), binary: "lAOAo3h5egI="), new ProtocolTestData( name: "CompletionWithHeadersAndNoResult", message: AddHeaders(TestHeaders, CompletionMessage.Empty(invocationId: "xyz")), - encoded: Array(HubProtocolConstants.CompletionMessageType, TestHeadersSerialized, "xyz", 2), binary: "lAODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6Ag=="), new ProtocolTestData( name: "CompletionWithNoHeadersAndNullResult", message: CompletionMessage.WithResult(invocationId: "xyz", payload: null), - encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, MessagePackObject.Nil), binary: "lQOAo3h5egPA"), new ProtocolTestData( name: "CompletionWithNoHeadersAndIntResult", message: CompletionMessage.WithResult(invocationId: "xyz", payload: 42), - encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, 42), binary: "lQOAo3h5egMq"), + new ProtocolTestData( + name: "CompletionWithNoHeadersAndEnumResult", + message: CompletionMessage.WithResult(invocationId: "xyz", payload: TestEnum.One), + binary: "lQOAo3h5egOjT25l"), new ProtocolTestData( name: "CompletionWithNoHeadersAndFloatResult", message: CompletionMessage.WithResult(invocationId: "xyz", payload: 42.0f), - encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, 42.0f), binary: "lQOAo3h5egPKQigAAA=="), new ProtocolTestData( name: "CompletionWithNoHeadersAndStringResult", message: CompletionMessage.WithResult(invocationId: "xyz", payload: "string"), - encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, "string"), binary: "lQOAo3h5egOmc3RyaW5n"), new ProtocolTestData( name: "CompletionWithNoHeadersAndBooleanResult", message: CompletionMessage.WithResult(invocationId: "xyz", payload: true), - encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, true), binary: "lQOAo3h5egPD"), new ProtocolTestData( name: "CompletionWithNoHeadersAndCustomObjectResult", message: CompletionMessage.WithResult(invocationId: "xyz", payload: new CustomObject()), - encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, CustomObjectSerialized), - binary: "lQOAo3h5egOGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="), + binary: "lQOAo3h5egOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="), new ProtocolTestData( name: "CompletionWithNoHeadersAndCustomObjectArrayResult", message: CompletionMessage.WithResult(invocationId: "xyz", payload: new[] { new CustomObject(), new CustomObject() }), - encoded: Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, Array(CustomObjectSerialized, CustomObjectSerialized)), - binary: "lQOAo3h5egOShqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIhhqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIh"), + binary: "lQOAo3h5egOShqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQIDhqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQID"), new ProtocolTestData( name: "CompletionWithHeadersAndCustomObjectArrayResult", message: AddHeaders(TestHeaders, CompletionMessage.WithResult(invocationId: "xyz", payload: new[] { new CustomObject(), new CustomObject() })), - encoded: Array(HubProtocolConstants.CompletionMessageType, TestHeadersSerialized, "xyz", 3, Array(CustomObjectSerialized, CustomObjectSerialized)), - binary: "lQODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6A5KGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="), + binary: "lQODo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6A5KGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="), // StreamInvocation Messages new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndNoArgs", message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null), - encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array()), binary: "lQSAo3h5eqZtZXRob2SQ"), new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndNullArg", message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, new object[] { null }), - encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(MessagePackObject.Nil)), binary: "lQSAo3h5eqZtZXRob2SRwA=="), new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndIntArg", message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, 42), - encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(42)), binary: "lQSAo3h5eqZtZXRob2SRKg=="), + new ProtocolTestData( + name: "StreamInvocationWithNoHeadersAndEnumArg", + message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, TestEnum.One), + binary: "lQSAo3h5eqZtZXRob2SRo09uZQ=="), new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndIntAndStringArgs", message: new StreamInvocationMessage(invocationId: "xyz", target: "method", argumentBindingException: null, 42, "string"), - encoded: Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "xyz", "method", Array(42, "string")), binary: "lQSAo3h5eqZtZXRob2SSKqZzdHJpbmc="), new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndIntStringAndCustomObjectArgs", 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: "lQSAo3h5eqZtZXRob2STKqZzdHJpbmeGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="), + binary: "lQSAo3h5eqZtZXRob2STKqZzdHJpbmeGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="), new ProtocolTestData( name: "StreamInvocationWithNoHeadersAndCustomObjectArrayArg", 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: "lQSAo3h5eqZtZXRob2SShqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIhhqtCeXRlQXJyUHJvcMQDAQIDrERhdGVUaW1lUHJvcNb/WOwcgKpEb3VibGVQcm9wy0AZIftUQs8Sp0ludFByb3AqqE51bGxQcm9wwKpTdHJpbmdQcm9wqFNpZ25hbFIh"), + binary: "lQSAo3h5eqZtZXRob2SShqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQIDhqpTdHJpbmdQcm9wqFNpZ25hbFIhqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqsRGF0ZVRpbWVQcm9w1v9Y7ByAqE51bGxQcm9wwKtCeXRlQXJyUHJvcMQDAQID"), new ProtocolTestData( name: "StreamInvocationWithHeadersAndCustomObjectArrayArg", 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: "lQSDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6pm1ldGhvZJKGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiGGq0J5dGVBcnJQcm9wxAMBAgOsRGF0ZVRpbWVQcm9w1v9Y7ByAqkRvdWJsZVByb3DLQBkh+1RCzxKnSW50UHJvcCqoTnVsbFByb3DAqlN0cmluZ1Byb3CoU2lnbmFsUiE="), + binary: "lQSDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6pm1ldGhvZJKGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgOGqlN0cmluZ1Byb3CoU2lnbmFsUiGqRG91YmxlUHJvcMtAGSH7VELPEqdJbnRQcm9wKqxEYXRlVGltZVByb3DW/1jsHICoTnVsbFByb3DAq0J5dGVBcnJQcm9wxAMBAgM="), // CancelInvocation Messages new ProtocolTestData( name: "CancelInvocationWithNoHeaders", message: new CancelInvocationMessage(invocationId: "xyz"), - encoded: Array(HubProtocolConstants.CancelInvocationMessageType, Map(), "xyz"), binary: "kwWAo3h5eg=="), new ProtocolTestData( name: "CancelInvocationWithHeaders", message: AddHeaders(TestHeaders, new CancelInvocationMessage(invocationId: "xyz")), - encoded: Array(HubProtocolConstants.CancelInvocationMessageType, TestHeadersSerialized, "xyz"), binary: "kwWDo0Zvb6NCYXKyS2V5V2l0aApOZXcNCkxpbmVzq1N0aWxsIFdvcmtzsVZhbHVlV2l0aE5ld0xpbmVzsEFsc28KV29ya3MNCkZpbmWjeHl6"), // Ping Messages new ProtocolTestData( name: "Ping", message: PingMessage.Instance, - encoded: Array(HubProtocolConstants.PingMessageType), binary: "kQY="), }.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 var bytes = Convert.FromBase64String(testData.Binary); - var obj = Unpack(bytes); - Assert.Equal(testData.Encoded, obj); // Parse the input fully now. bytes = Frame(bytes); - var protocol = new MessagePackHubProtocol(); var data = new ReadOnlySequence(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.Equal(testData.Message, message, TestHubMessageEqualityComparer.Instance); @@ -299,19 +258,14 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol public void ParseMessageWithExtraData() { 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 - var bytes = Convert.FromBase64String(binary); - var obj = Unpack(bytes); - Assert.Equal(encodedObj, obj); + 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' }; // Parse the input fully now. bytes = Frame(bytes); - var protocol = new MessagePackHubProtocol(); var data = new ReadOnlySequence(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.Equal(expectedMessage, message, TestHubMessageEqualityComparer.Instance); @@ -324,7 +278,6 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol var testData = TestData[testDataName]; var bytes = Write(testData.Message); - AssertMessages(testData.Encoded, bytes); // Unframe the message to check the binary encoding var byteSpan = new ReadOnlySequence(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"); } + [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(writer.ToArray()); + _hubProtocol.TryParseMessage(ref bytes, new TestBinder(typeof(DateTime)), out var hubMessage); + + var completionMessage = Assert.IsType(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(writer.ToArray()); + _hubProtocol.TryParseMessage(ref bytes, new TestBinder(typeof(DateTimeOffset)), out var hubMessage); + + var completionMessage = Assert.IsType(hubMessage); + + var resultDateTimeOffset = (DateTimeOffset)completionMessage.Result; + Assert.Equal(dateTimeOffset, resultDateTimeOffset); + } + finally + { + MemoryBufferWriter.Return(writer); + } + } + public static IDictionary InvalidPayloads => new[] { // 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 - new InvalidMessageData("HeadersNotAMap", Array(HubProtocolConstants.InvocationMessageType, "foo"), "Reading map length for 'headers' failed."), - new InvalidMessageData("HeaderKeyInt", Array(HubProtocolConstants.InvocationMessageType, Map((42, "foo"))), "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("HeaderKeyArray", Array(HubProtocolConstants.InvocationMessageType, Map(("biz", "boz"), (Array(), "foo"))), "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("HeadersNotAMap", new byte[] { 0x92, 1, 0xa3, (byte)'f', (byte)'o', (byte)'o' }, "Reading map length for 'headers' 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", new byte[] { 0x92, 1, 0x82, 0xa3, (byte)'f', (byte)'o', (byte)'o', 42 }, "Reading 'headers[0].Value' 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", 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 - new InvalidMessageData("InvocationMissingId", Array(HubProtocolConstants.InvocationMessageType, Map()), "Reading 'invocationId' as String failed."), - new InvalidMessageData("InvocationIdBoolean", Array(HubProtocolConstants.InvocationMessageType, Map(), false), "Reading 'invocationId' as String failed."), - new InvalidMessageData("InvocationTargetMissing", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc"), "Reading 'target' as String failed."), - new InvalidMessageData("InvocationTargetInt", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", 42), "Reading 'target' as String failed."), + new InvalidMessageData("InvocationMissingId", new byte[] { 0x92, 1, 0x80 }, "Reading 'invocationId' as String failed."), + new InvalidMessageData("InvocationIdBoolean", new byte[] { 0x91, 1, 0x80, 0xc2 }, "Reading 'invocationId' 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", new byte[] { 0x94, 1, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 42 }, "Reading 'target' as String failed."), // StreamInvocationMessage - new InvalidMessageData("StreamInvocationMissingId", Array(HubProtocolConstants.StreamInvocationMessageType, Map()), "Reading 'invocationId' as String failed."), - new InvalidMessageData("StreamInvocationIdBoolean", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), false), "Reading 'invocationId' as String failed."), - new InvalidMessageData("StreamInvocationTargetMissing", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc"), "Reading 'target' as String failed."), - new InvalidMessageData("StreamInvocationTargetInt", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", 42), "Reading 'target' as String failed."), + new InvalidMessageData("StreamInvocationMissingId", new byte[] { 0x92, 4, 0x80 }, "Reading 'invocationId' as String failed."), + new InvalidMessageData("StreamInvocationIdBoolean", new byte[] { 0x93, 4, 0x80, 0xc2 }, "Reading 'invocationId' 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", new byte[] { 0x94, 4, 0x80, 0xa3, (byte)'a', (byte)'b', (byte)'c', 42 }, "Reading 'target' as String failed."), // StreamItemMessage - new InvalidMessageData("StreamItemMissingId", Array(HubProtocolConstants.StreamItemMessageType, Map()), "Reading 'invocationId' as String failed."), - new InvalidMessageData("StreamItemInvocationIdBoolean", Array(HubProtocolConstants.StreamItemMessageType, Map(), false), "Reading 'invocationId' as String failed."), - new InvalidMessageData("StreamItemMissing", Array(HubProtocolConstants.StreamItemMessageType, Map(), "xyz"), "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("StreamItemMissingId", new byte[] { 0x92, 2, 0x80 }, "Reading 'invocationId' as String failed."), + new InvalidMessageData("StreamItemInvocationIdBoolean", new byte[] { 0x93, 2, 0x80, 0xc2 }, "Reading 'invocationId' as String 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", new byte[] { 0x94, 2, 0x80, 0xa3, (byte)'x', (byte)'y', (byte)'z', 42 }, "Deserializing object of the `String` type for 'item' failed."), // CompletionMessage - new InvalidMessageData("CompletionMissingId", Array(HubProtocolConstants.CompletionMessageType, Map()), "Reading 'invocationId' as String failed."), - new InvalidMessageData("CompletionIdBoolean", Array(HubProtocolConstants.CompletionMessageType, Map(), false), "Reading 'invocationId' as String failed."), - new InvalidMessageData("CompletionResultKindString", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", "abc"), "Reading 'resultKind' as Int32 failed."), - new InvalidMessageData("CompletionResultKindOutOfRange", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 42), "Invalid invocation result kind."), - new InvalidMessageData("CompletionErrorMissing", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 1), "Reading 'error' as String failed."), - new InvalidMessageData("CompletionErrorInt", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 1, 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("CompletionResultTypeMismatch", Array(HubProtocolConstants.CompletionMessageType, Map(), "xyz", 3, 42), "Deserializing object of the `String` type for 'argument' failed."), + new InvalidMessageData("CompletionMissingId", new byte[] { 0x92, 3, 0x80 }, "Reading 'invocationId' as String failed."), + new InvalidMessageData("CompletionIdBoolean", new byte[] { 0x93, 3, 0x80, 0xc2 }, "Reading 'invocationId' as String 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", new byte[] { 0x94, 3, 0x80, 0xa3, (byte)'x', (byte)'y', (byte)'z', 42 }, "Invalid invocation result kind."), + new InvalidMessageData("CompletionErrorMissing", new byte[] { 0x94, 3, 0x80, 0xa3, (byte)'x', (byte)'y', (byte)'z', 0x01 }, "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", 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", 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); public static IEnumerable 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 buffer = Frame(Pack(testData.Encoded)); + var buffer = Frame(testData.Encoded); var binder = new TestBinder(new[] { typeof(string) }, typeof(string)); var data = new ReadOnlySequence(buffer); var exception = Assert.Throws(() => _hubProtocol.TryParseMessage(ref data, binder, out _)); @@ -395,18 +397,18 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol public static IDictionary ArgumentBindingErrors => new[] { // InvocationMessage - new InvalidMessageData("InvocationArgumentArrayMissing", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz"), "Reading array length for 'arguments' failed."), - new InvalidMessageData("InvocationArgumentArrayNotAnArray", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz", 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("InvocationArgumentArraySizeMismatchTooLarge", Array(HubProtocolConstants.InvocationMessageType, Map(), "abc", "xyz", Array("a", "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("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", 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", 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", 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", 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 - new InvalidMessageData("StreamInvocationArgumentArrayMissing", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz"), "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("StreamInvocationArgumentArraySizeMismatchEmpty", Array(HubProtocolConstants.StreamInvocationMessageType, Map(), "abc", "xyz", Array()), "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("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("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", 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", 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", 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", 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); public static IEnumerable 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 buffer = Frame(Pack(testData.Encoded)); + var buffer = Frame(testData.Encoded); var binder = new TestBinder(new[] { typeof(string) }, typeof(string)); var data = new ReadOnlySequence(buffer); _hubProtocol.TryParseMessage(ref data, binder, out var message); @@ -440,15 +442,28 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol public void SerializerCanSerializeTypesWithNoDefaultCtor() { var result = Write(CompletionMessage.WithResult("0", new List { 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 bytes) { var data = new ReadOnlySequence(bytes); Assert.True(BinaryMessageParser.TryParseMessage(ref data, out var message)); - var obj = Unpack(message.ToArray()); - Assert.Equal(expectedOutput, obj); + Assert.Equal(expectedOutput, message.ToArray()); } 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) { - var protocol = new MessagePackHubProtocol(); var writer = MemoryBufferWriter.Get(); try { - protocol.WriteMessage(message, writer); + _hubProtocol.WriteMessage(message, writer); return writer.ToArray(); } finally @@ -515,10 +498,10 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol public class InvalidMessageData { 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 InvalidMessageData(string name, MessagePackObject encoded, string errorMessage) + public InvalidMessageData(string name, byte[] encoded, string errorMessage) { Name = name; Encoded = encoded; @@ -532,14 +515,12 @@ namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Protocol { public string Name { get; } public string Binary { get; } - public MessagePackObject Encoded { 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; Message = message; - Encoded = encoded; Binary = binary; } diff --git a/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisHubLifetimeManagerTests.cs b/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisHubLifetimeManagerTests.cs index cb46ecb3eb..a7092cac8b 100644 --- a/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisHubLifetimeManagerTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Redis.Tests/RedisHubLifetimeManagerTests.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.SignalR.Tests; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; -using MsgPack.Serialization; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using Xunit; @@ -506,7 +505,6 @@ namespace Microsoft.AspNetCore.SignalR.Redis.Tests var server = new TestRedisServer(); var messagePackOptions = new MessagePackHubProtocolOptions(); - messagePackOptions.SerializationContext.DictionarySerlaizationOptions.KeyTransformer = DictionaryKeyTransformers.LowerCamel; var jsonOptions = new JsonHubProtocolOptions(); jsonOptions.PayloadSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/HubConnectionHandlerTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/HubConnectionHandlerTests.cs index f02a95a8bf..59e7605dcc 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/HubConnectionHandlerTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/HubConnectionHandlerTests.cs @@ -8,6 +8,8 @@ using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; +using MessagePack; +using MessagePack.Formatters; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Connections; @@ -16,8 +18,6 @@ using Microsoft.AspNetCore.SignalR.Internal.Protocol; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Moq; -using MsgPack; -using MsgPack.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; @@ -1679,7 +1679,7 @@ namespace Microsoft.AspNetCore.SignalR.Tests services.AddSignalR() .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(await client.ReadAsync().OrTimeout()); - var msgPackObject = Assert.IsType(message.Arguments[0]); - // Custom serialization - object was serialized as an array and not a map - Assert.True(msgPackObject.IsArray); - Assert.Equal(new[] { "test", "param" }, ((MessagePackObject[])msgPackObject.ToObject()).Select(o => o.AsString())); + var result = message.Arguments[0] as Dictionary; + Assert.Equal("formattedString", result["Message"]); + Assert.Equal("formattedString", result["paramName"]); client.Dispose(); @@ -1708,6 +1707,50 @@ namespace Microsoft.AspNetCore.SignalR.Tests } } + private class CustomFormatter : IFormatterResolver + { + public IMessagePackFormatter GetFormatter() + { + if (typeof(T) == typeof(string)) + { + return new StringFormatter(); + } + return null; + } + + private class StringFormatter : IMessagePackFormatter + { + 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] public async Task CanGetHttpContextFromHubConnectionContext() {