using System; using System.IO; using System.Text; using ProtoBuf.Meta; #if FEAT_IKVM using Type = IKVM.Reflection.Type; #endif #if MF using EndOfStreamException = System.ApplicationException; using OverflowException = System.ApplicationException; #endif namespace ProtoBuf { /// /// 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. /// public sealed class ProtoReader : IDisposable { Stream source; byte[] ioBuffer; TypeModel model; int fieldNumber, depth, dataRemaining, ioIndex, position, available, blockEnd; 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 /// /// Gets the number of the field being processed. /// public int FieldNumber { get { return fieldNumber; } } /// /// Indicates the underlying proto serialization format on the wire. /// public WireType WireType { get { return wireType; } } /// /// Creates a new reader against a stream /// /// The source stream /// The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects /// Additional context about this serialization operation public ProtoReader(Stream source, TypeModel model, SerializationContext context) { Init(this, source, model, context, TO_EOF); } internal const int TO_EOF = -1; /// /// 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 custom interner - the system-wide string interner is not used. /// public bool InternStrings { get { return internStrings; } set { internStrings = value; } } /// /// Creates a new reader against a stream /// /// The source stream /// The model to use for serialization; this can be null, but this will impair the ability to deserialize sub-objects /// Additional context about this serialization operation /// The number of bytes to read, or -1 to read until the end of the stream public ProtoReader(Stream source, TypeModel model, SerializationContext context, int length) { Init(this, source, model, context, length); } private static void Init(ProtoReader reader, Stream source, TypeModel model, SerializationContext context, int length) { if (source == null) throw new ArgumentNullException("source"); if (!source.CanRead) throw new ArgumentException("Cannot read from stream", "source"); reader.source = source; reader.ioBuffer = BufferPool.GetBuffer(); reader.model = model; bool isFixedLength = length >= 0; reader.isFixedLength = isFixedLength; reader.dataRemaining = isFixedLength ? length : 0; if (context == null) { context = SerializationContext.Default; } else { context.Freeze(); } reader.context = context; reader.position = reader.available = reader.depth = reader.fieldNumber = reader.ioIndex = 0; reader.blockEnd = int.MaxValue; reader.internStrings = true; reader.wireType = WireType.None; reader.trapCount = 1; if(reader.netCache == null) reader.netCache = new NetObjectCache(); } private SerializationContext context; /// /// Addition information about this deserialization operation. /// public SerializationContext Context { get { return context; } } /// /// Releases resources used by the reader, but importantly does not 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. /// 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(); 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) { uint value; int read = TryReadUInt32VariantWithoutMoving(trimNegative, out value); if (read > 0) { ioIndex += read; available -= read; position += 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; position += read; return true; } return false; } /// /// Reads an unsigned 32-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64 /// public uint ReadUInt32() { switch (wireType) { case WireType.Variant: return ReadUInt32Variant(false); case WireType.Fixed32: if (available < 4) Ensure(4, true); position += 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(); } } /// /// 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) /// public int Position { get { return position; } } 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 Helpers.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 (dataRemaining < canRead) canRead = dataRemaining; } while (count > 0 && canRead > 0 && (bytesRead = source.Read(ioBuffer, writePos, canRead)) > 0) { available += bytesRead; count -= bytesRead; canRead -= bytesRead; writePos += bytesRead; if (isFixedLength) { dataRemaining -= bytesRead; } } if (strict && count > 0) { throw EoF(this); } } /// /// Reads a signed 16-bit integer from the stream: Variant, Fixed32, Fixed64, SignedVariant /// public short ReadInt16() { checked { return (short)ReadInt32(); } } /// /// Reads an unsigned 16-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64 /// public ushort ReadUInt16() { checked { return (ushort)ReadUInt32(); } } /// /// Reads an unsigned 8-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64 /// public byte ReadByte() { checked { return (byte)ReadUInt32(); } } /// /// Reads a signed 8-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant /// public sbyte ReadSByte() { checked { return (sbyte)ReadInt32(); } } /// /// Reads a signed 32-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant /// public int ReadInt32() { switch (wireType) { case WireType.Variant: return (int)ReadUInt32Variant(true); case WireType.Fixed32: if (available < 4) Ensure(4, true); position += 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); } /// /// Reads a signed 64-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64, SignedVariant /// 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); position += 8; available -= 8; 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); 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() { ulong value; int read = TryReadUInt64VariantWithoutMoving(out value); if (read > 0) { ioIndex += read; available -= read; position += read; return value; } throw EoF(this); } #if NO_GENERICS private System.Collections.Hashtable stringInterner; private string Intern(string value) { if (value == null) return null; if (value.Length == 0) return ""; if (stringInterner == null) { stringInterner = new System.Collections.Hashtable(); stringInterner.Add(value, value); } else if (stringInterner.ContainsKey(value)) { value = (string)stringInterner[value]; } else { stringInterner.Add(value, value); } return value; } #else private System.Collections.Generic.Dictionary stringInterner; private string Intern(string value) { if (value == null) return null; if (value.Length == 0) return ""; string found; if (stringInterner == null) { stringInterner = new System.Collections.Generic.Dictionary(); stringInterner.Add(value, value); } else if (stringInterner.TryGetValue(value, out found)) { value = found; } else { stringInterner.Add(value, value); } return value; } #endif #if COREFX static readonly Encoding encoding = Encoding.UTF8; #else static readonly UTF8Encoding encoding = new UTF8Encoding(); #endif /// /// Reads a string from the stream (using UTF8); supported wire-types: String /// public string ReadString() { if (wireType == WireType.String) { int bytes = (int)ReadUInt32Variant(false); if (bytes == 0) return ""; if (available < bytes) Ensure(bytes, true); #if MF byte[] tmp; if(ioIndex == 0 && bytes == ioBuffer.Length) { // unlikely, but... tmp = ioBuffer; } else { tmp = new byte[bytes]; Helpers.BlockCopy(ioBuffer, ioIndex, tmp, 0, bytes); } string s = new string(encoding.GetChars(tmp)); #else string s = encoding.GetString(ioBuffer, ioIndex, bytes); #endif if (internStrings) { s = Intern(s); } available -= bytes; position += bytes; ioIndex += bytes; return s; } throw CreateWireTypeException(); } /// /// Throws an exception indication that the given value cannot be mapped to an enum. /// public void ThrowEnumException(System.Type type, int value) { string desc = type == null ? "" : type.FullName; throw AddErrorData(new ProtoException("No " + desc + " enum is mapped to the wire-value " + value.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 http://stackoverflow.com/q/2152978/23354"); } private Exception CreateException(string message) { return AddErrorData(new ProtoException(message), this); } /// /// Reads a double-precision number from the stream; supported wire-types: Fixed32, Fixed64 /// 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(); } } /// /// 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 /// public static object ReadObject(object value, int key, ProtoReader reader) { #if FEAT_IKVM throw new NotSupportedException(); #else return ReadTypedObject(value, key, reader, null); #endif } #if !FEAT_IKVM 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)) { // ok } else { TypeModel.ThrowUnexpectedType(type); } ProtoReader.EndSubItem(token, reader); return value; } #endif /// /// 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) /// public static void EndSubItem(SubItemToken token, ProtoReader reader) { if (reader == null) throw new ArgumentNullException("reader"); int value = token.value; switch (reader.wireType) { case WireType.EndGroup: if (value >= 0) throw AddErrorData(new ArgumentException("token"), reader); if (-value != 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 (value < reader.position) throw reader.CreateException("Sub-message not read entirely"); if (reader.blockEnd != reader.position && reader.blockEnd != int.MaxValue) { throw reader.CreateException("Sub-message not read correctly"); } reader.blockEnd = value; reader.depth--; break; /*default: throw reader.BorkedIt(); */ } } /// /// Begins consuming a nested message in the stream; supported wire-types: StartGroup, String /// /// The token returned must be help and used when callining EndSubItem 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(-reader.fieldNumber); case WireType.String: int len = (int)reader.ReadUInt32Variant(false); if (len < 0) throw AddErrorData(new InvalidOperationException(), reader); int lastEnd = reader.blockEnd; reader.blockEnd = reader.position + len; reader.depth++; return new SubItemToken(lastEnd); default: throw reader.CreateWireTypeException(); // throws } } /// /// 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. /// 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 (blockEnd <= position || wireType == WireType.EndGroup) { return 0; } uint tag; if (TryReadUInt32Variant(out 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; } /// /// 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) /// public bool TryReadFieldHeader(int field) { // check for virtual end of stream if (blockEnd <= position || wireType == WireType.EndGroup) { return false; } uint tag; int read = TryReadUInt32VariantWithoutMoving(false, out 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; position += read; ioIndex += read; available -= read; return true; } return false; } /// /// Get the TypeModel associated with this reader /// public TypeModel Model { get { return model; } } /// /// 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. /// 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 } /// /// 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. /// 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(); } } /// /// Discards the data for the current field. /// public void SkipField() { switch (wireType) { case WireType.Fixed32: if(available < 4) Ensure(4, true); available -= 4; ioIndex += 4; position += 4; return; case WireType.Fixed64: if (available < 8) Ensure(8, true); available -= 8; ioIndex += 8; position += 8; return; case WireType.String: int len = (int)ReadUInt32Variant(false); if (len <= available) { // just jump it! available -= len; ioIndex += len; position += len; return; } // everything remaining in the buffer is garbage position += 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 > dataRemaining) throw EoF(this); // else assume we're going to be OK dataRemaining -= 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(); } } /// /// Reads an unsigned 64-bit integer from the stream; supported wire-types: Variant, Fixed32, Fixed64 /// public ulong ReadUInt64() { switch (wireType) { case WireType.Variant: return ReadUInt64Variant(); case WireType.Fixed32: return ReadUInt32(); case WireType.Fixed64: if (available < 8) Ensure(8, true); position += 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(); } } /// /// Reads a single-precision number from the stream; supported wire-types: Fixed32, Fixed64 /// 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 (Helpers.IsInfinity(f) && !Helpers.IsInfinity(value)) { throw AddErrorData(new OverflowException(), this); } return f; } default: throw CreateWireTypeException(); } } /// /// Reads a boolean value from the stream; supported wire-types: Variant, Fixed32, Fixed64 /// /// 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]; /// /// Reads a byte-sequence from the stream, appending them to an existing byte-sequence (which can be null); supported wire-types: String /// public static byte[] AppendBytes(byte[] value, ProtoReader reader) { if (reader == null) throw new ArgumentNullException("reader"); switch (reader.wireType) { case WireType.String: int len = (int)reader.ReadUInt32Variant(false); reader.wireType = WireType.None; if (len == 0) return value == null ? EmptyBlob : value; 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]; Helpers.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.position += len; // assume success while (len > reader.available) { if (reader.available > 0) { // copy what we *do* have Helpers.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 Helpers.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; } /// /// Reads the length-prefix of a message from a stream without buffering additional data, allowing a fixed-length /// reader to be created. /// public static int ReadLengthPrefix(Stream source, bool expectHeader, PrefixStyle style, out int fieldNumber) { int bytesRead; return ReadLengthPrefix(source, expectHeader, style, out fieldNumber, out bytesRead); } /// /// Reads a little-endian encoded integer. An exception is thrown if the data is not all available. /// public static int DirectReadLittleEndianInt32(Stream source) { return ReadByteOrThrow(source) | (ReadByteOrThrow(source) << 8) | (ReadByteOrThrow(source) << 16) | (ReadByteOrThrow(source) << 24); } /// /// Reads a big-endian encoded integer. An exception is thrown if the data is not all available. /// public static int DirectReadBigEndianInt32(Stream source) { return (ReadByteOrThrow(source) << 24) | (ReadByteOrThrow(source) << 16) | (ReadByteOrThrow(source) << 8) | ReadByteOrThrow(source); } /// /// Reads a varint encoded integer. An exception is thrown if the data is not all available. /// public static int DirectReadVarintInt32(Stream source) { uint val; int bytes = TryReadUInt32Variant(source, out val); if (bytes <= 0) throw EoF(null); return (int) val; } /// /// 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. /// 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); } /// /// Reads a given number of bytes directly from the source. An exception is thrown if the data is not all available. /// public static byte[] DirectReadBytes(Stream source, int count) { byte[] buffer = new byte[count]; DirectReadBytes(source, buffer, 0, count); return buffer; } /// /// Reads a string (of a given lenth, in bytes) directly from the source. An exception is thrown if the data is not all available. /// 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); } /// /// Reads the length-prefix of a message from a stream without buffering additional data, allowing a fixed-length /// reader to be created. /// public static int ReadLengthPrefix(Stream source, bool expectHeader, PrefixStyle style, out int fieldNumber, out int bytesRead) { fieldNumber = 0; switch (style) { case PrefixStyle.None: bytesRead = 0; return int.MaxValue; case PrefixStyle.Base128: uint val; int tmpBytesRead; bytesRead = 0; if (expectHeader) { tmpBytesRead = ProtoReader.TryReadUInt32Variant(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.TryReadUInt32Variant(source, out val); bytesRead += tmpBytesRead; if (bytesRead == 0) { // got a header, but no length throw EoF(null); } return (int)val; } else { // no header bytesRead = 0; return -1; } } // check for a length tmpBytesRead = ProtoReader.TryReadUInt32Variant(source, out val); bytesRead += tmpBytesRead; return bytesRead < 0 ? -1 : (int)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"); } } /// The number of bytes consumed; 0 if no data available private static int TryReadUInt32Variant(Stream source, out uint value) { value = 0; int b = source.ReadByte(); if (b < 0) { return 0; } value = (uint)b; if ((value & 0x80) == 0) { return 1; } value &= 0x7F; b = source.ReadByte(); if (b < 0) throw EoF(null); value |= ((uint)b & 0x7F) << 7; if ((b & 0x80) == 0) return 2; b = source.ReadByte(); if (b < 0) throw EoF(null); value |= ((uint)b & 0x7F) << 14; if ((b & 0x80) == 0) return 3; b = source.ReadByte(); if (b < 0) throw EoF(null); value |= ((uint)b & 0x7F) << 21; if ((b & 0x80) == 0) return 4; b = source.ReadByte(); if (b < 0) throw EoF(null); value |= (uint)b << 28; // can only use 4 bits from this chunk if ((b & 0xF0) == 0) return 5; throw new OverflowException(); } internal static void Seek(Stream source, int 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, 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, 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 && !FX11 && !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.position, source.depth)); } #endif return exception; } private static Exception EoF(ProtoReader source) { return AddErrorData(new EndOfStreamException(), source); } /// /// Copies the current field into the instance as extension data /// public void AppendExtensionData(IExtensible instance) { if (instance == null) throw new ArgumentNullException("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 = new ProtoWriter(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(); } } /// /// 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. /// public static bool HasSubValue(ProtoBuf.WireType wireType, ProtoReader source) { if (source == null) throw new ArgumentNullException("source"); // check for virtual end of stream if (source.blockEnd <= source.position || wireType == WireType.EndGroup) { return false; } source.wireType = wireType; return true; } internal int GetTypeKey(ref Type type) { return model.GetKey(ref type); } internal NetObjectCache NetCache { get { return netCache; } } internal System.Type DeserializeType(string value) { return TypeModel.DeserializeType(model, value); } internal void SetRootObject(object value) { netCache.SetKeyedObject(NetObjectCache.Root, value); trapCount--; } /// /// Utility method, not intended for public use; this helps maintain the root object is complex scenarios /// 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--; } } /// /// Reads a Type from the stream, using the model's DynamicTypeFormatting if appropriate; supported wire-types: String /// public System.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 (dataRemaining != 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"); } } /// /// 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. /// 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 (MemoryStream 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) { ProtoReader reader = GetRecycled(); if (reader == null) { return new ProtoReader(source, model, context, len); } Init(reader, source, model, context, len); 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 } }