/// <summary>
        /// Reads an *implementation specific* bundled .NET object, including (as options) type-metadata, identity/re-use, etc.
        /// </summary>
        public static object ReadNetObject(object value, ProtoReader source, int key, Type type, NetObjectOptions options)
        {
            SubItemToken token = ProtoReader.StartSubItem(source);
            int          fieldNumber;
            int          newObjectKey = -1, newTypeKey = -1, tmp;

            while ((fieldNumber = source.ReadFieldHeader()) > 0)
            {
                switch (fieldNumber)
                {
                case FieldExistingObjectKey:
                    tmp   = source.ReadInt32();
                    value = source.NetCache.GetKeyedObject(tmp);
                    break;

                case FieldNewObjectKey:
                    newObjectKey = source.ReadInt32();
                    break;

                case FieldExistingTypeKey:
                    tmp  = source.ReadInt32();
                    type = (Type)source.NetCache.GetKeyedObject(tmp);
                    key  = source.GetTypeKey(ref type);
                    break;

                case FieldNewTypeKey:
                    newTypeKey = source.ReadInt32();
                    break;

                case FieldTypeName:
                    string typeName = source.ReadString();
                    type = source.DeserializeType(typeName);
                    if (type == null)
                    {
                        throw new ProtoException("Unable to resolve type: " + typeName + " (you can use the TypeModel.DynamicTypeFormatting event to provide a custom mapping)");
                    }
                    if (type == typeof(string))
                    {
                        key = -1;
                    }
                    else
                    {
                        key = source.GetTypeKey(ref type);
                        if (key < 0)
                        {
                            throw new InvalidOperationException("Dynamic type is not a contract-type: " + type.Name);
                        }
                    }
                    break;

                case FieldObject:
                    bool isString = type == typeof(string);
                    bool wasNull  = value == null;
                    bool lateSet  = wasNull && (isString || ((options & NetObjectOptions.LateSet) != 0));

                    if (newObjectKey >= 0 && !lateSet)
                    {
                        if (value == null)
                        {
                            source.TrapNextObject(newObjectKey);
                        }
                        else
                        {
                            source.NetCache.SetKeyedObject(newObjectKey, value);
                        }
                        if (newTypeKey >= 0)
                        {
                            source.NetCache.SetKeyedObject(newTypeKey, type);
                        }
                    }
                    object oldValue = value;
                    if (isString)
                    {
                        value = source.ReadString();
                    }
                    else
                    {
                        value = ProtoReader.ReadTypedObject(oldValue, key, source, type);
                    }

                    if (newObjectKey >= 0)
                    {
                        if (wasNull && !lateSet)
                        {     // this both ensures (via exception) that it *was* set, and makes sure we don't shout
                            // about changed references
                            oldValue = source.NetCache.GetKeyedObject(newObjectKey);
                        }
                        if (lateSet)
                        {
                            source.NetCache.SetKeyedObject(newObjectKey, value);
                            if (newTypeKey >= 0)
                            {
                                source.NetCache.SetKeyedObject(newTypeKey, type);
                            }
                        }
                    }
                    if (newObjectKey >= 0 && !lateSet && !ReferenceEquals(oldValue, value))
                    {
                        throw new ProtoException("A reference-tracked object changed reference during deserialization");
                    }
                    if (newObjectKey < 0 && newTypeKey >= 0)
                    {      // have a new type, but not a new object
                        source.NetCache.SetKeyedObject(newTypeKey, type);
                    }
                    break;

                default:
                    source.SkipField();
                    break;
                }
            }
            if (newObjectKey >= 0 && (options & NetObjectOptions.AsReference) == 0)
            {
                throw new ProtoException("Object key in input stream, but reference-tracking was not expected");
            }
            ProtoReader.EndSubItem(token, source);

            return(value);
        }
        private static long ReadTimeSpanTicks(ProtoReader source, out DateTimeKind kind)
        {
            kind = DateTimeKind.Unspecified;
            switch (source.WireType)
            {
            case WireType.String:
            case WireType.StartGroup:
                SubItemToken  token = ProtoReader.StartSubItem(source);
                int           fieldNumber;
                TimeSpanScale scale = TimeSpanScale.Days;
                long          value = 0;
                while ((fieldNumber = source.ReadFieldHeader()) > 0)
                {
                    switch (fieldNumber)
                    {
                    case FieldTimeSpanScale:
                        scale = (TimeSpanScale)source.ReadInt32();
                        break;

                    case FieldTimeSpanValue:
                        source.Assert(WireType.SignedVariant);
                        value = source.ReadInt64();
                        break;

                    case FieldTimeSpanKind:
                        kind = (DateTimeKind)source.ReadInt32();
                        switch (kind)
                        {
                        case DateTimeKind.Unspecified:
                        case DateTimeKind.Utc:
                        case DateTimeKind.Local:
                            break;             // fine

                        default:
                            throw new ProtoException("Invalid date/time kind: " + kind.ToString());
                        }
                        break;

                    default:
                        source.SkipField();
                        break;
                    }
                }
                ProtoReader.EndSubItem(token, source);
                switch (scale)
                {
                case TimeSpanScale.Days:
                    return(value * TimeSpan.TicksPerDay);

                case TimeSpanScale.Hours:
                    return(value * TimeSpan.TicksPerHour);

                case TimeSpanScale.Minutes:
                    return(value * TimeSpan.TicksPerMinute);

                case TimeSpanScale.Seconds:
                    return(value * TimeSpan.TicksPerSecond);

                case TimeSpanScale.Milliseconds:
                    return(value * TimeSpan.TicksPerMillisecond);

                case TimeSpanScale.Ticks:
                    return(value);

                case TimeSpanScale.MinMax:
                    switch (value)
                    {
                    case 1: return(long.MaxValue);

                    case -1: return(long.MinValue);

                    default: throw new ProtoException("Unknown min/max value: " + value.ToString());
                    }

                default:
                    throw new ProtoException("Unknown timescale: " + scale.ToString());
                }

            case WireType.Fixed64:
                return(source.ReadInt64());

            default:
                throw new ProtoException("Unexpected wire-type: " + source.WireType.ToString());
            }
        }
        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 = (int)token.value64;

            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;
                    Buffer.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.position64 += 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);
            }
        }
        private static void WriteTimeSpanImpl(TimeSpan timeSpan, ProtoWriter dest, DateTimeKind kind)
        {
            if (dest == null)
            {
                throw new ArgumentNullException(nameof(dest));
            }
            long value;

            switch (dest.WireType)
            {
            case WireType.String:
            case WireType.StartGroup:
                TimeSpanScale scale;
                value = timeSpan.Ticks;
                if (timeSpan == TimeSpan.MaxValue)
                {
                    value = 1;
                    scale = TimeSpanScale.MinMax;
                }
                else if (timeSpan == TimeSpan.MinValue)
                {
                    value = -1;
                    scale = TimeSpanScale.MinMax;
                }
                else if (value % TimeSpan.TicksPerDay == 0)
                {
                    scale  = TimeSpanScale.Days;
                    value /= TimeSpan.TicksPerDay;
                }
                else if (value % TimeSpan.TicksPerHour == 0)
                {
                    scale  = TimeSpanScale.Hours;
                    value /= TimeSpan.TicksPerHour;
                }
                else if (value % TimeSpan.TicksPerMinute == 0)
                {
                    scale  = TimeSpanScale.Minutes;
                    value /= TimeSpan.TicksPerMinute;
                }
                else if (value % TimeSpan.TicksPerSecond == 0)
                {
                    scale  = TimeSpanScale.Seconds;
                    value /= TimeSpan.TicksPerSecond;
                }
                else if (value % TimeSpan.TicksPerMillisecond == 0)
                {
                    scale  = TimeSpanScale.Milliseconds;
                    value /= TimeSpan.TicksPerMillisecond;
                }
                else
                {
                    scale = TimeSpanScale.Ticks;
                }

                SubItemToken token = ProtoWriter.StartSubItem(null, dest);

                if (value != 0)
                {
                    ProtoWriter.WriteFieldHeader(FieldTimeSpanValue, WireType.SignedVariant, dest);
                    ProtoWriter.WriteInt64(value, dest);
                }
                if (scale != TimeSpanScale.Days)
                {
                    ProtoWriter.WriteFieldHeader(FieldTimeSpanScale, WireType.Variant, dest);
                    ProtoWriter.WriteInt32((int)scale, dest);
                }
                if (kind != DateTimeKind.Unspecified)
                {
                    ProtoWriter.WriteFieldHeader(FieldTimeSpanKind, WireType.Variant, dest);
                    ProtoWriter.WriteInt32((int)kind, dest);
                }
                ProtoWriter.EndSubItem(token, dest);
                break;

            case WireType.Fixed64:
                ProtoWriter.WriteInt64(timeSpan.Ticks, dest);
                break;

            default:
                throw new ProtoException("Unexpected wire-type: " + dest.WireType.ToString());
            }
        }
 /// <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);
 }