151 lines
6.1 KiB
C#
151 lines
6.1 KiB
C#
#nullable enable
|
|
|
|
using System;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
namespace ShrinkNetwork
|
|
{
|
|
public sealed class ShrinkMessagePackNetworkSerializer : IShrinkNetworkSerializer
|
|
{
|
|
private readonly MethodInfo _serializeMethod;
|
|
private readonly MethodInfo _deserializeMethod;
|
|
private readonly object? _serializerOptions;
|
|
|
|
public ShrinkMessagePackNetworkSerializer()
|
|
{
|
|
var serializerType = Type.GetType("MessagePack.MessagePackSerializer, MessagePack");
|
|
if (serializerType == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"MessagePack assembly was not found. Please install MessagePack-CSharp before using ShrinkMessagePackNetworkSerializer.");
|
|
}
|
|
|
|
_serializerOptions = ResolveSerializerOptions(serializerType.Assembly);
|
|
|
|
var serializeMethod = serializerType
|
|
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
|
.FirstOrDefault(m =>
|
|
{
|
|
if (m.Name != "Serialize")
|
|
return false;
|
|
var parameters = m.GetParameters();
|
|
return parameters.Length >= 2 &&
|
|
parameters[0].ParameterType == typeof(Type) &&
|
|
parameters[1].ParameterType == typeof(object);
|
|
});
|
|
|
|
var deserializeMethod = serializerType
|
|
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
|
.FirstOrDefault(m =>
|
|
{
|
|
if (m.Name != "Deserialize")
|
|
return false;
|
|
var parameters = m.GetParameters();
|
|
return parameters.Length >= 2 &&
|
|
parameters[0].ParameterType == typeof(Type) &&
|
|
(parameters[1].ParameterType == typeof(byte[]) ||
|
|
parameters[1].ParameterType == typeof(ReadOnlyMemory<byte>));
|
|
});
|
|
|
|
if (serializeMethod == null || deserializeMethod == null)
|
|
throw new MissingMethodException("MessagePack serialize/deserialize API not found.");
|
|
|
|
_serializeMethod = serializeMethod;
|
|
_deserializeMethod = deserializeMethod;
|
|
}
|
|
|
|
public byte[] Serialize(object value)
|
|
{
|
|
if (value == null)
|
|
return Array.Empty<byte>();
|
|
|
|
var parameters = BuildParameters(_serializeMethod, value.GetType(), value, _serializerOptions);
|
|
return (byte[])_serializeMethod.Invoke(null, parameters)!;
|
|
}
|
|
|
|
public object Deserialize(byte[] payload, Type type)
|
|
{
|
|
payload ??= Array.Empty<byte>();
|
|
var parameters = BuildParameters(_deserializeMethod, type, payload, _serializerOptions);
|
|
return _deserializeMethod.Invoke(null, parameters)
|
|
?? throw new InvalidOperationException($"MessagePack returned null for type {type.FullName}.");
|
|
}
|
|
|
|
public T Deserialize<T>(byte[] payload)
|
|
{
|
|
return (T)Deserialize(payload, typeof(T));
|
|
}
|
|
|
|
private static object?[] BuildParameters(MethodInfo method, Type type, object valueOrBytes, object? serializerOptions)
|
|
{
|
|
var parameters = method.GetParameters();
|
|
var args = new object?[parameters.Length];
|
|
|
|
if (parameters.Length > 0)
|
|
args[0] = type;
|
|
if (parameters.Length > 1)
|
|
args[1] = ConvertPrimaryArgument(parameters[1].ParameterType, valueOrBytes);
|
|
|
|
for (var i = 2; i < parameters.Length; i++)
|
|
{
|
|
args[i] = ResolveAdditionalArgument(parameters[i], serializerOptions);
|
|
}
|
|
|
|
return args;
|
|
}
|
|
|
|
private static object? ResolveAdditionalArgument(ParameterInfo parameter, object? serializerOptions)
|
|
{
|
|
if (serializerOptions != null && parameter.ParameterType.IsInstanceOfType(serializerOptions))
|
|
return serializerOptions;
|
|
|
|
return parameter.HasDefaultValue
|
|
? parameter.DefaultValue
|
|
: GetDefault(parameter.ParameterType);
|
|
}
|
|
|
|
private static object ConvertPrimaryArgument(Type parameterType, object value)
|
|
{
|
|
if (parameterType == typeof(ReadOnlyMemory<byte>) && value is byte[] bytes)
|
|
return new ReadOnlyMemory<byte>(bytes);
|
|
|
|
return value;
|
|
}
|
|
|
|
private static object? ResolveSerializerOptions(Assembly serializerAssembly)
|
|
{
|
|
var contractlessResolverType = serializerAssembly.GetType("MessagePack.Resolvers.ContractlessStandardResolver");
|
|
if (contractlessResolverType != null)
|
|
{
|
|
var optionsField = contractlessResolverType.GetField("Options",
|
|
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
|
var options = optionsField?.GetValue(null);
|
|
if (options != null)
|
|
return options;
|
|
|
|
var instanceField = contractlessResolverType.GetField("Instance",
|
|
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
|
var instance = instanceField?.GetValue(null);
|
|
if (instance != null)
|
|
{
|
|
var optionsType = serializerAssembly.GetType("MessagePack.MessagePackSerializerOptions");
|
|
var standardProperty = optionsType?.GetProperty("Standard", BindingFlags.Public | BindingFlags.Static);
|
|
var standardOptions = standardProperty?.GetValue(null);
|
|
var withResolverMethod = optionsType?.GetMethod("WithResolver", BindingFlags.Public | BindingFlags.Instance);
|
|
var resolvedOptions = withResolverMethod?.Invoke(standardOptions, new[] { instance });
|
|
if (resolvedOptions != null)
|
|
return resolvedOptions;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static object? GetDefault(Type type)
|
|
{
|
|
return type.IsValueType ? Activator.CreateInstance(type) : null;
|
|
}
|
|
}
|
|
}
|