1445 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			C#
		
	
	
			
		
		
	
	
			1445 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			C#
		
	
	
 | 
						|
using System;
 | 
						|
using System.Collections.Generic;
 | 
						|
using System.IO;
 | 
						|
using System.Text;
 | 
						|
using ProtoBuf.Meta;
 | 
						|
 | 
						|
namespace ProtoBuf
 | 
						|
{
 | 
						|
    /// <summary>
 | 
						|
    /// A stateful reader, used to read a protobuf stream. Typical usage would be (sequentially) to call
 | 
						|
    /// ReadFieldHeader and (after matching the field) an appropriate Read* method.
 | 
						|
    /// </summary>
 | 
						|
    public sealed class ProtoReader : IDisposable
 | 
						|
    {
 | 
						|
        Stream source;
 | 
						|
        byte[] ioBuffer;
 | 
						|
        TypeModel model;
 | 
						|
        int fieldNumber, depth, ioIndex, available;
 | 
						|
        long position64, blockEnd64, dataRemaining64;
 | 
						|
        WireType wireType;
 | 
						|
        bool isFixedLength, internStrings;
 | 
						|
        private NetObjectCache netCache;
 | 
						|
 | 
						|
        // this is how many outstanding objects do not currently have
 | 
						|
        // values for the purposes of reference tracking; we'll default
 | 
						|
        // to just trapping the root object
 | 
						|
        // note: objects are trapped (the ref and key mapped) via NoteObject
 | 
						|
        uint trapCount; // uint is so we can use beq/bne more efficiently than bgt
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Gets the number of the field being processed.
 | 
						|
        /// </summary>
 | 
						|
        public int FieldNumber => fieldNumber;
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Indicates the underlying proto serialization format on the wire.
 | 
						|
        /// </summary>
 | 
						|
        public WireType WireType => wireType;
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Creates a new reader against a stream
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="source">The source stream</param>
 | 
						|
        /// <param name="model">The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects</param>
 | 
						|
        /// <param name="context">Additional context about this serialization operation</param>
 | 
						|
        [Obsolete("Please use ProtoReader.Create; this API may be removed in a future version", error: false)]
 | 
						|
        public ProtoReader(Stream source, TypeModel model, SerializationContext context)
 | 
						|
        {
 | 
						|
 | 
						|
            Init(this, source, model, context, TO_EOF);
 | 
						|
        }
 | 
						|
 | 
						|
        internal const long TO_EOF = -1;
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Gets / sets a flag indicating whether strings should be checked for repetition; if
 | 
						|
        /// true, any repeated UTF-8 byte sequence will result in the same String instance, rather
 | 
						|
        /// than a second instance of the same string. Enabled by default. Note that this uses
 | 
						|
        /// a <i>custom</i> interner - the system-wide string interner is not used.
 | 
						|
        /// </summary>
 | 
						|
        public bool InternStrings { get { return internStrings; } set { internStrings = value; } }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Creates a new reader against a stream
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="source">The source stream</param>
 | 
						|
        /// <param name="model">The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects</param>
 | 
						|
        /// <param name="context">Additional context about this serialization operation</param>
 | 
						|
        /// <param name="length">The number of bytes to read, or -1 to read until the end of the stream</param>
 | 
						|
        [Obsolete("Please use ProtoReader.Create; this API may be removed in a future version", error: false)]
 | 
						|
        public ProtoReader(Stream source, TypeModel model, SerializationContext context, int length)
 | 
						|
        {
 | 
						|
            Init(this, source, model, context, length);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Creates a new reader against a stream
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="source">The source stream</param>
 | 
						|
        /// <param name="model">The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects</param>
 | 
						|
        /// <param name="context">Additional context about this serialization operation</param>
 | 
						|
        /// <param name="length">The number of bytes to read, or -1 to read until the end of the stream</param>
 | 
						|
        [Obsolete("Please use ProtoReader.Create; this API may be removed in a future version", error: false)]
 | 
						|
        public ProtoReader(Stream source, TypeModel model, SerializationContext context, long length)
 | 
						|
        {
 | 
						|
            Init(this, source, model, context, length);
 | 
						|
        }
 | 
						|
 | 
						|
        private static void Init(ProtoReader reader, Stream source, TypeModel model, SerializationContext context, long length)
 | 
						|
        {
 | 
						|
            if (source == null) throw new ArgumentNullException(nameof(source));
 | 
						|
            if (!source.CanRead) throw new ArgumentException("Cannot read from stream", nameof(source));
 | 
						|
            reader.source = source;
 | 
						|
            reader.ioBuffer = BufferPool.GetBuffer();
 | 
						|
            reader.model = model;
 | 
						|
            bool isFixedLength = length >= 0;
 | 
						|
            reader.isFixedLength = isFixedLength;
 | 
						|
            reader.dataRemaining64 = isFixedLength ? length : 0;
 | 
						|
 | 
						|
            if (context == null) { context = SerializationContext.Default; }
 | 
						|
            else { context.Freeze(); }
 | 
						|
            reader.context = context;
 | 
						|
            reader.position64 = 0;
 | 
						|
            reader.available = reader.depth = reader.fieldNumber = reader.ioIndex = 0;
 | 
						|
            reader.blockEnd64 = long.MaxValue;
 | 
						|
            reader.internStrings = RuntimeTypeModel.Default.InternStrings;
 | 
						|
            reader.wireType = WireType.None;
 | 
						|
            reader.trapCount = 1;
 | 
						|
            if (reader.netCache == null) reader.netCache = new NetObjectCache();
 | 
						|
        }
 | 
						|
 | 
						|
        private SerializationContext context;
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Addition information about this deserialization operation.
 | 
						|
        /// </summary>
 | 
						|
        public SerializationContext Context => context;
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Releases resources used by the reader, but importantly <b>does not</b> Dispose the 
 | 
						|
        /// underlying stream; in many typical use-cases the stream is used for different
 | 
						|
        /// processes, so it is assumed that the consumer will Dispose their stream separately.
 | 
						|
        /// </summary>
 | 
						|
        public void Dispose()
 | 
						|
        {
 | 
						|
            // importantly, this does **not** own the stream, and does not dispose it
 | 
						|
            source = null;
 | 
						|
            model = null;
 | 
						|
            BufferPool.ReleaseBufferToPool(ref ioBuffer);
 | 
						|
            if (stringInterner != null)
 | 
						|
            {
 | 
						|
                stringInterner.Clear();
 | 
						|
                stringInterner = null;
 | 
						|
            }
 | 
						|
            if (netCache != null) netCache.Clear();
 | 
						|
        }
 | 
						|
        internal int TryReadUInt32VariantWithoutMoving(bool trimNegative, out uint value)
 | 
						|
        {
 | 
						|
            if (available < 10) Ensure(10, false);
 | 
						|
            if (available == 0)
 | 
						|
            {
 | 
						|
                value = 0;
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
            int readPos = ioIndex;
 | 
						|
            value = ioBuffer[readPos++];
 | 
						|
            if ((value & 0x80) == 0) return 1;
 | 
						|
            value &= 0x7F;
 | 
						|
            if (available == 1) throw EoF(this);
 | 
						|
 | 
						|
            uint chunk = ioBuffer[readPos++];
 | 
						|
            value |= (chunk & 0x7F) << 7;
 | 
						|
            if ((chunk & 0x80) == 0) return 2;
 | 
						|
            if (available == 2) throw EoF(this);
 | 
						|
 | 
						|
            chunk = ioBuffer[readPos++];
 | 
						|
            value |= (chunk & 0x7F) << 14;
 | 
						|
            if ((chunk & 0x80) == 0) return 3;
 | 
						|
            if (available == 3) throw EoF(this);
 | 
						|
 | 
						|
            chunk = ioBuffer[readPos++];
 | 
						|
            value |= (chunk & 0x7F) << 21;
 | 
						|
            if ((chunk & 0x80) == 0) return 4;
 | 
						|
            if (available == 4) throw EoF(this);
 | 
						|
 | 
						|
            chunk = ioBuffer[readPos];
 | 
						|
            value |= chunk << 28; // can only use 4 bits from this chunk
 | 
						|
            if ((chunk & 0xF0) == 0) return 5;
 | 
						|
 | 
						|
            if (trimNegative // allow for -ve values
 | 
						|
                && (chunk & 0xF0) == 0xF0
 | 
						|
                && available >= 10
 | 
						|
                    && ioBuffer[++readPos] == 0xFF
 | 
						|
                    && ioBuffer[++readPos] == 0xFF
 | 
						|
                    && ioBuffer[++readPos] == 0xFF
 | 
						|
                    && ioBuffer[++readPos] == 0xFF
 | 
						|
                    && ioBuffer[++readPos] == 0x01)
 | 
						|
            {
 | 
						|
                return 10;
 | 
						|
            }
 | 
						|
            throw AddErrorData(new OverflowException(), this);
 | 
						|
        }
 | 
						|
 | 
						|
        private uint ReadUInt32Variant(bool trimNegative)
 | 
						|
        {
 | 
						|
            int read = TryReadUInt32VariantWithoutMoving(trimNegative, out uint value);
 | 
						|
            if (read > 0)
 | 
						|
            {
 | 
						|
                ioIndex += read;
 | 
						|
                available -= read;
 | 
						|
                position64 += read;
 | 
						|
                return value;
 | 
						|
            }
 | 
						|
            throw EoF(this);
 | 
						|
        }
 | 
						|
 | 
						|
        private bool TryReadUInt32Variant(out uint value)
 | 
						|
        {
 | 
						|
            int read = TryReadUInt32VariantWithoutMoving(false, out value);
 | 
						|
            if (read > 0)
 | 
						|
            {
 | 
						|
                ioIndex += read;
 | 
						|
                available -= read;
 | 
						|
                position64 += read;
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads an unsigned 32-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64
 | 
						|
        /// </summary>
 | 
						|
        public uint ReadUInt32()
 | 
						|
        {
 | 
						|
            switch (wireType)
 | 
						|
            {
 | 
						|
                case WireType.Variant:
 | 
						|
                    return ReadUInt32Variant(false);
 | 
						|
                case WireType.Fixed32:
 | 
						|
                    if (available < 4) Ensure(4, true);
 | 
						|
                    position64 += 4;
 | 
						|
                    available -= 4;
 | 
						|
                    return ((uint)ioBuffer[ioIndex++])
 | 
						|
                        | (((uint)ioBuffer[ioIndex++]) << 8)
 | 
						|
                        | (((uint)ioBuffer[ioIndex++]) << 16)
 | 
						|
                        | (((uint)ioBuffer[ioIndex++]) << 24);
 | 
						|
                case WireType.Fixed64:
 | 
						|
                    ulong val = ReadUInt64();
 | 
						|
                    checked { return (uint)val; }
 | 
						|
                default:
 | 
						|
                    throw CreateWireTypeException();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Returns the position of the current reader (note that this is not necessarily the same as the position
 | 
						|
        /// in the underlying stream, if multiple readers are used on the same stream)
 | 
						|
        /// </summary>
 | 
						|
        public int Position { get { return checked((int)position64); } }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Returns the position of the current reader (note that this is not necessarily the same as the position
 | 
						|
        /// in the underlying stream, if multiple readers are used on the same stream)
 | 
						|
        /// </summary>
 | 
						|
        public long LongPosition { get { return position64; } }
 | 
						|
        internal void Ensure(int count, bool strict)
 | 
						|
        {
 | 
						|
            Helpers.DebugAssert(available <= count, "Asking for data without checking first");
 | 
						|
            if (count > ioBuffer.Length)
 | 
						|
            {
 | 
						|
                BufferPool.ResizeAndFlushLeft(ref ioBuffer, count, ioIndex, available);
 | 
						|
                ioIndex = 0;
 | 
						|
            }
 | 
						|
            else if (ioIndex + count >= ioBuffer.Length)
 | 
						|
            {
 | 
						|
                // need to shift the buffer data to the left to make space
 | 
						|
                Buffer.BlockCopy(ioBuffer, ioIndex, ioBuffer, 0, available);
 | 
						|
                ioIndex = 0;
 | 
						|
            }
 | 
						|
            count -= available;
 | 
						|
            int writePos = ioIndex + available, bytesRead;
 | 
						|
            int canRead = ioBuffer.Length - writePos;
 | 
						|
            if (isFixedLength)
 | 
						|
            {   // throttle it if needed
 | 
						|
                if (dataRemaining64 < canRead) canRead = (int)dataRemaining64;
 | 
						|
            }
 | 
						|
            while (count > 0 && canRead > 0 && (bytesRead = source.Read(ioBuffer, writePos, canRead)) > 0)
 | 
						|
            {
 | 
						|
                available += bytesRead;
 | 
						|
                count -= bytesRead;
 | 
						|
                canRead -= bytesRead;
 | 
						|
                writePos += bytesRead;
 | 
						|
                if (isFixedLength) { dataRemaining64 -= bytesRead; }
 | 
						|
            }
 | 
						|
            if (strict && count > 0)
 | 
						|
            {
 | 
						|
                throw EoF(this);
 | 
						|
            }
 | 
						|
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a signed 16-bit integer from the stream: Variant, Fixed32, Fixed64, SignedVariant
 | 
						|
        /// </summary>
 | 
						|
        public short ReadInt16()
 | 
						|
        {
 | 
						|
            checked { return (short)ReadInt32(); }
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Reads an unsigned 16-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64
 | 
						|
        /// </summary>
 | 
						|
        public ushort ReadUInt16()
 | 
						|
        {
 | 
						|
            checked { return (ushort)ReadUInt32(); }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads an unsigned 8-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64
 | 
						|
        /// </summary>
 | 
						|
        public byte ReadByte()
 | 
						|
        {
 | 
						|
            checked { return (byte)ReadUInt32(); }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a signed 8-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
 | 
						|
        /// </summary>
 | 
						|
        public sbyte ReadSByte()
 | 
						|
        {
 | 
						|
            checked { return (sbyte)ReadInt32(); }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a signed 32-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
 | 
						|
        /// </summary>
 | 
						|
        public int ReadInt32()
 | 
						|
        {
 | 
						|
            switch (wireType)
 | 
						|
            {
 | 
						|
                case WireType.Variant:
 | 
						|
                    return (int)ReadUInt32Variant(true);
 | 
						|
                case WireType.Fixed32:
 | 
						|
                    if (available < 4) Ensure(4, true);
 | 
						|
                    position64 += 4;
 | 
						|
                    available -= 4;
 | 
						|
                    return ((int)ioBuffer[ioIndex++])
 | 
						|
                        | (((int)ioBuffer[ioIndex++]) << 8)
 | 
						|
                        | (((int)ioBuffer[ioIndex++]) << 16)
 | 
						|
                        | (((int)ioBuffer[ioIndex++]) << 24);
 | 
						|
                case WireType.Fixed64:
 | 
						|
                    long l = ReadInt64();
 | 
						|
                    checked { return (int)l; }
 | 
						|
                case WireType.SignedVariant:
 | 
						|
                    return Zag(ReadUInt32Variant(true));
 | 
						|
                default:
 | 
						|
                    throw CreateWireTypeException();
 | 
						|
            }
 | 
						|
        }
 | 
						|
        private const long Int64Msb = ((long)1) << 63;
 | 
						|
        private const int Int32Msb = ((int)1) << 31;
 | 
						|
        private static int Zag(uint ziggedValue)
 | 
						|
        {
 | 
						|
            int value = (int)ziggedValue;
 | 
						|
            return (-(value & 0x01)) ^ ((value >> 1) & ~ProtoReader.Int32Msb);
 | 
						|
        }
 | 
						|
 | 
						|
        private static long Zag(ulong ziggedValue)
 | 
						|
        {
 | 
						|
            long value = (long)ziggedValue;
 | 
						|
            return (-(value & 0x01L)) ^ ((value >> 1) & ~ProtoReader.Int64Msb);
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a signed 64-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant
 | 
						|
        /// </summary>
 | 
						|
        public long ReadInt64()
 | 
						|
        {
 | 
						|
            switch (wireType)
 | 
						|
            {
 | 
						|
                case WireType.Variant:
 | 
						|
                    return (long)ReadUInt64Variant();
 | 
						|
                case WireType.Fixed32:
 | 
						|
                    return ReadInt32();
 | 
						|
                case WireType.Fixed64:
 | 
						|
                    if (available < 8) Ensure(8, true);
 | 
						|
                    position64 += 8;
 | 
						|
                    available -= 8;
 | 
						|
 | 
						|
#if NETCOREAPP2_1
 | 
						|
                    var result = System.Buffers.Binary.BinaryPrimitives.ReadInt64LittleEndian(ioBuffer.AsSpan(ioIndex, 8));
 | 
						|
 | 
						|
                    ioIndex+= 8;
 | 
						|
 | 
						|
                    return result;
 | 
						|
#else
 | 
						|
                    return ((long)ioBuffer[ioIndex++])
 | 
						|
                        | (((long)ioBuffer[ioIndex++]) << 8)
 | 
						|
                        | (((long)ioBuffer[ioIndex++]) << 16)
 | 
						|
                        | (((long)ioBuffer[ioIndex++]) << 24)
 | 
						|
                        | (((long)ioBuffer[ioIndex++]) << 32)
 | 
						|
                        | (((long)ioBuffer[ioIndex++]) << 40)
 | 
						|
                        | (((long)ioBuffer[ioIndex++]) << 48)
 | 
						|
                        | (((long)ioBuffer[ioIndex++]) << 56);
 | 
						|
#endif
 | 
						|
                case WireType.SignedVariant:
 | 
						|
                    return Zag(ReadUInt64Variant());
 | 
						|
                default:
 | 
						|
                    throw CreateWireTypeException();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        private int TryReadUInt64VariantWithoutMoving(out ulong value)
 | 
						|
        {
 | 
						|
            if (available < 10) Ensure(10, false);
 | 
						|
            if (available == 0)
 | 
						|
            {
 | 
						|
                value = 0;
 | 
						|
                return 0;
 | 
						|
            }
 | 
						|
            int readPos = ioIndex;
 | 
						|
            value = ioBuffer[readPos++];
 | 
						|
            if ((value & 0x80) == 0) return 1;
 | 
						|
            value &= 0x7F;
 | 
						|
            if (available == 1) throw EoF(this);
 | 
						|
 | 
						|
            ulong chunk = ioBuffer[readPos++];
 | 
						|
            value |= (chunk & 0x7F) << 7;
 | 
						|
            if ((chunk & 0x80) == 0) return 2;
 | 
						|
            if (available == 2) throw EoF(this);
 | 
						|
 | 
						|
            chunk = ioBuffer[readPos++];
 | 
						|
            value |= (chunk & 0x7F) << 14;
 | 
						|
            if ((chunk & 0x80) == 0) return 3;
 | 
						|
            if (available == 3) throw EoF(this);
 | 
						|
 | 
						|
            chunk = ioBuffer[readPos++];
 | 
						|
            value |= (chunk & 0x7F) << 21;
 | 
						|
            if ((chunk & 0x80) == 0) return 4;
 | 
						|
            if (available == 4) throw EoF(this);
 | 
						|
 | 
						|
            chunk = ioBuffer[readPos++];
 | 
						|
            value |= (chunk & 0x7F) << 28;
 | 
						|
            if ((chunk & 0x80) == 0) return 5;
 | 
						|
            if (available == 5) throw EoF(this);
 | 
						|
 | 
						|
            chunk = ioBuffer[readPos++];
 | 
						|
            value |= (chunk & 0x7F) << 35;
 | 
						|
            if ((chunk & 0x80) == 0) return 6;
 | 
						|
            if (available == 6) throw EoF(this);
 | 
						|
 | 
						|
            chunk = ioBuffer[readPos++];
 | 
						|
            value |= (chunk & 0x7F) << 42;
 | 
						|
            if ((chunk & 0x80) == 0) return 7;
 | 
						|
            if (available == 7) throw EoF(this);
 | 
						|
 | 
						|
 | 
						|
            chunk = ioBuffer[readPos++];
 | 
						|
            value |= (chunk & 0x7F) << 49;
 | 
						|
            if ((chunk & 0x80) == 0) return 8;
 | 
						|
            if (available == 8) throw EoF(this);
 | 
						|
 | 
						|
            chunk = ioBuffer[readPos++];
 | 
						|
            value |= (chunk & 0x7F) << 56;
 | 
						|
            if ((chunk & 0x80) == 0) return 9;
 | 
						|
            if (available == 9) throw EoF(this);
 | 
						|
 | 
						|
            chunk = ioBuffer[readPos];
 | 
						|
            value |= chunk << 63; // can only use 1 bit from this chunk
 | 
						|
 | 
						|
            if ((chunk & ~(ulong)0x01) != 0) throw AddErrorData(new OverflowException(), this);
 | 
						|
            return 10;
 | 
						|
        }
 | 
						|
 | 
						|
        private ulong ReadUInt64Variant()
 | 
						|
        {
 | 
						|
            int read = TryReadUInt64VariantWithoutMoving(out ulong value);
 | 
						|
            if (read > 0)
 | 
						|
            {
 | 
						|
                ioIndex += read;
 | 
						|
                available -= read;
 | 
						|
                position64 += read;
 | 
						|
                return value;
 | 
						|
            }
 | 
						|
            throw EoF(this);
 | 
						|
        }
 | 
						|
 | 
						|
        private Dictionary<string, string> stringInterner;
 | 
						|
        private string Intern(string value)
 | 
						|
        {
 | 
						|
            if (value == null) return null;
 | 
						|
            if (value.Length == 0) return "";
 | 
						|
            if (stringInterner == null)
 | 
						|
            {
 | 
						|
                stringInterner = new Dictionary<string, string>
 | 
						|
                {
 | 
						|
                    { value, value }
 | 
						|
                };
 | 
						|
            }
 | 
						|
            else if (stringInterner.TryGetValue(value, out string found))
 | 
						|
            {
 | 
						|
                value = found;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                stringInterner.Add(value, value);
 | 
						|
            }
 | 
						|
            return value;
 | 
						|
        }
 | 
						|
 | 
						|
#if COREFX
 | 
						|
        static readonly Encoding encoding = Encoding.UTF8;
 | 
						|
#else
 | 
						|
        static readonly UTF8Encoding encoding = new UTF8Encoding();
 | 
						|
#endif
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a string from the stream (using UTF8); supported wire-types: String
 | 
						|
        /// </summary>
 | 
						|
        public string ReadString()
 | 
						|
        {
 | 
						|
            if (wireType == WireType.String)
 | 
						|
            {
 | 
						|
                int bytes = (int)ReadUInt32Variant(false);
 | 
						|
                if (bytes == 0) return "";
 | 
						|
                if (bytes < 0) ThrowInvalidLength(bytes);
 | 
						|
                if (available < bytes) Ensure(bytes, true);
 | 
						|
 | 
						|
                string s = encoding.GetString(ioBuffer, ioIndex, bytes);
 | 
						|
 | 
						|
                if (internStrings) { s = Intern(s); }
 | 
						|
                available -= bytes;
 | 
						|
                position64 += bytes;
 | 
						|
                ioIndex += bytes;
 | 
						|
                return s;
 | 
						|
            }
 | 
						|
            throw CreateWireTypeException();
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Throws an exception indication that the given value cannot be mapped to an enum.
 | 
						|
        /// </summary>
 | 
						|
        public void ThrowEnumException(Type type, int value)
 | 
						|
        {
 | 
						|
            string desc = type == null ? "<null>" : type.FullName;
 | 
						|
            throw AddErrorData(new ProtoException("No " + desc + " enum is mapped to the wire-value " + value.ToString()), this);
 | 
						|
        }
 | 
						|
 | 
						|
        private void ThrowInvalidLength(long length)
 | 
						|
        {
 | 
						|
            throw AddErrorData(new InvalidOperationException("Invalid length: " + length.ToString()), this);
 | 
						|
        }
 | 
						|
 | 
						|
        private Exception CreateWireTypeException()
 | 
						|
        {
 | 
						|
            return CreateException("Invalid wire-type; this usually means you have over-written a file without truncating or setting the length; see https://stackoverflow.com/q/2152978/23354");
 | 
						|
        }
 | 
						|
 | 
						|
        private Exception CreateException(string message)
 | 
						|
        {
 | 
						|
            return AddErrorData(new ProtoException(message), this);
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a double-precision number from the stream; supported wire-types: Fixed32, Fixed64
 | 
						|
        /// </summary>
 | 
						|
        public
 | 
						|
#if !FEAT_SAFE
 | 
						|
 unsafe
 | 
						|
#endif
 | 
						|
 double ReadDouble()
 | 
						|
        {
 | 
						|
            switch (wireType)
 | 
						|
            {
 | 
						|
                case WireType.Fixed32:
 | 
						|
                    return ReadSingle();
 | 
						|
                case WireType.Fixed64:
 | 
						|
                    long value = ReadInt64();
 | 
						|
#if FEAT_SAFE
 | 
						|
                    return BitConverter.ToDouble(BitConverter.GetBytes(value), 0);
 | 
						|
#else
 | 
						|
                    return *(double*)&value;
 | 
						|
#endif
 | 
						|
                default:
 | 
						|
                    throw CreateWireTypeException();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads (merges) a sub-message from the stream, internally calling StartSubItem and EndSubItem, and (in between)
 | 
						|
        /// parsing the message in accordance with the model associated with the reader
 | 
						|
        /// </summary>
 | 
						|
        public static object ReadObject(object value, int key, ProtoReader reader)
 | 
						|
        {
 | 
						|
            return ReadTypedObject(value, key, reader, null);
 | 
						|
        }
 | 
						|
 | 
						|
        internal static object ReadTypedObject(object value, int key, ProtoReader reader, Type type)
 | 
						|
        {
 | 
						|
            if (reader.model == null)
 | 
						|
            {
 | 
						|
                throw AddErrorData(new InvalidOperationException("Cannot deserialize sub-objects unless a model is provided"), reader);
 | 
						|
            }
 | 
						|
            SubItemToken token = ProtoReader.StartSubItem(reader);
 | 
						|
            if (key >= 0)
 | 
						|
            {
 | 
						|
                value = reader.model.Deserialize(key, value, reader);
 | 
						|
            }
 | 
						|
            else if (type != null && reader.model.TryDeserializeAuxiliaryType(reader, DataFormat.Default, Serializer.ListItemTag, type, ref value, true, false, true, false, null))
 | 
						|
            {
 | 
						|
                // ok
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                TypeModel.ThrowUnexpectedType(type);
 | 
						|
            }
 | 
						|
            ProtoReader.EndSubItem(token, reader);
 | 
						|
            return value;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Makes the end of consuming a nested message in the stream; the stream must be either at the correct EndGroup
 | 
						|
        /// marker, or all fields of the sub-message must have been consumed (in either case, this means ReadFieldHeader
 | 
						|
        /// should return zero)
 | 
						|
        /// </summary>
 | 
						|
        public static void EndSubItem(SubItemToken token, ProtoReader reader)
 | 
						|
        {
 | 
						|
            if (reader == null) throw new ArgumentNullException("reader");
 | 
						|
            long value64 = token.value64;
 | 
						|
            switch (reader.wireType)
 | 
						|
            {
 | 
						|
                case WireType.EndGroup:
 | 
						|
                    if (value64 >= 0) throw AddErrorData(new ArgumentException("token"), reader);
 | 
						|
                    if (-(int)value64 != reader.fieldNumber) throw reader.CreateException("Wrong group was ended"); // wrong group ended!
 | 
						|
                    reader.wireType = WireType.None; // this releases ReadFieldHeader
 | 
						|
                    reader.depth--;
 | 
						|
                    break;
 | 
						|
                // case WireType.None: // TODO reinstate once reads reset the wire-type
 | 
						|
                default:
 | 
						|
                    if (value64 < reader.position64) throw reader.CreateException($"Sub-message not read entirely; expected {value64}, was {reader.position64}");
 | 
						|
                    if (reader.blockEnd64 != reader.position64 && reader.blockEnd64 != long.MaxValue)
 | 
						|
                    {
 | 
						|
                        throw reader.CreateException("Sub-message not read correctly");
 | 
						|
                    }
 | 
						|
                    reader.blockEnd64 = value64;
 | 
						|
                    reader.depth--;
 | 
						|
                    break;
 | 
						|
                    /*default:
 | 
						|
                        throw reader.BorkedIt(); */
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Begins consuming a nested message in the stream; supported wire-types: StartGroup, String
 | 
						|
        /// </summary>
 | 
						|
        /// <remarks>The token returned must be help and used when callining EndSubItem</remarks>
 | 
						|
        public static SubItemToken StartSubItem(ProtoReader reader)
 | 
						|
        {
 | 
						|
            if (reader == null) throw new ArgumentNullException("reader");
 | 
						|
            switch (reader.wireType)
 | 
						|
            {
 | 
						|
                case WireType.StartGroup:
 | 
						|
                    reader.wireType = WireType.None; // to prevent glitches from double-calling
 | 
						|
                    reader.depth++;
 | 
						|
                    return new SubItemToken((long)(-reader.fieldNumber));
 | 
						|
                case WireType.String:
 | 
						|
                    long len = (long)reader.ReadUInt64Variant();
 | 
						|
                    if (len < 0) reader.ThrowInvalidLength(len);
 | 
						|
                    long lastEnd = reader.blockEnd64;
 | 
						|
                    reader.blockEnd64 = reader.position64 + len;
 | 
						|
                    reader.depth++;
 | 
						|
                    return new SubItemToken(lastEnd);
 | 
						|
                default:
 | 
						|
                    throw reader.CreateWireTypeException(); // throws
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a field header from the stream, setting the wire-type and retuning the field number. If no
 | 
						|
        /// more fields are available, then 0 is returned. This methods respects sub-messages.
 | 
						|
        /// </summary>
 | 
						|
        public int ReadFieldHeader()
 | 
						|
        {
 | 
						|
            // at the end of a group the caller must call EndSubItem to release the
 | 
						|
            // reader (which moves the status to Error, since ReadFieldHeader must
 | 
						|
            // then be called)
 | 
						|
            if (blockEnd64 <= position64 || wireType == WireType.EndGroup) { return 0; }
 | 
						|
 | 
						|
            if (TryReadUInt32Variant(out uint tag) && tag != 0)
 | 
						|
            {
 | 
						|
                wireType = (WireType)(tag & 7);
 | 
						|
                fieldNumber = (int)(tag >> 3);
 | 
						|
                if (fieldNumber < 1) throw new ProtoException("Invalid field in source data: " + fieldNumber.ToString());
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                wireType = WireType.None;
 | 
						|
                fieldNumber = 0;
 | 
						|
            }
 | 
						|
            if (wireType == ProtoBuf.WireType.EndGroup)
 | 
						|
            {
 | 
						|
                if (depth > 0) return 0; // spoof an end, but note we still set the field-number
 | 
						|
                throw new ProtoException("Unexpected end-group in source data; this usually means the source data is corrupt");
 | 
						|
            }
 | 
						|
            return fieldNumber;
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Looks ahead to see whether the next field in the stream is what we expect
 | 
						|
        /// (typically; what we've just finished reading - for example ot read successive list items)
 | 
						|
        /// </summary>
 | 
						|
        public bool TryReadFieldHeader(int field)
 | 
						|
        {
 | 
						|
            // check for virtual end of stream
 | 
						|
            if (blockEnd64 <= position64 || wireType == WireType.EndGroup) { return false; }
 | 
						|
 | 
						|
            int read = TryReadUInt32VariantWithoutMoving(false, out uint tag);
 | 
						|
            WireType tmpWireType; // need to catch this to exclude (early) any "end group" tokens
 | 
						|
            if (read > 0 && ((int)tag >> 3) == field
 | 
						|
                && (tmpWireType = (WireType)(tag & 7)) != WireType.EndGroup)
 | 
						|
            {
 | 
						|
                wireType = tmpWireType;
 | 
						|
                fieldNumber = field;
 | 
						|
                position64 += read;
 | 
						|
                ioIndex += read;
 | 
						|
                available -= read;
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Get the TypeModel associated with this reader
 | 
						|
        /// </summary>
 | 
						|
        public TypeModel Model { get { return model; } }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Compares the streams current wire-type to the hinted wire-type, updating the reader if necessary; for example,
 | 
						|
        /// a Variant may be updated to SignedVariant. If the hinted wire-type is unrelated then no change is made.
 | 
						|
        /// </summary>
 | 
						|
        public void Hint(WireType wireType)
 | 
						|
        {
 | 
						|
            if (this.wireType == wireType) { }  // fine; everything as we expect
 | 
						|
            else if (((int)wireType & 7) == (int)this.wireType)
 | 
						|
            {   // the underling type is a match; we're customising it with an extension
 | 
						|
                this.wireType = wireType;
 | 
						|
            }
 | 
						|
            // note no error here; we're OK about using alternative data
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Verifies that the stream's current wire-type is as expected, or a specialized sub-type (for example,
 | 
						|
        /// SignedVariant) - in which case the current wire-type is updated. Otherwise an exception is thrown.
 | 
						|
        /// </summary>
 | 
						|
        public void Assert(WireType wireType)
 | 
						|
        {
 | 
						|
            if (this.wireType == wireType) { }  // fine; everything as we expect
 | 
						|
            else if (((int)wireType & 7) == (int)this.wireType)
 | 
						|
            {   // the underling type is a match; we're customising it with an extension
 | 
						|
                this.wireType = wireType;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {   // nope; that is *not* what we were expecting!
 | 
						|
                throw CreateWireTypeException();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Discards the data for the current field.
 | 
						|
        /// </summary>
 | 
						|
        public void SkipField()
 | 
						|
        {
 | 
						|
            switch (wireType)
 | 
						|
            {
 | 
						|
                case WireType.Fixed32:
 | 
						|
                    if (available < 4) Ensure(4, true);
 | 
						|
                    available -= 4;
 | 
						|
                    ioIndex += 4;
 | 
						|
                    position64 += 4;
 | 
						|
                    return;
 | 
						|
                case WireType.Fixed64:
 | 
						|
                    if (available < 8) Ensure(8, true);
 | 
						|
                    available -= 8;
 | 
						|
                    ioIndex += 8;
 | 
						|
                    position64 += 8;
 | 
						|
                    return;
 | 
						|
                case WireType.String:
 | 
						|
                    long len = (long)ReadUInt64Variant();
 | 
						|
                    if (len < 0) ThrowInvalidLength(len);
 | 
						|
                    if (len <= available)
 | 
						|
                    { // just jump it!
 | 
						|
                        available -= (int)len;
 | 
						|
                        ioIndex += (int)len;
 | 
						|
                        position64 += len;
 | 
						|
                        return;
 | 
						|
                    }
 | 
						|
                    // everything remaining in the buffer is garbage
 | 
						|
                    position64 += len; // assumes success, but if it fails we're screwed anyway
 | 
						|
                    len -= available; // discount anything we've got to-hand
 | 
						|
                    ioIndex = available = 0; // note that we have no data in the buffer
 | 
						|
                    if (isFixedLength)
 | 
						|
                    {
 | 
						|
                        if (len > dataRemaining64) throw EoF(this);
 | 
						|
                        // else assume we're going to be OK
 | 
						|
                        dataRemaining64 -= len;
 | 
						|
                    }
 | 
						|
                    ProtoReader.Seek(source, len, ioBuffer);
 | 
						|
                    return;
 | 
						|
                case WireType.Variant:
 | 
						|
                case WireType.SignedVariant:
 | 
						|
                    ReadUInt64Variant(); // and drop it
 | 
						|
                    return;
 | 
						|
                case WireType.StartGroup:
 | 
						|
                    int originalFieldNumber = this.fieldNumber;
 | 
						|
                    depth++; // need to satisfy the sanity-checks in ReadFieldHeader
 | 
						|
                    while (ReadFieldHeader() > 0) { SkipField(); }
 | 
						|
                    depth--;
 | 
						|
                    if (wireType == WireType.EndGroup && fieldNumber == originalFieldNumber)
 | 
						|
                    { // we expect to exit in a similar state to how we entered
 | 
						|
                        wireType = ProtoBuf.WireType.None;
 | 
						|
                        return;
 | 
						|
                    }
 | 
						|
                    throw CreateWireTypeException();
 | 
						|
                case WireType.None: // treat as explicit errorr
 | 
						|
                case WireType.EndGroup: // treat as explicit error
 | 
						|
                default: // treat as implicit error
 | 
						|
                    throw CreateWireTypeException();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads an unsigned 64-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64
 | 
						|
        /// </summary>
 | 
						|
        public ulong ReadUInt64()
 | 
						|
        {
 | 
						|
            switch (wireType)
 | 
						|
            {
 | 
						|
                case WireType.Variant:
 | 
						|
                    return ReadUInt64Variant();
 | 
						|
                case WireType.Fixed32:
 | 
						|
                    return ReadUInt32();
 | 
						|
                case WireType.Fixed64:
 | 
						|
                    if (available < 8) Ensure(8, true);
 | 
						|
                    position64 += 8;
 | 
						|
                    available -= 8;
 | 
						|
 | 
						|
                    return ((ulong)ioBuffer[ioIndex++])
 | 
						|
                        | (((ulong)ioBuffer[ioIndex++]) << 8)
 | 
						|
                        | (((ulong)ioBuffer[ioIndex++]) << 16)
 | 
						|
                        | (((ulong)ioBuffer[ioIndex++]) << 24)
 | 
						|
                        | (((ulong)ioBuffer[ioIndex++]) << 32)
 | 
						|
                        | (((ulong)ioBuffer[ioIndex++]) << 40)
 | 
						|
                        | (((ulong)ioBuffer[ioIndex++]) << 48)
 | 
						|
                        | (((ulong)ioBuffer[ioIndex++]) << 56);
 | 
						|
                default:
 | 
						|
                    throw CreateWireTypeException();
 | 
						|
            }
 | 
						|
        }
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a single-precision number from the stream; supported wire-types: Fixed32, Fixed64
 | 
						|
        /// </summary>
 | 
						|
        public
 | 
						|
#if !FEAT_SAFE
 | 
						|
 unsafe
 | 
						|
#endif
 | 
						|
 float ReadSingle()
 | 
						|
        {
 | 
						|
            switch (wireType)
 | 
						|
            {
 | 
						|
                case WireType.Fixed32:
 | 
						|
                    {
 | 
						|
                        int value = ReadInt32();
 | 
						|
#if FEAT_SAFE
 | 
						|
                        return BitConverter.ToSingle(BitConverter.GetBytes(value), 0);
 | 
						|
#else
 | 
						|
                        return *(float*)&value;
 | 
						|
#endif
 | 
						|
                    }
 | 
						|
                case WireType.Fixed64:
 | 
						|
                    {
 | 
						|
                        double value = ReadDouble();
 | 
						|
                        float f = (float)value;
 | 
						|
                        if (float.IsInfinity(f) && !double.IsInfinity(value))
 | 
						|
                        {
 | 
						|
                            throw AddErrorData(new OverflowException(), this);
 | 
						|
                        }
 | 
						|
                        return f;
 | 
						|
                    }
 | 
						|
                default:
 | 
						|
                    throw CreateWireTypeException();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a boolean value from the stream; supported wire-types: Variant, Fixed32, Fixed64
 | 
						|
        /// </summary>
 | 
						|
        /// <returns></returns>
 | 
						|
        public bool ReadBoolean()
 | 
						|
        {
 | 
						|
            switch (ReadUInt32())
 | 
						|
            {
 | 
						|
                case 0: return false;
 | 
						|
                case 1: return true;
 | 
						|
                default: throw CreateException("Unexpected boolean value");
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        private static readonly byte[] EmptyBlob = new byte[0];
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a byte-sequence from the stream, appending them to an existing byte-sequence (which can be null); supported wire-types: String
 | 
						|
        /// </summary>
 | 
						|
        public static byte[] AppendBytes(byte[] value, ProtoReader reader)
 | 
						|
        {
 | 
						|
            if (reader == null) throw new ArgumentNullException(nameof(reader));
 | 
						|
            switch (reader.wireType)
 | 
						|
            {
 | 
						|
                case WireType.String:
 | 
						|
                    int len = (int)reader.ReadUInt32Variant(false);
 | 
						|
                    reader.wireType = WireType.None;
 | 
						|
                    if (len == 0) return value ?? EmptyBlob;
 | 
						|
                    if (len < 0) reader.ThrowInvalidLength(len);
 | 
						|
                    int offset;
 | 
						|
                    if (value == null || value.Length == 0)
 | 
						|
                    {
 | 
						|
                        offset = 0;
 | 
						|
                        value = new byte[len];
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        offset = value.Length;
 | 
						|
                        byte[] tmp = new byte[value.Length + len];
 | 
						|
                        Buffer.BlockCopy(value, 0, tmp, 0, value.Length);
 | 
						|
                        value = tmp;
 | 
						|
                    }
 | 
						|
                    // value is now sized with the final length, and (if necessary)
 | 
						|
                    // contains the old data up to "offset"
 | 
						|
                    reader.position64 += len; // assume success
 | 
						|
                    while (len > reader.available)
 | 
						|
                    {
 | 
						|
                        if (reader.available > 0)
 | 
						|
                        {
 | 
						|
                            // copy what we *do* have
 | 
						|
                            Buffer.BlockCopy(reader.ioBuffer, reader.ioIndex, value, offset, reader.available);
 | 
						|
                            len -= reader.available;
 | 
						|
                            offset += reader.available;
 | 
						|
                            reader.ioIndex = reader.available = 0; // we've drained the buffer
 | 
						|
                        }
 | 
						|
                        //  now refill the buffer (without overflowing it)
 | 
						|
                        int count = len > reader.ioBuffer.Length ? reader.ioBuffer.Length : len;
 | 
						|
                        if (count > 0) reader.Ensure(count, true);
 | 
						|
                    }
 | 
						|
                    // at this point, we know that len <= available
 | 
						|
                    if (len > 0)
 | 
						|
                    {   // still need data, but we have enough buffered
 | 
						|
                        Buffer.BlockCopy(reader.ioBuffer, reader.ioIndex, value, offset, len);
 | 
						|
                        reader.ioIndex += len;
 | 
						|
                        reader.available -= len;
 | 
						|
                    }
 | 
						|
                    return value;
 | 
						|
                case WireType.Variant:
 | 
						|
                    return new byte[0];
 | 
						|
                default:
 | 
						|
                    throw reader.CreateWireTypeException();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        //static byte[] ReadBytes(Stream stream, int length)
 | 
						|
        //{
 | 
						|
        //    if (stream == null) throw new ArgumentNullException("stream");
 | 
						|
        //    if (length < 0) throw new ArgumentOutOfRangeException("length");
 | 
						|
        //    byte[] buffer = new byte[length];
 | 
						|
        //    int offset = 0, read;
 | 
						|
        //    while (length > 0 && (read = stream.Read(buffer, offset, length)) > 0)
 | 
						|
        //    {
 | 
						|
        //        length -= read;
 | 
						|
        //    }
 | 
						|
        //    if (length > 0) throw EoF(null);
 | 
						|
        //    return buffer;
 | 
						|
        //}
 | 
						|
        private static int ReadByteOrThrow(Stream source)
 | 
						|
        {
 | 
						|
            int val = source.ReadByte();
 | 
						|
            if (val < 0) throw EoF(null);
 | 
						|
            return val;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads the length-prefix of a message from a stream without buffering additional data, allowing a fixed-length
 | 
						|
        /// reader to be created.
 | 
						|
        /// </summary>
 | 
						|
        public static int ReadLengthPrefix(Stream source, bool expectHeader, PrefixStyle style, out int fieldNumber)
 | 
						|
            => ReadLengthPrefix(source, expectHeader, style, out fieldNumber, out int bytesRead);
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a little-endian encoded integer. An exception is thrown if the data is not all available.
 | 
						|
        /// </summary>
 | 
						|
        public static int DirectReadLittleEndianInt32(Stream source)
 | 
						|
        {
 | 
						|
            return ReadByteOrThrow(source)
 | 
						|
                | (ReadByteOrThrow(source) << 8)
 | 
						|
                | (ReadByteOrThrow(source) << 16)
 | 
						|
                | (ReadByteOrThrow(source) << 24);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a big-endian encoded integer. An exception is thrown if the data is not all available.
 | 
						|
        /// </summary>
 | 
						|
        public static int DirectReadBigEndianInt32(Stream source)
 | 
						|
        {
 | 
						|
            return (ReadByteOrThrow(source) << 24)
 | 
						|
                 | (ReadByteOrThrow(source) << 16)
 | 
						|
                 | (ReadByteOrThrow(source) << 8)
 | 
						|
                 | ReadByteOrThrow(source);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a varint encoded integer. An exception is thrown if the data is not all available.
 | 
						|
        /// </summary>
 | 
						|
        public static int DirectReadVarintInt32(Stream source)
 | 
						|
        {
 | 
						|
            int bytes = TryReadUInt64Variant(source, out ulong val);
 | 
						|
            if (bytes <= 0) throw EoF(null);
 | 
						|
            return checked((int)val);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a string (of a given lenth, in bytes) directly from the source into a pre-existing buffer. An exception is thrown if the data is not all available.
 | 
						|
        /// </summary>
 | 
						|
        public static void DirectReadBytes(Stream source, byte[] buffer, int offset, int count)
 | 
						|
        {
 | 
						|
            int read;
 | 
						|
            if (source == null) throw new ArgumentNullException("source");
 | 
						|
            while (count > 0 && (read = source.Read(buffer, offset, count)) > 0)
 | 
						|
            {
 | 
						|
                count -= read;
 | 
						|
                offset += read;
 | 
						|
            }
 | 
						|
            if (count > 0) throw EoF(null);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a given number of bytes directly from the source. An exception is thrown if the data is not all available.
 | 
						|
        /// </summary>
 | 
						|
        public static byte[] DirectReadBytes(Stream source, int count)
 | 
						|
        {
 | 
						|
            byte[] buffer = new byte[count];
 | 
						|
            DirectReadBytes(source, buffer, 0, count);
 | 
						|
            return buffer;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a string (of a given lenth, in bytes) directly from the source. An exception is thrown if the data is not all available.
 | 
						|
        /// </summary>
 | 
						|
        public static string DirectReadString(Stream source, int length)
 | 
						|
        {
 | 
						|
            byte[] buffer = new byte[length];
 | 
						|
            DirectReadBytes(source, buffer, 0, length);
 | 
						|
            return Encoding.UTF8.GetString(buffer, 0, length);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads the length-prefix of a message from a stream without buffering additional data, allowing a fixed-length
 | 
						|
        /// reader to be created.
 | 
						|
        /// </summary>
 | 
						|
        public static int ReadLengthPrefix(Stream source, bool expectHeader, PrefixStyle style, out int fieldNumber, out int bytesRead)
 | 
						|
        {
 | 
						|
            if (style == PrefixStyle.None)
 | 
						|
            {
 | 
						|
                bytesRead = fieldNumber = 0;
 | 
						|
                return int.MaxValue; // avoid the long.maxvalue causing overflow
 | 
						|
            }
 | 
						|
            long len64 = ReadLongLengthPrefix(source, expectHeader, style, out fieldNumber, out bytesRead);
 | 
						|
            return checked((int)len64);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads the length-prefix of a message from a stream without buffering additional data, allowing a fixed-length
 | 
						|
        /// reader to be created.
 | 
						|
        /// </summary>
 | 
						|
        public static long ReadLongLengthPrefix(Stream source, bool expectHeader, PrefixStyle style, out int fieldNumber, out int bytesRead)
 | 
						|
        {
 | 
						|
            fieldNumber = 0;
 | 
						|
            switch (style)
 | 
						|
            {
 | 
						|
                case PrefixStyle.None:
 | 
						|
                    bytesRead = 0;
 | 
						|
                    return long.MaxValue;
 | 
						|
                case PrefixStyle.Base128:
 | 
						|
                    ulong val;
 | 
						|
                    int tmpBytesRead;
 | 
						|
                    bytesRead = 0;
 | 
						|
                    if (expectHeader)
 | 
						|
                    {
 | 
						|
                        tmpBytesRead = ProtoReader.TryReadUInt64Variant(source, out val);
 | 
						|
                        bytesRead += tmpBytesRead;
 | 
						|
                        if (tmpBytesRead > 0)
 | 
						|
                        {
 | 
						|
                            if ((val & 7) != (uint)WireType.String)
 | 
						|
                            { // got a header, but it isn't a string
 | 
						|
                                throw new InvalidOperationException();
 | 
						|
                            }
 | 
						|
                            fieldNumber = (int)(val >> 3);
 | 
						|
                            tmpBytesRead = ProtoReader.TryReadUInt64Variant(source, out val);
 | 
						|
                            bytesRead += tmpBytesRead;
 | 
						|
                            if (bytesRead == 0)
 | 
						|
                            { // got a header, but no length
 | 
						|
                                throw EoF(null);
 | 
						|
                            }
 | 
						|
                            return (long)val;
 | 
						|
                        }
 | 
						|
                        else
 | 
						|
                        { // no header
 | 
						|
                            bytesRead = 0;
 | 
						|
                            return -1;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    // check for a length
 | 
						|
                    tmpBytesRead = ProtoReader.TryReadUInt64Variant(source, out val);
 | 
						|
                    bytesRead += tmpBytesRead;
 | 
						|
                    return bytesRead < 0 ? -1 : (long)val;
 | 
						|
 | 
						|
                case PrefixStyle.Fixed32:
 | 
						|
                    {
 | 
						|
                        int b = source.ReadByte();
 | 
						|
                        if (b < 0)
 | 
						|
                        {
 | 
						|
                            bytesRead = 0;
 | 
						|
                            return -1;
 | 
						|
                        }
 | 
						|
                        bytesRead = 4;
 | 
						|
                        return b
 | 
						|
                             | (ReadByteOrThrow(source) << 8)
 | 
						|
                             | (ReadByteOrThrow(source) << 16)
 | 
						|
                             | (ReadByteOrThrow(source) << 24);
 | 
						|
                    }
 | 
						|
                case PrefixStyle.Fixed32BigEndian:
 | 
						|
                    {
 | 
						|
                        int b = source.ReadByte();
 | 
						|
                        if (b < 0)
 | 
						|
                        {
 | 
						|
                            bytesRead = 0;
 | 
						|
                            return -1;
 | 
						|
                        }
 | 
						|
                        bytesRead = 4;
 | 
						|
                        return (b << 24)
 | 
						|
                            | (ReadByteOrThrow(source) << 16)
 | 
						|
                            | (ReadByteOrThrow(source) << 8)
 | 
						|
                            | ReadByteOrThrow(source);
 | 
						|
                    }
 | 
						|
                default:
 | 
						|
                    throw new ArgumentOutOfRangeException("style");
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <returns>The number of bytes consumed; 0 if no data available</returns>
 | 
						|
        private static int TryReadUInt64Variant(Stream source, out ulong value)
 | 
						|
        {
 | 
						|
            value = 0;
 | 
						|
            int b = source.ReadByte();
 | 
						|
            if (b < 0) { return 0; }
 | 
						|
            value = (uint)b;
 | 
						|
            if ((value & 0x80) == 0) { return 1; }
 | 
						|
            value &= 0x7F;
 | 
						|
            int bytesRead = 1, shift = 7;
 | 
						|
            while (bytesRead < 9)
 | 
						|
            {
 | 
						|
                b = source.ReadByte();
 | 
						|
                if (b < 0) throw EoF(null);
 | 
						|
                value |= ((ulong)b & 0x7F) << shift;
 | 
						|
                shift += 7;
 | 
						|
                bytesRead++;
 | 
						|
 | 
						|
                if ((b & 0x80) == 0) return bytesRead;
 | 
						|
            }
 | 
						|
            b = source.ReadByte();
 | 
						|
            if (b < 0) throw EoF(null);
 | 
						|
            if ((b & 1) == 0) // only use 1 bit from the last byte
 | 
						|
            {
 | 
						|
                value |= ((ulong)b & 0x7F) << shift;
 | 
						|
                return ++bytesRead;
 | 
						|
            }
 | 
						|
            throw new OverflowException();
 | 
						|
        }
 | 
						|
 | 
						|
        internal static void Seek(Stream source, long count, byte[] buffer)
 | 
						|
        {
 | 
						|
            if (source.CanSeek)
 | 
						|
            {
 | 
						|
                source.Seek(count, SeekOrigin.Current);
 | 
						|
                count = 0;
 | 
						|
            }
 | 
						|
            else if (buffer != null)
 | 
						|
            {
 | 
						|
                int bytesRead;
 | 
						|
                while (count > buffer.Length && (bytesRead = source.Read(buffer, 0, buffer.Length)) > 0)
 | 
						|
                {
 | 
						|
                    count -= bytesRead;
 | 
						|
                }
 | 
						|
                while (count > 0 && (bytesRead = source.Read(buffer, 0, (int)count)) > 0)
 | 
						|
                {
 | 
						|
                    count -= bytesRead;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            else // borrow a buffer
 | 
						|
            {
 | 
						|
                buffer = BufferPool.GetBuffer();
 | 
						|
                try
 | 
						|
                {
 | 
						|
                    int bytesRead;
 | 
						|
                    while (count > buffer.Length && (bytesRead = source.Read(buffer, 0, buffer.Length)) > 0)
 | 
						|
                    {
 | 
						|
                        count -= bytesRead;
 | 
						|
                    }
 | 
						|
                    while (count > 0 && (bytesRead = source.Read(buffer, 0, (int)count)) > 0)
 | 
						|
                    {
 | 
						|
                        count -= bytesRead;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                finally
 | 
						|
                {
 | 
						|
                    BufferPool.ReleaseBufferToPool(ref buffer);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if (count > 0) throw EoF(null);
 | 
						|
        }
 | 
						|
        internal static Exception AddErrorData(Exception exception, ProtoReader source)
 | 
						|
        {
 | 
						|
#if !CF && !PORTABLE
 | 
						|
            if (exception != null && source != null && !exception.Data.Contains("protoSource"))
 | 
						|
            {
 | 
						|
                exception.Data.Add("protoSource", string.Format("tag={0}; wire-type={1}; offset={2}; depth={3}",
 | 
						|
                    source.fieldNumber, source.wireType, source.position64, source.depth));
 | 
						|
            }
 | 
						|
#endif
 | 
						|
            return exception;
 | 
						|
        }
 | 
						|
 | 
						|
        private static Exception EoF(ProtoReader source)
 | 
						|
        {
 | 
						|
            return AddErrorData(new EndOfStreamException(), source);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Copies the current field into the instance as extension data
 | 
						|
        /// </summary>
 | 
						|
        public void AppendExtensionData(IExtensible instance)
 | 
						|
        {
 | 
						|
            if (instance == null) throw new ArgumentNullException(nameof(instance));
 | 
						|
            IExtension extn = instance.GetExtensionObject(true);
 | 
						|
            bool commit = false;
 | 
						|
            // unusually we *don't* want "using" here; the "finally" does that, with
 | 
						|
            // the extension object being responsible for disposal etc
 | 
						|
            Stream dest = extn.BeginAppend();
 | 
						|
            try
 | 
						|
            {
 | 
						|
                //TODO: replace this with stream-based, buffered raw copying
 | 
						|
                using (ProtoWriter writer = ProtoWriter.Create(dest, model, null))
 | 
						|
                {
 | 
						|
                    AppendExtensionField(writer);
 | 
						|
                    writer.Close();
 | 
						|
                }
 | 
						|
                commit = true;
 | 
						|
            }
 | 
						|
            finally { extn.EndAppend(dest, commit); }
 | 
						|
        }
 | 
						|
 | 
						|
        private void AppendExtensionField(ProtoWriter writer)
 | 
						|
        {
 | 
						|
            //TODO: replace this with stream-based, buffered raw copying
 | 
						|
            ProtoWriter.WriteFieldHeader(fieldNumber, wireType, writer);
 | 
						|
            switch (wireType)
 | 
						|
            {
 | 
						|
                case WireType.Fixed32:
 | 
						|
                    ProtoWriter.WriteInt32(ReadInt32(), writer);
 | 
						|
                    return;
 | 
						|
                case WireType.Variant:
 | 
						|
                case WireType.SignedVariant:
 | 
						|
                case WireType.Fixed64:
 | 
						|
                    ProtoWriter.WriteInt64(ReadInt64(), writer);
 | 
						|
                    return;
 | 
						|
                case WireType.String:
 | 
						|
                    ProtoWriter.WriteBytes(AppendBytes(null, this), writer);
 | 
						|
                    return;
 | 
						|
                case WireType.StartGroup:
 | 
						|
                    SubItemToken readerToken = StartSubItem(this),
 | 
						|
                        writerToken = ProtoWriter.StartSubItem(null, writer);
 | 
						|
                    while (ReadFieldHeader() > 0) { AppendExtensionField(writer); }
 | 
						|
                    EndSubItem(readerToken, this);
 | 
						|
                    ProtoWriter.EndSubItem(writerToken, writer);
 | 
						|
                    return;
 | 
						|
                case WireType.None: // treat as explicit errorr
 | 
						|
                case WireType.EndGroup: // treat as explicit error
 | 
						|
                default: // treat as implicit error
 | 
						|
                    throw CreateWireTypeException();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Indicates whether the reader still has data remaining in the current sub-item,
 | 
						|
        /// additionally setting the wire-type for the next field if there is more data.
 | 
						|
        /// This is used when decoding packed data.
 | 
						|
        /// </summary>
 | 
						|
        public static bool HasSubValue(ProtoBuf.WireType wireType, ProtoReader source)
 | 
						|
        {
 | 
						|
            if (source == null) throw new ArgumentNullException("source");
 | 
						|
            // check for virtual end of stream
 | 
						|
            if (source.blockEnd64 <= source.position64 || wireType == WireType.EndGroup) { return false; }
 | 
						|
            source.wireType = wireType;
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
 | 
						|
        internal int GetTypeKey(ref Type type)
 | 
						|
        {
 | 
						|
            return model.GetKey(ref type);
 | 
						|
        }
 | 
						|
 | 
						|
        internal NetObjectCache NetCache => netCache;
 | 
						|
 | 
						|
        internal Type DeserializeType(string value)
 | 
						|
        {
 | 
						|
            return TypeModel.DeserializeType(model, value);
 | 
						|
        }
 | 
						|
 | 
						|
        internal void SetRootObject(object value)
 | 
						|
        {
 | 
						|
            netCache.SetKeyedObject(NetObjectCache.Root, value);
 | 
						|
            trapCount--;
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Utility method, not intended for public use; this helps maintain the root object is complex scenarios
 | 
						|
        /// </summary>
 | 
						|
        public static void NoteObject(object value, ProtoReader reader)
 | 
						|
        {
 | 
						|
            if (reader == null) throw new ArgumentNullException("reader");
 | 
						|
            if (reader.trapCount != 0)
 | 
						|
            {
 | 
						|
                reader.netCache.RegisterTrappedObject(value);
 | 
						|
                reader.trapCount--;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Reads a Type from the stream, using the model's DynamicTypeFormatting if appropriate; supported wire-types: String
 | 
						|
        /// </summary>
 | 
						|
        public Type ReadType()
 | 
						|
        {
 | 
						|
            return TypeModel.DeserializeType(model, ReadString());
 | 
						|
        }
 | 
						|
 | 
						|
        internal void TrapNextObject(int newObjectKey)
 | 
						|
        {
 | 
						|
            trapCount++;
 | 
						|
            netCache.SetKeyedObject(newObjectKey, null); // use null as a temp
 | 
						|
        }
 | 
						|
 | 
						|
        internal void CheckFullyConsumed()
 | 
						|
        {
 | 
						|
            if (isFixedLength)
 | 
						|
            {
 | 
						|
                if (dataRemaining64 != 0) throw new ProtoException("Incorrect number of bytes consumed");
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                if (available != 0) throw new ProtoException("Unconsumed data left in the buffer; this suggests corrupt input");
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /// <summary>
 | 
						|
        /// Merge two objects using the details from the current reader; this is used to change the type
 | 
						|
        /// of objects when an inheritance relationship is discovered later than usual during deserilazation.
 | 
						|
        /// </summary>
 | 
						|
        public static object Merge(ProtoReader parent, object from, object to)
 | 
						|
        {
 | 
						|
            if (parent == null) throw new ArgumentNullException("parent");
 | 
						|
            TypeModel model = parent.Model;
 | 
						|
            SerializationContext ctx = parent.Context;
 | 
						|
            if (model == null) throw new InvalidOperationException("Types cannot be merged unless a type-model has been specified");
 | 
						|
            using (var ms = new MemoryStream())
 | 
						|
            {
 | 
						|
                model.Serialize(ms, from, ctx);
 | 
						|
                ms.Position = 0;
 | 
						|
                return model.Deserialize(ms, to, null);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        #region RECYCLER
 | 
						|
 | 
						|
        internal static ProtoReader Create(Stream source, TypeModel model, SerializationContext context, int len)
 | 
						|
            => Create(source, model, context, (long)len);
 | 
						|
        /// <summary>
 | 
						|
        /// Creates a new reader against a stream
 | 
						|
        /// </summary>
 | 
						|
        /// <param name="source">The source stream</param>
 | 
						|
        /// <param name="model">The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects</param>
 | 
						|
        /// <param name="context">Additional context about this serialization operation</param>
 | 
						|
        /// <param name="length">The number of bytes to read, or -1 to read until the end of the stream</param>
 | 
						|
        public static ProtoReader Create(Stream source, TypeModel model, SerializationContext context = null, long length = TO_EOF)
 | 
						|
        {
 | 
						|
            ProtoReader reader = GetRecycled();
 | 
						|
            if (reader == null)
 | 
						|
            {
 | 
						|
#pragma warning disable CS0618
 | 
						|
                return new ProtoReader(source, model, context, length);
 | 
						|
#pragma warning restore CS0618
 | 
						|
            }
 | 
						|
            Init(reader, source, model, context, length);
 | 
						|
            return reader;
 | 
						|
        }
 | 
						|
 | 
						|
#if !PLAT_NO_THREADSTATIC
 | 
						|
        [ThreadStatic]
 | 
						|
        private static ProtoReader lastReader;
 | 
						|
 | 
						|
        private static ProtoReader GetRecycled()
 | 
						|
        {
 | 
						|
            ProtoReader tmp = lastReader;
 | 
						|
            lastReader = null;
 | 
						|
            return tmp;
 | 
						|
        }
 | 
						|
        internal static void Recycle(ProtoReader reader)
 | 
						|
        {
 | 
						|
            if (reader != null)
 | 
						|
            {
 | 
						|
                reader.Dispose();
 | 
						|
                lastReader = reader;
 | 
						|
            }
 | 
						|
        }
 | 
						|
#elif !PLAT_NO_INTERLOCKED
 | 
						|
        private static object lastReader;
 | 
						|
        private static ProtoReader GetRecycled()
 | 
						|
        {
 | 
						|
            return (ProtoReader)System.Threading.Interlocked.Exchange(ref lastReader, null);
 | 
						|
        }
 | 
						|
        internal static void Recycle(ProtoReader reader)
 | 
						|
        {
 | 
						|
            if(reader != null)
 | 
						|
            {
 | 
						|
                reader.Dispose();
 | 
						|
                System.Threading.Interlocked.Exchange(ref lastReader, reader);
 | 
						|
            }
 | 
						|
        }
 | 
						|
#else
 | 
						|
        private static readonly object recycleLock = new object();
 | 
						|
        private static ProtoReader lastReader;
 | 
						|
        private static ProtoReader GetRecycled()
 | 
						|
        {
 | 
						|
            lock(recycleLock)
 | 
						|
            {
 | 
						|
                ProtoReader tmp = lastReader;
 | 
						|
                lastReader = null;
 | 
						|
                return tmp;
 | 
						|
            }            
 | 
						|
        }
 | 
						|
        internal static void Recycle(ProtoReader reader)
 | 
						|
        {
 | 
						|
            if(reader != null)
 | 
						|
            {
 | 
						|
                reader.Dispose();
 | 
						|
                lock(recycleLock)
 | 
						|
                {
 | 
						|
                    lastReader = reader;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
#endif
 | 
						|
 | 
						|
        #endregion
 | 
						|
    }
 | 
						|
}
 |