如何在不引发异常的情况下安全地反序列化 MessagePack 消息?

How to safely deserialize MessagePack messages without throwing exceptions?

提问人:Szyszka947 提问时间:9/18/2023 更新时间:9/24/2023 访问量:221

问:

这是我的DTO:

public class InvocationMessage
{
    public string MethodName { get; set; }
    public object?[]? Args { get; set; }
}

最初我使用MessagePackSerializer.Serialize(Msg, ContractlessStandardResolver.Options);

然后,在反序列化时,我想从具体类型转换为具体类型。如果没有自定义格式化程序,这似乎是不可能的。所以我写了一个:objectArgs

public InvocationMessage Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
    var result = new InvocationMessage
    {
        MethodName = reader.ReadString()
    };

    // Hardcoded for testing purpose
    var argTypes = new Type[] { typeof(SomeType) };
    if (reader.TryReadArrayHeader(out int count))
    {
        result.Args = new object?[count];

        for (int i = 0; i < argTypes.Length; i++)
        {
            result.Args[i] = MessagePackSerializer.Deserialize(argTypes[i], ref reader,
                ContractlessStandardResolver.Options.WithSecurity(MessagePackSecurity.UntrustedData));
        }
    }

    return result;
}

public void Serialize(ref MessagePackWriter writer, InvocationMessage value, MessagePackSerializerOptions options)
{
    var methodName = Encoding.UTF8.GetBytes(value.MethodName);

    writer.WriteString(methodName);

    if (value.Args is not null && value.Args.Length > 0)
    {
        writer.WriteArrayHeader(value.Args.Length);

        foreach (var arg in value.Args)
        {
            MessagePackSerializer.Serialize(ref writer, arg, ContractlessStandardResolver.Options);
        }
    }
}

不幸的是,当我尝试反序列化时,总是抛出异常,例如:

var msg = new InvocationMessage
{
    MethodName = "sometestname",
    Args = new object?[]
        {
            new 
            {
                Test = 3
            },
            new
            {
                ReceiverId = "12ds",
                Content = "s",
            },
            new 
            {
                Test2 = 5
            },
        }
};

这是因为我想将一些未知类型反序列化到我的 ,或者因为我想要属性并得到 .在很多情况下,它都会抛出。SomeTypeSomeTypestringReceiverIdint

但我需要避免抛出,因为这对我的应用程序来说是一个很大的性能问题。没有类似方法的东西,所以我不知道我能做什么。TryDeserialize

C# 反序列化 msgpack

评论

0赞 FFranz 9/20/2023
你的班级是什么样子的?SomeType
0赞 Szyszka947 9/20/2023
通常,在反序列化时,我会得到所有“结果”的类型。Args“按顺序排列。我不知道这些类型是什么.例如,这个“SomeType”,它的外观并不重要,因为正如我在实际代码中所说,我也不知道这些类型的外观,它们是使用我不知道某些开发人员声明的方法的反射提供的。
0赞 FFranz 9/21/2023
如果您不知道要期待哪些类型,那么您希望如何将它们反序列化为具体类型?如果只想按所有内容出现的顺序反序列化所有内容,则可以使用 和 属性(有关详细信息,请参阅 github.com/MessagePack-CSharp/...)。对不起,如果没有关于您的应用程序的目的、抛出什么异常以及您收到哪些内容的进一步信息,人们可能无法完全理解您的问题。ArgsMessagePackSerializerArgsMessagePackObjectKeyArgs
0赞 Szyszka947 9/21/2023
它类似于 SignalR 集线器。使用它的人定义中心方法,而 SignalR 不知道这些方法中将使用哪些参数。但它必须反序列化这些存储在 .SignalR 只知道 的位置应该是什么类型。但是发送的对象可能格式不正确(例如被攻击者攻击),然后会抛出异常。我有类比的情况,我必须避免例外。object?[]iArgs
0赞 Nikolay 9/23/2023
@Szyszka947 您是否尝试过将序列化更改为无类型?github.com/MessagePack-CSharp/MessagePack-CSharp#typeless

答:

2赞 Freeman 9/24/2023 #1

我认为最好编辑您的反序列化代码以使用处理未知类型或类型不匹配的自定义格式化程序,因此首先为您的定义自定义格式化程序InvocationMessage

public class InvocationMessageFormatter : IMessagePackFormatter<InvocationMessage>
{
    public InvocationMessage Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
    {
        var result = new InvocationMessage
        {
            MethodName = reader.ReadString()
        };

        if (reader.TryReadArrayHeader(out int count))
        {
            result.Args = new object?[count];

            for (int i = 0; i < count; i++)
            {
                result.Args[i] = DeserializeObject(ref reader, options);
            }
        }

        return result;
    }

    public void Serialize(ref MessagePackWriter writer, InvocationMessage value, MessagePackSerializerOptions options)
    {
        writer.WriteString(value.MethodName);

        if (value.Args is not null && value.Args.Length > 0)
        {
            writer.WriteArrayHeader(value.Args.Length);

            foreach (var arg in value.Args)
            {
                SerializeObject(ref writer, arg, options);
            }
        }
    }

    private object? DeserializeObject(ref MessagePackReader reader, MessagePackSerializerOptions options)
    {
        /* you can return null or a default value for unknown types,
         or convert the object to the expected type if possible.
         I'll simply return null for unknown types */
        return null;
    }

    private void SerializeObject(ref MessagePackWriter writer, object? value, MessagePackSerializerOptions options)
    {
        //you can throw an exception or convert the object to a compatible type,for example, I'll do nothing when serializing unknown types
    }
}

现在,您可以轻松地在序列化/反序列化代码中注册自定义格式化程序

var options = MessagePackSerializerOptions.Standard.WithResolver(ContractlessStandardResolver.Options);
options = options.WithFormatter<InvocationMessage>(new InvocationMessageFormatter());

//serialize
var serializedBytes = MessagePackSerializer.Serialize(msg, options);

//deserialize
var deserializedMsg = MessagePackSerializer.Deserialize<InvocationMessage>(serializedBytes, options);

评论

0赞 Szyszka947 9/24/2023
但是,如何确保某些字节表示有效类型呢?我知道,我可以捕捉到异常,但我必须避免它们
0赞 Freeman 9/24/2023
许多方法,例如,在反序列化之前,可以对序列化数据执行一些基本检查,以确保其有效。例如,可以检查字节数组是否为 null 或为空,或者它是否具有最小长度,或者在反序列化时,从字节中读取类型标识符并检查它是否与预期类型匹配。如果它们不匹配,您可以相应地处理错误,依此类推!
0赞 Szyszka947 9/26/2023
但这不会有效,因为在我的情况下,我可能需要使用反射:/它扼杀了 MessagePack 的优势(性能)。