123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955 |
- 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
- {
- /// <summary>
- /// 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
- /// </summary>
- public sealed class ProtoWriter : IDisposable
- {
- private Stream dest;
- TypeModel model;
- /// <summary>
- /// Write an encapsulated sub-object, using the supplied unique key (reprasenting a type).
- /// </summary>
- /// <param name="value">The object to write.</param>
- /// <param name="key">The key that uniquely identifies the type within the model.</param>
- /// <param name="writer">The destination.</param>
- 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
- }
- /// <summary>
- /// 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.
- /// </summary>
- /// <param name="value">The object to write.</param>
- /// <param name="key">The key that uniquely identifies the type within the model.</param>
- /// <param name="writer">The destination.</param>
- 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; } }
- /// <summary>
- /// Writes a field-header, indicating the format of the next data we plan to write.
- /// </summary>
- 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);
- }
- /// <summary>
- /// Writes a byte-array to the stream; supported wire-types: String
- /// </summary>
- public static void WriteBytes(byte[] data, ProtoWriter writer)
- {
- if (data == null) throw new ArgumentNullException("data");
- ProtoWriter.WriteBytes(data, 0, data.Length, writer);
- }
- /// <summary>
- /// Writes a byte-array to the stream; supported wire-types: String
- /// </summary>
- 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;
- /// <summary>
- /// Indicates the start of a nested record.
- /// </summary>
- /// <param name="instance">The instance to write.</param>
- /// <param name="writer">The destination.</param>
- /// <returns>A token representing the state of the stream; this token is given to EndSubItem.</returns>
- 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 ? "<null>" : obj.ToString());
- }
- Helpers.DebugWriteLine(instance == null ? "<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);
- }
- }
- /// <summary>
- /// Indicates the end of a nested record.
- /// </summary>
- /// <param name="token">The token obtained from StartubItem.</param>
- /// <param name="writer">The destination.</param>
- 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);
- }
-
- }
- /// <summary>
- /// Creates a new writer against a stream
- /// </summary>
- /// <param name="dest">The destination stream</param>
- /// <param name="model">The model to use for serialization; this can be null, but this will impair the ability to serialize sub-objects</param>
- /// <param name="context">Additional context about this serialization operation</param>
- 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;
- /// <summary>
- /// Addition information about this serialization operation.
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// Flushes data to the underlying stream, and releases any resources. The underlying stream is *not* disposed
- /// by this operation.
- /// </summary>
- 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");
- }
- /// <summary>
- /// Get the TypeModel associated with this writer
- /// </summary>
- public TypeModel Model { get { return model; } }
- /// <summary>
- /// Writes any buffered data (if possible) to the underlying stream.
- /// </summary>
- /// <param name="writer">The writer to flush</param>
- /// <remarks>It is not always possible to fully flush, since some sequences
- /// may require values to be back-filled into the byte-stream.</remarks>
- internal static void Flush(ProtoWriter writer)
- {
- if (writer.flushLock == 0 && writer.ioIndex != 0)
- {
- writer.dest.Write(writer.ioBuffer, 0, writer.ioIndex);
- writer.ioIndex = 0;
- }
- }
- /// <summary>
- /// Writes an unsigned 32-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64
- /// </summary>
- 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;
- }
- /// <summary>
- /// Writes a string to the stream; supported wire-types: String
- /// </summary>
- 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);
- }
- /// <summary>
- /// Writes an unsigned 64-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// Writes a signed 64-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// Writes an unsigned 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// Writes a signed 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
- /// </summary>
- public static void WriteInt16(short value, ProtoWriter writer)
- {
- ProtoWriter.WriteInt32(value, writer);
- }
- /// <summary>
- /// Writes an unsigned 16-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64
- /// </summary>
- public static void WriteUInt16(ushort value, ProtoWriter writer)
- {
- ProtoWriter.WriteUInt32(value, writer);
- }
- /// <summary>
- /// Writes an unsigned 8-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64
- /// </summary>
- public static void WriteByte(byte value, ProtoWriter writer)
- {
- ProtoWriter.WriteUInt32(value, writer);
- }
- /// <summary>
- /// Writes a signed 8-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
- /// </summary>
- 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);
- }
- /// <summary>
- /// Writes a signed 32-bit integer to the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
- /// </summary>
- 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);
- }
-
- }
- /// <summary>
- /// Writes a double-precision number to the stream; supported wire-types: Fixed32, Fixed64
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// Writes a single-precision number to the stream; supported wire-types: Fixed32, Fixed64
- /// </summary>
- 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);
- }
- }
- /// <summary>
- /// Throws an exception indicating that the given enum cannot be mapped to a serialized value.
- /// </summary>
- public static void ThrowEnumException(ProtoWriter writer, object enumValue)
- {
- if (writer == null) throw new ArgumentNullException("writer");
- string rhs = enumValue == null ? "<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());
- }
- /// <summary>
- /// Writes a boolean to the stream; supported wire-types: Variant, Fixed32, Fixed64
- /// </summary>
- public static void WriteBoolean(bool value, ProtoWriter writer)
- {
- ProtoWriter.WriteUInt32(value ? (uint)1 : (uint)0, writer);
- }
- /// <summary>
- /// Copies any extension data stored for the instance to the underlying stream
- /// </summary>
- 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;
- /// <summary>
- /// 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.
- /// </summary>
- 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);
- }
- /// <summary>
- /// Specifies a known root object to use during reference-tracked serialization
- /// </summary>
- public void SetRootObject(object value)
- {
- NetCache.SetKeyedObject(NetObjectCache.Root, value);
- }
- /// <summary>
- /// Writes a Type to the stream, using the model's DynamicTypeFormatting if appropriate; supported wire-types: String
- /// </summary>
- public static void WriteType(System.Type value, ProtoWriter writer)
- {
- if (writer == null) throw new ArgumentNullException("writer");
- WriteString(writer.SerializeType(value), writer);
- }
- }
- }
|