using System; using System.IO; using System.Text; using ProtoBuf.Meta; #if MF using OverflowException = System.ApplicationException; #endif #if FEAT_IKVM using Type = IKVM.Reflection.Type; #endif namespace ProtoBuf { /// /// Represents an output stream for writing protobuf data. /// /// Why is the API backwards (static methods with writer arguments)? /// See: http://marcgravell.blogspot.com/2010/03/last-will-be-first-and-first-will-be.html /// public sealed class ProtoWriter : IDisposable { private Stream dest; TypeModel model; /// /// Write an encapsulated sub-object, using the supplied unique key (reprasenting a type). /// /// The object to write. /// The key that uniquely identifies the type within the model. /// The destination. public static void WriteObject(object value, int key, ProtoWriter writer) { #if FEAT_IKVM throw new NotSupportedException(); #else if (writer == null) throw new ArgumentNullException("writer"); if (writer.model == null) { throw new InvalidOperationException("Cannot serialize sub-objects unless a model is provided"); } SubItemToken token = StartSubItem(value, writer); if (key >= 0) { writer.model.Serialize(key, value, writer); } else if (writer.model != null && writer.model.TrySerializeAuxiliaryType(writer, value.GetType(), DataFormat.Default, Serializer.ListItemTag, value, false)) { // all ok } else { TypeModel.ThrowUnexpectedType(value.GetType()); } EndSubItem(token, writer); #endif } /// /// Write an encapsulated sub-object, using the supplied unique key (reprasenting a type) - but the /// caller is asserting that this relationship is non-recursive; no recursion check will be /// performed. /// /// The object to write. /// The key that uniquely identifies the type within the model. /// The destination. public static void WriteRecursionSafeObject(object value, int key, ProtoWriter writer) { if (writer == null) throw new ArgumentNullException("writer"); if (writer.model == null) { throw new InvalidOperationException("Cannot serialize sub-objects unless a model is provided"); } SubItemToken token = StartSubItem(null, writer); writer.model.Serialize(key, value, writer); EndSubItem(token, writer); } internal static void WriteObject(object value, int key, ProtoWriter writer, PrefixStyle style, int fieldNumber) { #if FEAT_IKVM throw new NotSupportedException(); #else if (writer.model == null) { throw new InvalidOperationException("Cannot serialize sub-objects unless a model is provided"); } if (writer.wireType != WireType.None) throw ProtoWriter.CreateException(writer); switch (style) { case PrefixStyle.Base128: writer.wireType = WireType.String; writer.fieldNumber = fieldNumber; if (fieldNumber > 0) WriteHeaderCore(fieldNumber, WireType.String, writer); break; case PrefixStyle.Fixed32: case PrefixStyle.Fixed32BigEndian: writer.fieldNumber = 0; writer.wireType = WireType.Fixed32; break; default: throw new ArgumentOutOfRangeException("style"); } SubItemToken token = StartSubItem(value, writer, true); if (key < 0) { if (!writer.model.TrySerializeAuxiliaryType(writer, value.GetType(), DataFormat.Default, Serializer.ListItemTag, value, false)) { TypeModel.ThrowUnexpectedType(value.GetType()); } } else { writer.model.Serialize(key, value, writer); } EndSubItem(token, writer, style); #endif } internal int GetTypeKey(ref Type type) { return model.GetKey(ref type); } private readonly NetObjectCache netCache = new NetObjectCache(); internal NetObjectCache NetCache { get { return netCache;} } private int fieldNumber, flushLock; WireType wireType; internal WireType WireType { get { return wireType; } } /// /// Writes a field-header, indicating the format of the next data we plan to write. /// public static void WriteFieldHeader(int fieldNumber, WireType wireType, ProtoWriter writer) { if (writer == null) throw new ArgumentNullException("writer"); if (writer.wireType != WireType.None) throw new InvalidOperationException("Cannot write a " + wireType.ToString() + " header until the " + writer.wireType.ToString() + " data has been written"); if(fieldNumber < 0) throw new ArgumentOutOfRangeException("fieldNumber"); #if DEBUG switch (wireType) { // validate requested header-type case WireType.Fixed32: case WireType.Fixed64: case WireType.String: case WireType.StartGroup: case WireType.SignedVariant: case WireType.Variant: break; // fine case WireType.None: case WireType.EndGroup: default: throw new ArgumentException("Invalid wire-type: " + wireType.ToString(), "wireType"); } #endif if (writer.packedFieldNumber == 0) { writer.fieldNumber = fieldNumber; writer.wireType = wireType; WriteHeaderCore(fieldNumber, wireType, writer); } else if (writer.packedFieldNumber == fieldNumber) { // we'll set things up, but note we *don't* actually write the header here switch (wireType) { case WireType.Fixed32: case WireType.Fixed64: case WireType.Variant: case WireType.SignedVariant: break; // fine default: throw new InvalidOperationException("Wire-type cannot be encoded as packed: " + wireType.ToString()); } writer.fieldNumber = fieldNumber; writer.wireType = wireType; } else { throw new InvalidOperationException("Field mismatch during packed encoding; expected " + writer.packedFieldNumber.ToString() + " but received " + fieldNumber.ToString()); } } internal static void WriteHeaderCore(int fieldNumber, WireType wireType, ProtoWriter writer) { uint header = (((uint)fieldNumber) << 3) | (((uint)wireType) & 7); WriteUInt32Variant(header, writer); } /// /// Writes a byte-array to the stream; supported wire-types: String /// public static void WriteBytes(byte[] data, ProtoWriter writer) { if (data == null) throw new ArgumentNullException("data"); ProtoWriter.WriteBytes(data, 0, data.Length, writer); } /// /// Writes a byte-array to the stream; supported wire-types: String /// public static void WriteBytes(byte[] data, int offset, int length, ProtoWriter writer) { if (data == null) throw new ArgumentNullException("data"); if (writer == null) throw new ArgumentNullException("writer"); switch (writer.wireType) { case WireType.Fixed32: if (length != 4) throw new ArgumentException("length"); goto CopyFixedLength; // ugly but effective case WireType.Fixed64: if (length != 8) throw new ArgumentException("length"); goto CopyFixedLength; // ugly but effective case WireType.String: WriteUInt32Variant((uint)length, writer); writer.wireType = WireType.None; if (length == 0) return; if (writer.flushLock != 0 || length <= writer.ioBuffer.Length) // write to the buffer { goto CopyFixedLength; // ugly but effective } // writing data that is bigger than the buffer (and the buffer // isn't currently locked due to a sub-object needing the size backfilled) Flush(writer); // commit any existing data from the buffer // now just write directly to the underlying stream writer.dest.Write(data, offset, length); writer.position += length; // since we've flushed offset etc is 0, and remains // zero since we're writing directly to the stream return; } throw CreateException(writer); CopyFixedLength: // no point duplicating this lots of times, and don't really want another stackframe DemandSpace(length, writer); Helpers.BlockCopy(data, offset, writer.ioBuffer, writer.ioIndex, length); IncrementedAndReset(length, writer); } private static void CopyRawFromStream(Stream source, ProtoWriter writer) { byte[] buffer = writer.ioBuffer; int space = buffer.Length - writer.ioIndex, bytesRead = 1; // 1 here to spoof case where already full // try filling the buffer first while (space > 0 && (bytesRead = source.Read(buffer, writer.ioIndex, space)) > 0) { writer.ioIndex += bytesRead; writer.position += bytesRead; space -= bytesRead; } if (bytesRead <= 0) return; // all done using just the buffer; stream exhausted // at this point the stream still has data, but buffer is full; if (writer.flushLock == 0) { // flush the buffer and write to the underlying stream instead Flush(writer); while ((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) { writer.dest.Write(buffer, 0, bytesRead); writer.position += bytesRead; } } else { do { // need more space; resize (double) as necessary, // requesting a reasonable minimum chunk each time // (128 is the minimum; there may actually be much // more space than this in the buffer) DemandSpace(128, writer); if((bytesRead = source.Read(writer.ioBuffer, writer.ioIndex, writer.ioBuffer.Length - writer.ioIndex)) <= 0) break; writer.position += bytesRead; writer.ioIndex += bytesRead; } while (true); } } private static void IncrementedAndReset(int length, ProtoWriter writer) { Helpers.DebugAssert(length >= 0); writer.ioIndex += length; writer.position += length; writer.wireType = WireType.None; } int depth = 0; const int RecursionCheckDepth = 25; /// /// Indicates the start of a nested record. /// /// The instance to write. /// The destination. /// A token representing the state of the stream; this token is given to EndSubItem. public static SubItemToken StartSubItem(object instance, ProtoWriter writer) { return StartSubItem(instance, writer, false); } MutableList recursionStack; private void CheckRecursionStackAndPush(object instance) { int hitLevel; if (recursionStack == null) { recursionStack = new MutableList(); } else if (instance != null && (hitLevel = recursionStack.IndexOfReference(instance)) >= 0) { #if DEBUG Helpers.DebugWriteLine("Stack:"); foreach(object obj in recursionStack) { Helpers.DebugWriteLine(obj == null ? "" : obj.ToString()); } Helpers.DebugWriteLine(instance == null ? "" : instance.ToString()); #endif throw new ProtoException("Possible recursion detected (offset: " + (recursionStack.Count - hitLevel).ToString() + " level(s)): " + instance.ToString()); } recursionStack.Add(instance); } private void PopRecursionStack() { recursionStack.RemoveLast(); } private static SubItemToken StartSubItem(object instance, ProtoWriter writer, bool allowFixed) { if (writer == null) throw new ArgumentNullException("writer"); if (++writer.depth > RecursionCheckDepth) { writer.CheckRecursionStackAndPush(instance); } if(writer.packedFieldNumber != 0) throw new InvalidOperationException("Cannot begin a sub-item while performing packed encoding"); switch (writer.wireType) { case WireType.StartGroup: writer.wireType = WireType.None; return new SubItemToken(-writer.fieldNumber); case WireType.String: #if DEBUG if(writer.model != null && writer.model.ForwardsOnly) { throw new ProtoException("Should not be buffering data"); } #endif writer.wireType = WireType.None; DemandSpace(32, writer); // make some space in anticipation... writer.flushLock++; writer.position++; return new SubItemToken(writer.ioIndex++); // leave 1 space (optimistic) for length case WireType.Fixed32: { if (!allowFixed) throw CreateException(writer); DemandSpace(32, writer); // make some space in anticipation... writer.flushLock++; SubItemToken token = new SubItemToken(writer.ioIndex); ProtoWriter.IncrementedAndReset(4, writer); // leave 4 space (rigid) for length return token; } default: throw CreateException(writer); } } /// /// Indicates the end of a nested record. /// /// The token obtained from StartubItem. /// The destination. public static void EndSubItem(SubItemToken token, ProtoWriter writer) { EndSubItem(token, writer, PrefixStyle.Base128); } private static void EndSubItem(SubItemToken token, ProtoWriter writer, PrefixStyle style) { if (writer == null) throw new ArgumentNullException("writer"); if (writer.wireType != WireType.None) { throw CreateException(writer); } int value = token.value; if (writer.depth <= 0) throw CreateException(writer); if (writer.depth-- > RecursionCheckDepth) { writer.PopRecursionStack(); } writer.packedFieldNumber = 0; // ending the sub-item always wipes packed encoding if (value < 0) { // group - very simple append WriteHeaderCore(-value, WireType.EndGroup, writer); writer.wireType = WireType.None; return; } // so we're backfilling the length into an existing sequence int len; switch(style) { case PrefixStyle.Fixed32: len = (int)((writer.ioIndex - value) - 4); ProtoWriter.WriteInt32ToBuffer(len, writer.ioBuffer, value); break; case PrefixStyle.Fixed32BigEndian: len = (int)((writer.ioIndex - value) - 4); byte[] buffer = writer.ioBuffer; ProtoWriter.WriteInt32ToBuffer(len, buffer, value); // and swap the byte order byte b = buffer[value]; buffer[value] = buffer[value + 3]; buffer[value + 3] = b; b = buffer[value + 1]; buffer[value + 1] = buffer[value + 2]; buffer[value + 2] = b; break; case PrefixStyle.Base128: // string - complicated because we only reserved one byte; // if the prefix turns out to need more than this then // we need to shuffle the existing data len = (int)((writer.ioIndex - value) - 1); int offset = 0; uint tmp = (uint)len; while ((tmp >>= 7) != 0) offset++; if (offset == 0) { writer.ioBuffer[value] = (byte)(len & 0x7F); } else { DemandSpace(offset, writer); byte[] blob = writer.ioBuffer; Helpers.BlockCopy(blob, value + 1, blob, value + 1 + offset, len); tmp = (uint)len; do { blob[value++] = (byte)((tmp & 0x7F) | 0x80); } while ((tmp >>= 7) != 0); blob[value - 1] = (byte)(blob[value - 1] & ~0x80); writer.position += offset; writer.ioIndex += offset; } break; default: throw new ArgumentOutOfRangeException("style"); } // and this object is no longer a blockage - also flush if sensible const int ADVISORY_FLUSH_SIZE = 1024; if (--writer.flushLock == 0 && writer.ioIndex >= ADVISORY_FLUSH_SIZE) { ProtoWriter.Flush(writer); } } /// /// Creates a new writer against a stream /// /// The destination stream /// The model to use for serialization; this can be null, but this will impair the ability to serialize sub-objects /// Additional context about this serialization operation public ProtoWriter(Stream dest, TypeModel model, SerializationContext context) { if (dest == null) throw new ArgumentNullException("dest"); if (!dest.CanWrite) throw new ArgumentException("Cannot write to stream", "dest"); //if (model == null) throw new ArgumentNullException("model"); this.dest = dest; this.ioBuffer = BufferPool.GetBuffer(); this.model = model; this.wireType = WireType.None; if (context == null) { context = SerializationContext.Default; } else { context.Freeze(); } this.context = context; } private readonly SerializationContext context; /// /// Addition information about this serialization operation. /// public SerializationContext Context { get { return context; } } void IDisposable.Dispose() { Dispose(); } private void Dispose() { // importantly, this does **not** own the stream, and does not dispose it if (dest != null) { Flush(this); dest = null; } model = null; BufferPool.ReleaseBufferToPool(ref ioBuffer); } private byte[] ioBuffer; private int ioIndex; // note that this is used by some of the unit tests and should not be removed internal static int GetPosition(ProtoWriter writer) { return writer.position; } private int position; private static void DemandSpace(int required, ProtoWriter writer) { // check for enough space if ((writer.ioBuffer.Length - writer.ioIndex) < required) { if (writer.flushLock == 0) { Flush(writer); // try emptying the buffer if ((writer.ioBuffer.Length - writer.ioIndex) >= required) return; } // either can't empty the buffer, or that didn't help; need more space BufferPool.ResizeAndFlushLeft(ref writer.ioBuffer, required + writer.ioIndex, 0, writer.ioIndex); } } /// /// Flushes data to the underlying stream, and releases any resources. The underlying stream is *not* disposed /// by this operation. /// public void Close() { if (depth != 0 || flushLock != 0) throw new InvalidOperationException("Unable to close stream in an incomplete state"); Dispose(); } internal void CheckDepthFlushlock() { if (depth != 0 || flushLock != 0) throw new InvalidOperationException("The writer is in an incomplete state"); } /// /// Get the TypeModel associated with this writer /// public TypeModel Model { get { return model; } } /// /// Writes any buffered data (if possible) to the underlying stream. /// /// The writer to flush /// It is not always possible to fully flush, since some sequences /// may require values to be back-filled into the byte-stream. internal static void Flush(ProtoWriter writer) { if (writer.flushLock == 0 && writer.ioIndex != 0) { writer.dest.Write(writer.ioBuffer, 0, writer.ioIndex); writer.ioIndex = 0; } } /// /// Writes an unsigned 32-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64 /// private static void WriteUInt32Variant(uint value, ProtoWriter writer) { DemandSpace(5, writer); int count = 0; do { writer.ioBuffer[writer.ioIndex++] = (byte)((value & 0x7F) | 0x80); count++; } while ((value >>= 7) != 0); writer.ioBuffer[writer.ioIndex - 1] &= 0x7F; writer.position += count; } #if COREFX static readonly Encoding encoding = Encoding.UTF8; #else static readonly UTF8Encoding encoding = new UTF8Encoding(); #endif internal static uint Zig(int value) { return (uint)((value << 1) ^ (value >> 31)); } internal static ulong Zig(long value) { return (ulong)((value << 1) ^ (value >> 63)); } private static void WriteUInt64Variant(ulong value, ProtoWriter writer) { DemandSpace(10, writer); int count = 0; do { writer.ioBuffer[writer.ioIndex++] = (byte)((value & 0x7F) | 0x80); count++; } while ((value >>= 7) != 0); writer.ioBuffer[writer.ioIndex - 1] &= 0x7F; writer.position += count; } /// /// Writes a string to the stream; supported wire-types: String /// public static void WriteString(string value, ProtoWriter writer) { if (writer == null) throw new ArgumentNullException("writer"); if (writer.wireType != WireType.String) throw CreateException(writer); if (value == null) throw new ArgumentNullException("value"); // written header; now what? int len = value.Length; if (len == 0) { WriteUInt32Variant(0, writer); writer.wireType = WireType.None; return; // just a header } #if MF byte[] bytes = encoding.GetBytes(value); int actual = bytes.Length; writer.WriteUInt32Variant((uint)actual); writer.Ensure(actual); Helpers.BlockCopy(bytes, 0, writer.ioBuffer, writer.ioIndex, actual); #else int predicted = encoding.GetByteCount(value); WriteUInt32Variant((uint)predicted, writer); DemandSpace(predicted, writer); int actual = encoding.GetBytes(value, 0, value.Length, writer.ioBuffer, writer.ioIndex); Helpers.DebugAssert(predicted == actual); #endif IncrementedAndReset(actual, writer); } /// /// Writes an unsigned 64-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64 /// public static void WriteUInt64(ulong value, ProtoWriter writer) { if (writer == null) throw new ArgumentNullException("writer"); switch (writer.wireType) { case WireType.Fixed64: ProtoWriter.WriteInt64((long)value, writer); return; case WireType.Variant: WriteUInt64Variant(value, writer); writer.wireType = WireType.None; return; case WireType.Fixed32: checked { ProtoWriter.WriteUInt32((uint)value, writer); } return; default: throw CreateException(writer); } } /// /// Writes a signed 64-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant /// public static void WriteInt64(long value, ProtoWriter writer) { byte[] buffer; int index; if (writer == null) throw new ArgumentNullException("writer"); switch (writer.wireType) { case WireType.Fixed64: DemandSpace(8, writer); buffer = writer.ioBuffer; index = writer.ioIndex; buffer[index] = (byte)value; buffer[index + 1] = (byte)(value >> 8); buffer[index + 2] = (byte)(value >> 16); buffer[index + 3] = (byte)(value >> 24); buffer[index + 4] = (byte)(value >> 32); buffer[index + 5] = (byte)(value >> 40); buffer[index + 6] = (byte)(value >> 48); buffer[index + 7] = (byte)(value >> 56); IncrementedAndReset(8, writer); return; case WireType.SignedVariant: WriteUInt64Variant(Zig(value), writer); writer.wireType = WireType.None; return; case WireType.Variant: if (value >= 0) { WriteUInt64Variant((ulong)value, writer); writer.wireType = WireType.None; } else { DemandSpace(10, writer); buffer = writer.ioBuffer; index = writer.ioIndex; buffer[index] = (byte)(value | 0x80); buffer[index + 1] = (byte)((int)(value >> 7) | 0x80); buffer[index + 2] = (byte)((int)(value >> 14) | 0x80); buffer[index + 3] = (byte)((int)(value >> 21) | 0x80); buffer[index + 4] = (byte)((int)(value >> 28) | 0x80); buffer[index + 5] = (byte)((int)(value >> 35) | 0x80); buffer[index + 6] = (byte)((int)(value >> 42) | 0x80); buffer[index + 7] = (byte)((int)(value >> 49) | 0x80); buffer[index + 8] = (byte)((int)(value >> 56) | 0x80); buffer[index + 9] = 0x01; // sign bit IncrementedAndReset(10, writer); } return; case WireType.Fixed32: checked { WriteInt32((int)value, writer); } return; default: throw CreateException(writer); } } /// /// Writes an unsigned 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64 /// public static void WriteUInt32(uint value, ProtoWriter writer) { if (writer == null) throw new ArgumentNullException("writer"); switch (writer.wireType) { case WireType.Fixed32: ProtoWriter.WriteInt32((int)value, writer); return; case WireType.Fixed64: ProtoWriter.WriteInt64((int)value, writer); return; case WireType.Variant: WriteUInt32Variant(value, writer); writer.wireType = WireType.None; return; default: throw CreateException(writer); } } /// /// Writes a signed 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant /// public static void WriteInt16(short value, ProtoWriter writer) { ProtoWriter.WriteInt32(value, writer); } /// /// Writes an unsigned 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64 /// public static void WriteUInt16(ushort value, ProtoWriter writer) { ProtoWriter.WriteUInt32(value, writer); } /// /// Writes an unsigned 8-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64 /// public static void WriteByte(byte value, ProtoWriter writer) { ProtoWriter.WriteUInt32(value, writer); } /// /// Writes a signed 8-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant /// public static void WriteSByte(sbyte value, ProtoWriter writer) { ProtoWriter.WriteInt32(value, writer); } private static void WriteInt32ToBuffer(int value, byte[] buffer, int index) { buffer[index] = (byte)value; buffer[index + 1] = (byte)(value >> 8); buffer[index + 2] = (byte)(value >> 16); buffer[index + 3] = (byte)(value >> 24); } /// /// Writes a signed 32-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant /// public static void WriteInt32(int value, ProtoWriter writer) { byte[] buffer; int index; if (writer == null) throw new ArgumentNullException("writer"); switch (writer.wireType) { case WireType.Fixed32: DemandSpace(4, writer); WriteInt32ToBuffer(value, writer.ioBuffer, writer.ioIndex); IncrementedAndReset(4, writer); return; case WireType.Fixed64: DemandSpace(8, writer); buffer = writer.ioBuffer; index = writer.ioIndex; buffer[index] = (byte)value; buffer[index + 1] = (byte)(value >> 8); buffer[index + 2] = (byte)(value >> 16); buffer[index + 3] = (byte)(value >> 24); buffer[index + 4] = buffer[index + 5] = buffer[index + 6] = buffer[index + 7] = 0; IncrementedAndReset(8, writer); return; case WireType.SignedVariant: WriteUInt32Variant(Zig(value), writer); writer.wireType = WireType.None; return; case WireType.Variant: if (value >= 0) { WriteUInt32Variant((uint)value, writer); writer.wireType = WireType.None; } else { DemandSpace(10, writer); buffer = writer.ioBuffer; index = writer.ioIndex; buffer[index] = (byte)(value | 0x80); buffer[index + 1] = (byte)((value >> 7) | 0x80); buffer[index + 2] = (byte)((value >> 14) | 0x80); buffer[index + 3] = (byte)((value >> 21) | 0x80); buffer[index + 4] = (byte)((value >> 28) | 0x80); buffer[index + 5] = buffer[index + 6] = buffer[index + 7] = buffer[index + 8] = (byte)0xFF; buffer[index + 9] = (byte)0x01; IncrementedAndReset(10, writer); } return; default: throw CreateException(writer); } } /// /// Writes a double-precision number to the stream; supported wire-types: Fixed32, Fixed64 /// public #if !FEAT_SAFE unsafe #endif static void WriteDouble(double value, ProtoWriter writer) { if (writer == null) throw new ArgumentNullException("writer"); switch (writer.wireType) { case WireType.Fixed32: float f = (float)value; if (Helpers.IsInfinity(f) && !Helpers.IsInfinity(value)) { throw new OverflowException(); } ProtoWriter.WriteSingle(f, writer); return; case WireType.Fixed64: #if FEAT_SAFE ProtoWriter.WriteInt64(BitConverter.ToInt64(BitConverter.GetBytes(value), 0), writer); #else ProtoWriter.WriteInt64(*(long*)&value, writer); #endif return; default: throw CreateException(writer); } } /// /// Writes a single-precision number to the stream; supported wire-types: Fixed32, Fixed64 /// public #if !FEAT_SAFE unsafe #endif static void WriteSingle(float value, ProtoWriter writer) { if (writer == null) throw new ArgumentNullException("writer"); switch (writer.wireType) { case WireType.Fixed32: #if FEAT_SAFE ProtoWriter.WriteInt32(BitConverter.ToInt32(BitConverter.GetBytes(value), 0), writer); #else ProtoWriter.WriteInt32(*(int*)&value, writer); #endif return; case WireType.Fixed64: ProtoWriter.WriteDouble((double)value, writer); return; default: throw CreateException(writer); } } /// /// Throws an exception indicating that the given enum cannot be mapped to a serialized value. /// public static void ThrowEnumException(ProtoWriter writer, object enumValue) { if (writer == null) throw new ArgumentNullException("writer"); string rhs = enumValue == null ? "" : (enumValue.GetType().FullName + "." + enumValue.ToString()); throw new ProtoException("No wire-value is mapped to the enum " + rhs + " at position " + writer.position.ToString()); } // general purpose serialization exception message internal static Exception CreateException(ProtoWriter writer) { if (writer == null) throw new ArgumentNullException("writer"); return new ProtoException("Invalid serialization operation with wire-type " + writer.wireType.ToString() + " at position " + writer.position.ToString()); } /// /// Writes a boolean to the stream; supported wire-types: Variant, Fixed32, Fixed64 /// public static void WriteBoolean(bool value, ProtoWriter writer) { ProtoWriter.WriteUInt32(value ? (uint)1 : (uint)0, writer); } /// /// Copies any extension data stored for the instance to the underlying stream /// public static void AppendExtensionData(IExtensible instance, ProtoWriter writer) { if (instance == null) throw new ArgumentNullException("instance"); if (writer == null) throw new ArgumentNullException("writer"); // we expect the writer to be raw here; the extension data will have the // header detail, so we'll copy it implicitly if(writer.wireType != WireType.None) throw CreateException(writer); IExtension extn = instance.GetExtensionObject(false); if (extn != null) { // unusually we *don't* want "using" here; the "finally" does that, with // the extension object being responsible for disposal etc Stream source = extn.BeginQuery(); try { CopyRawFromStream(source, writer); } finally { extn.EndQuery(source); } } } private int packedFieldNumber; /// /// Used for packed encoding; indicates that the next field should be skipped rather than /// a field header written. Note that the field number must match, else an exception is thrown /// when the attempt is made to write the (incorrect) field. The wire-type is taken from the /// subsequent call to WriteFieldHeader. Only primitive types can be packed. /// public static void SetPackedField(int fieldNumber, ProtoWriter writer) { if (fieldNumber <= 0) throw new ArgumentOutOfRangeException("fieldNumber"); if (writer == null) throw new ArgumentNullException("writer"); writer.packedFieldNumber = fieldNumber; } internal string SerializeType(System.Type type) { return TypeModel.SerializeType(model, type); } /// /// Specifies a known root object to use during reference-tracked serialization /// public void SetRootObject(object value) { NetCache.SetKeyedObject(NetObjectCache.Root, value); } /// /// Writes a Type to the stream, using the model's DynamicTypeFormatting if appropriate; supported wire-types: String /// public static void WriteType(System.Type value, ProtoWriter writer) { if (writer == null) throw new ArgumentNullException("writer"); WriteString(writer.SerializeType(value), writer); } } }