Skip to content

MessagePack deserialization throws error when using Lz4BlockArray compression #46787

@Kloizdena

Description

@Kloizdena

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

I have a class hierarchy with a base type and some derived types which I want to send through SignalR and use MessagePack format with compression. I added a reference to the Microsoft.AspNetCore.SignalR.Protocols.MessagePack to setup this configuration.
I have implemented MessagePack formatters to serialize my types and resolve the concrete type during deserialization.
The problem is that when I use MessagePackCompression.Lz4BlockArray, deserialization throws an error.

I tried the the below test with the latest MessagePack version (currently 2.4.59), it does not throw the error, but Microsoft.AspNetCore.SignalR.Protocols.MessagePack references an earlier MessagePack version (2.1.90), and if the project only has the MessagePack reference through the AspNetCore nuget, it throws an error.

Is there a chance to upgrade the referenced MessagePack version in the Microsoft.AspNetCore.SignalR.Protocols.MessagePack project?

image

image

Expected Behavior

All tests should be green, regardless of compression type.

Steps To Reproduce

Code:

using MessagePack;
using MessagePack.Formatters;
using MessagePack.Resolvers;
using NUnit.Framework;

namespace MessagePackTest;

public class Tests
{
	[TestCase(MessagePack.MessagePackCompression.None)]
	[TestCase(MessagePack.MessagePackCompression.Lz4Block)]
	[TestCase(MessagePack.MessagePackCompression.Lz4BlockArray)]
	public void TestSerializationWithStandardOptions(MessagePackCompression compression)
	{
		Base instance = new Derived
		{
			Name = "Test Data"
		};
		TestSerialization(instance, MessagePackSerializerOptions.Standard, compression);
	}

	[TestCase(MessagePack.MessagePackCompression.None)]
	[TestCase(MessagePack.MessagePackCompression.Lz4Block)]
	[TestCase(MessagePack.MessagePackCompression.Lz4BlockArray)]
	public void TestSerializationWithAspNetOptions(MessagePackCompression compression)
	{
		Base instance = new Derived
		{
			Name = "Test Data"
		};
		var options = new Microsoft.AspNetCore.SignalR.MessagePackHubProtocolOptions().SerializerOptions;
		TestSerialization(instance, options, compression);
	}

	private static void TestSerialization<T>(T instance, MessagePackSerializerOptions options, MessagePackCompression compression)
	{
		options = options.WithResolver
			(
			CompositeResolver.Create(
				new IMessagePackFormatter[] {
					new BaseFormatter(),
					new DerivedFormatter(),
				},
				new[]
				{
					options.Resolver
				})
			);

		options = options
			.WithCompression(compression);
		var bytes = MessagePackSerializer.Serialize<T>(instance, options);
		var deserialized = MessagePackSerializer.Deserialize<T>(bytes, options);
		Assert.NotNull(deserialized);
	}
}

public class BaseFormatter : IMessagePackFormatter<Base>
{
	public Base Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
	{
		var typeName = options.Resolver.GetFormatterWithVerify<string>().Deserialize(ref reader, options);
		var type = typeof(Base).Assembly.GetType(typeName);
		var result = (Base)Activator.CreateInstance(type)!;
		// ... Fill properties
		return result;
	}

	public void Serialize(ref MessagePackWriter writer, Base value, MessagePackSerializerOptions options)
	{
		var type = value.GetType();
		if (type == typeof(Base))
		{
			options.Resolver.GetFormatterWithVerify<string>().Serialize(ref writer, value.Type, options);
		}
		else
		{
			MessagePackSerializer.Serialize(type, ref writer, value, options);
		}
	}
}

public class DerivedFormatter : IMessagePackFormatter<Derived>
{
	public Derived Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
	{
		return new()
		{
			Type = options.Resolver.GetFormatterWithVerify<string>().Deserialize(ref reader, options),
			Name = options.Resolver.GetFormatterWithVerify<string>().Deserialize(ref reader, options),
		};
	}

	public void Serialize(ref MessagePackWriter writer, Derived value, MessagePackSerializerOptions options)
	{
		options.Resolver.GetFormatterWithVerify<string>().Serialize(ref writer, value.Type, options);
		options.Resolver.GetFormatterWithVerify<string>().Serialize(ref writer, value.Name, options);
	}
}

public class Base
{
	public string? Type { get; set; } = typeof(Base).FullName;
}

public class Derived : Base
{
	public string? Name { get; set; }
	public Derived()
	{
		Type = typeof(Derived).FullName;
	}
}

csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>

    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
	  <!--<PackageReference Include="MessagePack" Version="2.4.59" />-->
	  <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="7.0.3" />
	  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
    <PackageReference Include="NUnit" Version="3.13.3" />
    <PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
    <PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
    <PackageReference Include="coverlet.collector" Version="3.1.2" />
  </ItemGroup>

</Project>

Exceptions (if any)

 TestSerializationWithAspNetOptions(Lz4BlockArray)
   Source: UnitTest1.cs line 25
   Duration: 20 ms

  Message: 
MessagePack.MessagePackSerializationException : Failed to deserialize MessagePackTest.Base value.
  ----> MessagePack.MessagePackSerializationException : Unexpected msgpack code 146 (fixarray) encountered.

  Stack Trace: 
MessagePackSerializer.Deserialize[T](MessagePackReader& reader, MessagePackSerializerOptions options)
MessagePackSerializer.Deserialize[T](ReadOnlyMemory`1 buffer, MessagePackSerializerOptions options, CancellationToken cancellationToken)
Tests.TestSerialization[T](T instance, MessagePackSerializerOptions options, MessagePackCompression compression) line 53
Tests.TestSerializationWithAspNetOptions(MessagePackCompression compression) line 32
InvokeStub_Tests.TestSerializationWithAspNetOptions(Object, Object, IntPtr*)
MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
--MessagePackSerializationException
MessagePackReader.ThrowInvalidCode(Byte code)
MessagePackReader.GetStringLengthInBytesSlow(Byte code)
NullableStringFormatter.Deserialize(MessagePackReader& reader, MessagePackSerializerOptions options)
BaseFormatter.Deserialize(MessagePackReader& reader, MessagePackSerializerOptions options) line 62
MessagePackSerializer.Deserialize[T](MessagePackReader& reader, MessagePackSerializerOptions options)

.NET Version

7.0.103

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-signalrIncludes: SignalR clients and servers

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions