我无法回答问题#1 或#2,但我可以对问题#3 说一点。首先,快速概述 protobuf-net。
在线上,Google 的协议缓冲区是一种消息格式而不是序列化格式。消息由键/值对序列组成,其中值可以是原始数据类型或嵌套序列。它有点类似于JSON,除了键是整数而不是名称字符串。该格式与语言无关(因此不包含特定于语言的类型信息),并且具有足够的自文档性,可以跳过意外属性的值,并且可以推断原始值的数据类型。
protobuf-net是一个基于合约的 .NET 类型序列化程序,可从协议缓冲区格式序列化到协议缓冲区格式。“基于契约”意味着序列化程序在消息属性和 .Net 属性之间构建一个映射(也称为“契约”),然后使用该契约来构造和反序列化 .Net 对象。要反序列化的“根”对象由反序列化调用指定,例如
var rootObject = ProtoBuf.Serializer.Deserialize<RootObjectType>(stream)
可以在此处和此处找到其使用的基本说明。
protubuf-net 仅有限地使用不安全代码 - 我相信目前仅用于浮点值的有效反序列化。这可以通过使用FEAT_SAFE定义的构建来禁用。因此,格式错误的消息覆盖堆或堆栈的可能性被最小化。Protobuf-net 不与Google 实现的协议缓冲区共享代码库。
那么,protobuf-net 是如何构造它的合约(the RuntimeTypeModel)来将 .Net 类型映射到消息的呢?由于消息键是整数而不是字符串,因此使用 .Net 公共属性或字段名称的规范选择不起作用。相反,有以下选项:
该类型可以使用[ProtoContract]和[ProtoMember()]属性进行注释,例如文档中的示例:
[ProtoContract]
class Person {
[ProtoMember(1)]
public string Name {get;set:}
[ProtoMember(2)]
public Address Address {get;set;}
}
[ProtoContract]
class Address {
[ProtoMember(1)]
public string Line1 {get;set;}
[ProtoMember(2)]
public string Line2 {get;set;}
}
标记为的属性和字段[ProtoMember]将使用反射发现并序列化。其他人不会。
可以以编程方式构建合约,如此处所示。
默认情况下,protobuf-net 合同生成是可选的。即,仅对指定的属性和字段进行序列化。但是,可以通过设置ProtoContractAttribute.ImplicitFields为以下值之一来覆盖它:
public enum ImplicitFields
{
/// <summary>
/// No members are serialized implicitly; all members require a suitable
/// attribute such as [ProtoMember]. This is the recmomended mode for
/// most scenarios.
/// </summary>
None = 0,
/// <summary>
/// Public properties and fields are eligible for implicit serialization;
/// this treats the public API as a contract. Ordering beings from ImplicitFirstTag.
/// </summary>
AllPublic= 1,
/// <summary>
/// Public and non-public fields are eligible for implicit serialization;
/// this acts as a state/implementation serializer. Ordering beings from ImplicitFirstTag.
/// </summary>
AllFields = 2
}
protobuf-net 还可以使用数据合约序列化器和XmlSerializer属性来自动创建合约。这可以通过属性禁用ProtoContractAttribute.UseProtoMembersOnly。
虽然 protobuf-net 最初设计为代码优先序列化程序(您定义类型,然后定义如何序列化它们),但它确实支持从消息原型生成 c# 代码。有关详细信息,请参见此处。
因此,如您所见,在序列化期间注入不需要的类型和代码的可能性受到设计的限制,因为合同而不是消息控制构造对象的类型。这可以通过以下建议进一步限制:
不要使用[ProtoMember(DynamicType = true)].
protobuf-net 实现了对标准的扩展,允许反序列化 .Net 类型以在消息本身中指定 - 破坏了开箱即用标准的相对安全性。默认情况下禁用,但可以通过设置启用DynamicType = true。不要使用此功能,因为这是在反序列化期间注入意外对象的最明显方式。
如果您担心团队中的其他开发人员可能会使用此功能(因为它确实使序列化多态类型变得非常容易),您可以TypeModel.DynamicTypeFormatting在序列化程序尝试解析动态类型或动态类型时设置一个引发异常的事件输入名称,例如:
ProtoBuf.Meta.TypeFormatEventHandler handler = (o, e) => { throw new NotSupportedException("Dynamic typing is not allowed."); };
ProtoBuf.Meta.RuntimeTypeModel.Default.DynamicTypeFormatting += handler;
请务必在您实际使用的类型模型上设置事件,正如此处指出的那样。
小心继承。protobuf-net 确实支持继承。注意不要让不需要的派生类型潜入合约。例如,给定以下内容:
[ProtoContract]
[ProtoInclude(7, typeof(ValidatedSqlQuery))]
[ProtoInclude(8, typeof(DebugSqlCommand))]
public abstract class SqlCommand
{
}
/// <summary>
/// Represents a fully validated, parameterized SQL query
/// </summary>
[ProtoContract]
public class ValidatedSqlQuery
{
}
/// <summary>
/// For testing purposes, execute any command against the database.
/// </summary>
[ProtoContract]
class DebugSqlCommand
{
}
a 有可能通过适当的消息DebugSqlCommand在 a 中构建,这可能会导致损坏。List<SqlCommand>
序列化属性,而不是字段。不要使用ImplicitFields.AllFields. ImplicitFields.AllPublic尽可能避免使用。
验证所有属性设置器中的数据。如果您不想对“普通”设置器进行验证,则可以使用特定于序列化的设置器,例如:
[ProtoContract]
class Contact {
public string Email { get; set; }
[ProtoMember(1)]
string SerializedEmail {
get { return Email; }
set
{
// Make sure the email is well-formed, throw an exception if not
ValidateEmail(value);
Email = value;
}
}
}
[OnDeserialized]在复杂的验证场景中,您可以在回调中验证多个相关属性。
最后,您的反序列化代码仅与您反序列化的对象一样安全!不要序列化包含或使用不安全代码的对象。不允许序列化调试对象。