using ProtoBuf.Meta; using System; using System.IO; #if !NO_GENERICS using System.Collections.Generic; #endif #if FEAT_IKVM using Type = IKVM.Reflection.Type; using IKVM.Reflection; #else using System.Reflection; #endif namespace ProtoBuf { /// /// Provides protocol-buffer serialization capability for concrete, attributed types. This /// is a *default* model, but custom serializer models are also supported. /// /// /// Protocol-buffer serialization is a compact binary format, designed to take /// advantage of sparse data and knowledge of specific data types; it is also /// extensible, allowing a type to be deserialized / merged even if some data is /// not recognised. /// public #if FX11 sealed #else static #endif class Serializer { #if FX11 private Serializer() { } // not a static class for C# 1.2 reasons #endif #if !NO_RUNTIME && !NO_GENERICS /// /// Suggest a .proto definition for the given type /// /// The type to generate a .proto definition for /// The .proto definition as a string public static string GetProto() { return RuntimeTypeModel.Default.GetSchema(RuntimeTypeModel.Default.MapType(typeof(T))); } /// /// Create a deep clone of the supplied instance; any sub-items are also cloned. /// public static T DeepClone(T instance) { return instance == null ? instance : (T)RuntimeTypeModel.Default.DeepClone(instance); } /// /// Applies a protocol-buffer stream to an existing instance. /// /// The type being merged. /// The existing instance to be modified (can be null). /// The binary stream to apply to the instance (cannot be null). /// The updated instance; this may be different to the instance argument if /// either the original instance was null, or the stream defines a known sub-type of the /// original instance. public static T Merge(Stream source, T instance) { return (T)RuntimeTypeModel.Default.Deserialize(source, instance, typeof(T)); } /// /// Creates a new instance from a protocol-buffer stream /// /// The type to be created. /// The binary stream to apply to the new instance (cannot be null). /// A new, initialized instance. public static T Deserialize(Stream source) { return (T) RuntimeTypeModel.Default.Deserialize(source, null, typeof(T)); } /// /// Creates a new instance from a protocol-buffer stream /// /// The type to be created. /// The binary stream to apply to the new instance (cannot be null). /// A new, initialized instance. public static object Deserialize(System.Type type, Stream source) { return RuntimeTypeModel.Default.Deserialize(source, null, type); } /// /// Writes a protocol-buffer representation of the given instance to the supplied stream. /// /// The existing instance to be serialized (cannot be null). /// The destination stream to write to. public static void Serialize(Stream destination, T instance) { if(instance != null) { RuntimeTypeModel.Default.Serialize(destination, instance); } } /// /// Serializes a given instance and deserializes it as a different type; /// this can be used to translate between wire-compatible objects (where /// two .NET types represent the same data), or to promote/demote a type /// through an inheritance hierarchy. /// /// No assumption of compatibility is made between the types. /// The type of the object being copied. /// The type of the new object to be created. /// The existing instance to use as a template. /// A new instane of type TNewType, with the data from TOldType. public static TTo ChangeType(TFrom instance) { using (MemoryStream ms = new MemoryStream()) { Serialize(ms, instance); ms.Position = 0; return Deserialize(ms); } } #if PLAT_BINARYFORMATTER && !(WINRT || PHONE8 || COREFX) /// /// Writes a protocol-buffer representation of the given instance to the supplied SerializationInfo. /// /// The type being serialized. /// The existing instance to be serialized (cannot be null). /// The destination SerializationInfo to write to. public static void Serialize(System.Runtime.Serialization.SerializationInfo info, T instance) where T : class, System.Runtime.Serialization.ISerializable { Serialize(info, new System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.Persistence), instance); } /// /// Writes a protocol-buffer representation of the given instance to the supplied SerializationInfo. /// /// The type being serialized. /// The existing instance to be serialized (cannot be null). /// The destination SerializationInfo to write to. /// Additional information about this serialization operation. public static void Serialize(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context, T instance) where T : class, System.Runtime.Serialization.ISerializable { // note: also tried byte[]... it doesn't perform hugely well with either (compared to regular serialization) if (info == null) throw new ArgumentNullException("info"); if (instance == null) throw new ArgumentNullException("instance"); if (instance.GetType() != typeof(T)) throw new ArgumentException("Incorrect type", "instance"); using (MemoryStream ms = new MemoryStream()) { RuntimeTypeModel.Default.Serialize(ms, instance, context); info.AddValue(ProtoBinaryField, ms.ToArray()); } } #endif #if PLAT_XMLSERIALIZER /// /// Writes a protocol-buffer representation of the given instance to the supplied XmlWriter. /// /// The type being serialized. /// The existing instance to be serialized (cannot be null). /// The destination XmlWriter to write to. public static void Serialize(System.Xml.XmlWriter writer, T instance) where T : System.Xml.Serialization.IXmlSerializable { if (writer == null) throw new ArgumentNullException("writer"); if (instance == null) throw new ArgumentNullException("instance"); using (MemoryStream ms = new MemoryStream()) { Serializer.Serialize(ms, instance); writer.WriteBase64(Helpers.GetBuffer(ms), 0, (int)ms.Length); } } /// /// Applies a protocol-buffer from an XmlReader to an existing instance. /// /// The type being merged. /// The existing instance to be modified (cannot be null). /// The XmlReader containing the data to apply to the instance (cannot be null). public static void Merge(System.Xml.XmlReader reader, T instance) where T : System.Xml.Serialization.IXmlSerializable { if (reader == null) throw new ArgumentNullException("reader"); if (instance == null) throw new ArgumentNullException("instance"); const int LEN = 4096; byte[] buffer = new byte[LEN]; int read; using (MemoryStream ms = new MemoryStream()) { int depth = reader.Depth; while(reader.Read() && reader.Depth > depth) { if (reader.NodeType == System.Xml.XmlNodeType.Text) { while ((read = reader.ReadContentAsBase64(buffer, 0, LEN)) > 0) { ms.Write(buffer, 0, read); } if (reader.Depth <= depth) break; } } ms.Position = 0; Serializer.Merge(ms, instance); } } #endif private const string ProtoBinaryField = "proto"; #if PLAT_BINARYFORMATTER && !NO_GENERICS && !(WINRT || PHONE8 || COREFX) /// /// Applies a protocol-buffer from a SerializationInfo to an existing instance. /// /// The type being merged. /// The existing instance to be modified (cannot be null). /// The SerializationInfo containing the data to apply to the instance (cannot be null). public static void Merge(System.Runtime.Serialization.SerializationInfo info, T instance) where T : class, System.Runtime.Serialization.ISerializable { Merge(info, new System.Runtime.Serialization.StreamingContext(System.Runtime.Serialization.StreamingContextStates.Persistence), instance); } /// /// Applies a protocol-buffer from a SerializationInfo to an existing instance. /// /// The type being merged. /// The existing instance to be modified (cannot be null). /// The SerializationInfo containing the data to apply to the instance (cannot be null). /// Additional information about this serialization operation. public static void Merge(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context, T instance) where T : class, System.Runtime.Serialization.ISerializable { // note: also tried byte[]... it doesn't perform hugely well with either (compared to regular serialization) if (info == null) throw new ArgumentNullException("info"); if (instance == null) throw new ArgumentNullException("instance"); if (instance.GetType() != typeof(T)) throw new ArgumentException("Incorrect type", "instance"); byte[] buffer = (byte[])info.GetValue(ProtoBinaryField, typeof(byte[])); using (MemoryStream ms = new MemoryStream(buffer)) { T result = (T)RuntimeTypeModel.Default.Deserialize(ms, instance, typeof(T), context); if (!ReferenceEquals(result, instance)) { throw new ProtoException("Deserialization changed the instance; cannot succeed."); } } } #endif #if !NO_GENERICS /// /// Precompiles the serializer for a given type. /// public static void PrepareSerializer() { #if FEAT_COMPILER RuntimeTypeModel model = RuntimeTypeModel.Default; model[model.MapType(typeof(T))].CompileInPlace(); #endif } #if PLAT_BINARYFORMATTER && !(WINRT || PHONE8 || COREFX) /// /// Creates a new IFormatter that uses protocol-buffer [de]serialization. /// /// The type of object to be [de]deserialized by the formatter. /// A new IFormatter to be used during [de]serialization. public static System.Runtime.Serialization.IFormatter CreateFormatter() { #if FEAT_IKVM throw new NotSupportedException(); #else return RuntimeTypeModel.Default.CreateFormatter(typeof(T)); #endif } #endif /// /// Reads a sequence of consecutive length-prefixed items from a stream, using /// either base-128 or fixed-length prefixes. Base-128 prefixes with a tag /// are directly comparable to serializing multiple items in succession /// (use the tag to emulate the implicit behavior /// when serializing a list/array). When a tag is /// specified, any records with different tags are silently omitted. The /// tag is ignored. The tag is ignored for fixed-length prefixes. /// /// The type of object to deserialize. /// The binary stream containing the serialized records. /// The prefix style used in the data. /// The tag of records to return (if non-positive, then no tag is /// expected and all records are returned). /// The sequence of deserialized objects. public static IEnumerable DeserializeItems(Stream source, PrefixStyle style, int fieldNumber) { return RuntimeTypeModel.Default.DeserializeItems(source, style, fieldNumber); } /// /// Creates a new instance from a protocol-buffer stream that has a length-prefix /// on data (to assist with network IO). /// /// The type to be created. /// The binary stream to apply to the new instance (cannot be null). /// How to encode the length prefix. /// A new, initialized instance. public static T DeserializeWithLengthPrefix(Stream source, PrefixStyle style) { return DeserializeWithLengthPrefix(source, style, 0); } /// /// Creates a new instance from a protocol-buffer stream that has a length-prefix /// on data (to assist with network IO). /// /// The type to be created. /// The binary stream to apply to the new instance (cannot be null). /// How to encode the length prefix. /// The expected tag of the item (only used with base-128 prefix style). /// A new, initialized instance. public static T DeserializeWithLengthPrefix(Stream source, PrefixStyle style, int fieldNumber) { RuntimeTypeModel model = RuntimeTypeModel.Default; return (T)model.DeserializeWithLengthPrefix(source, null, model.MapType(typeof(T)), style, fieldNumber); } /// /// Applies a protocol-buffer stream to an existing instance, using length-prefixed /// data - useful with network IO. /// /// The type being merged. /// The existing instance to be modified (can be null). /// The binary stream to apply to the instance (cannot be null). /// How to encode the length prefix. /// The updated instance; this may be different to the instance argument if /// either the original instance was null, or the stream defines a known sub-type of the /// original instance. public static T MergeWithLengthPrefix(Stream source, T instance, PrefixStyle style) { RuntimeTypeModel model = RuntimeTypeModel.Default; return (T)model.DeserializeWithLengthPrefix(source, instance, model.MapType(typeof(T)), style, 0); } /// /// Writes a protocol-buffer representation of the given instance to the supplied stream, /// with a length-prefix. This is useful for socket programming, /// as DeserializeWithLengthPrefix/MergeWithLengthPrefix can be used to read the single object back /// from an ongoing stream. /// /// The type being serialized. /// The existing instance to be serialized (cannot be null). /// How to encode the length prefix. /// The destination stream to write to. public static void SerializeWithLengthPrefix(Stream destination, T instance, PrefixStyle style) { SerializeWithLengthPrefix(destination, instance, style, 0); } /// /// Writes a protocol-buffer representation of the given instance to the supplied stream, /// with a length-prefix. This is useful for socket programming, /// as DeserializeWithLengthPrefix/MergeWithLengthPrefix can be used to read the single object back /// from an ongoing stream. /// /// The type being serialized. /// The existing instance to be serialized (cannot be null). /// How to encode the length prefix. /// The destination stream to write to. /// The tag used as a prefix to each record (only used with base-128 style prefixes). public static void SerializeWithLengthPrefix(Stream destination, T instance, PrefixStyle style, int fieldNumber) { RuntimeTypeModel model = RuntimeTypeModel.Default; model.SerializeWithLengthPrefix(destination, instance, model.MapType(typeof(T)), style, fieldNumber); } #endif /// Indicates the number of bytes expected for the next message. /// The stream containing the data to investigate for a length. /// The algorithm used to encode the length. /// The length of the message, if it could be identified. /// True if a length could be obtained, false otherwise. public static bool TryReadLengthPrefix(Stream source, PrefixStyle style, out int length) { int fieldNumber, bytesRead; length = ProtoReader.ReadLengthPrefix(source, false, style, out fieldNumber, out bytesRead); return bytesRead > 0; } /// Indicates the number of bytes expected for the next message. /// The buffer containing the data to investigate for a length. /// The offset of the first byte to read from the buffer. /// The number of bytes to read from the buffer. /// The algorithm used to encode the length. /// The length of the message, if it could be identified. /// True if a length could be obtained, false otherwise. public static bool TryReadLengthPrefix(byte[] buffer, int index, int count, PrefixStyle style, out int length) { using (Stream source = new MemoryStream(buffer, index, count)) { return TryReadLengthPrefix(source, style, out length); } } #endif /// /// The field number that is used as a default when serializing/deserializing a list of objects. /// The data is treated as repeated message with field number 1. /// public const int ListItemTag = 1; #if !NO_RUNTIME /// /// Provides non-generic access to the default serializer. /// public #if FX11 sealed #else static #endif class NonGeneric { #if FX11 private NonGeneric() { } // not a static class for C# 1.2 reasons #endif /// /// Create a deep clone of the supplied instance; any sub-items are also cloned. /// public static object DeepClone(object instance) { return instance == null ? null : RuntimeTypeModel.Default.DeepClone(instance); } /// /// Writes a protocol-buffer representation of the given instance to the supplied stream. /// /// The existing instance to be serialized (cannot be null). /// The destination stream to write to. public static void Serialize(Stream dest, object instance) { if (instance != null) { RuntimeTypeModel.Default.Serialize(dest, instance); } } /// /// Creates a new instance from a protocol-buffer stream /// /// The type to be created. /// The binary stream to apply to the new instance (cannot be null). /// A new, initialized instance. public static object Deserialize(System.Type type, Stream source) { return RuntimeTypeModel.Default.Deserialize(source, null, type); } /// Applies a protocol-buffer stream to an existing instance. /// The existing instance to be modified (cannot be null). /// The binary stream to apply to the instance (cannot be null). /// The updated instance public static object Merge(Stream source, object instance) { if (instance == null) throw new ArgumentNullException("instance"); return RuntimeTypeModel.Default.Deserialize(source, instance, instance.GetType(), null); } /// /// Writes a protocol-buffer representation of the given instance to the supplied stream, /// with a length-prefix. This is useful for socket programming, /// as DeserializeWithLengthPrefix/MergeWithLengthPrefix can be used to read the single object back /// from an ongoing stream. /// /// The existing instance to be serialized (cannot be null). /// How to encode the length prefix. /// The destination stream to write to. /// The tag used as a prefix to each record (only used with base-128 style prefixes). public static void SerializeWithLengthPrefix(Stream destination, object instance, PrefixStyle style, int fieldNumber) { if (instance == null) throw new ArgumentNullException("instance"); RuntimeTypeModel model = RuntimeTypeModel.Default; model.SerializeWithLengthPrefix(destination, instance, model.MapType(instance.GetType()), style, fieldNumber); } /// /// Applies a protocol-buffer stream to an existing instance (or null), using length-prefixed /// data - useful with network IO. /// /// The existing instance to be modified (can be null). /// The binary stream to apply to the instance (cannot be null). /// How to encode the length prefix. /// Used to resolve types on a per-field basis. /// The updated instance; this may be different to the instance argument if /// either the original instance was null, or the stream defines a known sub-type of the /// original instance. public static bool TryDeserializeWithLengthPrefix(Stream source, PrefixStyle style, TypeResolver resolver, out object value) { value = RuntimeTypeModel.Default.DeserializeWithLengthPrefix(source, null, null, style, 0, resolver); return value != null; } /// /// Indicates whether the supplied type is explicitly modelled by the model /// public static bool CanSerialize(Type type) { return RuntimeTypeModel.Default.IsDefined(type); } } /// /// Global switches that change the behavior of protobuf-net /// public #if FX11 sealed #else static #endif class GlobalOptions { #if FX11 private GlobalOptions() { } // not a static class for C# 1.2 reasons #endif /// /// /// [Obsolete("Please use RuntimeTypeModel.Default.InferTagFromNameDefault instead (or on a per-model basis)", false)] public static bool InferTagFromName { get { return RuntimeTypeModel.Default.InferTagFromNameDefault; } set { RuntimeTypeModel.Default.InferTagFromNameDefault = value; } } } #endif /// /// Maps a field-number to a type /// public delegate Type TypeResolver(int fieldNumber); /// /// Releases any internal buffers that have been reserved for efficiency; this does not affect any serialization /// operations; simply: it can be used (optionally) to release the buffers for garbage collection (at the expense /// of having to re-allocate a new buffer for the next operation, rather than re-use prior buffers). /// public static void FlushPool() { BufferPool.Flush(); } } }