#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// http://github.com/jskeet/dotnet-protobufs/
// Original C++/Java/Python code:
// http://code.google.com/p/protobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Google.ProtocolBuffers.Descriptors;
namespace Google.ProtocolBuffers
{
///
/// Readings and decodes protocol message fields.
///
///
/// This class contains two kinds of methods: methods that read specific
/// protocol message constructs and field types (e.g. ReadTag and
/// ReadInt32) and methods that read low-level values (e.g.
/// ReadRawVarint32 and ReadRawBytes). If you are reading encoded protocol
/// messages, you should use the former methods, but if you are reading some
/// other format of your own design, use the latter. The names of the former
/// methods are taken from the protocol buffer type names, not .NET types.
/// (Hence ReadFloat instead of ReadSingle, and ReadBool instead of ReadBoolean.)
///
/// TODO(jonskeet): Consider whether recursion and size limits shouldn't be readonly,
/// set at construction time.
///
public sealed class CodedInputStream : ICodedInputStream
{
private readonly byte[] buffer;
private int bufferSize;
private int bufferSizeAfterLimit = 0;
private int bufferPos = 0;
private readonly Stream input;
private uint lastTag = 0;
private uint nextTag = 0;
private bool hasNextTag = false;
internal const int DefaultRecursionLimit = 64;
internal const int DefaultSizeLimit = 64 << 20; // 64MB
public const int BufferSize = 4096;
///
/// The total number of bytes read before the current buffer. The
/// total bytes read up to the current position can be computed as
/// totalBytesRetired + bufferPos.
///
private int totalBytesRetired = 0;
///
/// The absolute position of the end of the current message.
///
private int currentLimit = int.MaxValue;
///
///
///
private int recursionDepth = 0;
private int recursionLimit = DefaultRecursionLimit;
///
///
///
private int sizeLimit = DefaultSizeLimit;
#region Construction
///
/// Creates a new CodedInputStream reading data from the given
/// stream.
///
public static CodedInputStream CreateInstance(Stream input)
{
return new CodedInputStream(input);
}
///
/// Creates a new CodedInputStream reading data from the given
/// byte array.
///
public static CodedInputStream CreateInstance(byte[] buf)
{
return new CodedInputStream(buf, 0, buf.Length);
}
///
/// Creates a new CodedInputStream that reads from the given
/// byte array slice.
///
public static CodedInputStream CreateInstance(byte[] buf, int offset, int length)
{
return new CodedInputStream(buf, offset, length);
}
private CodedInputStream(byte[] buffer, int offset, int length)
{
this.buffer = buffer;
this.bufferPos = offset;
this.bufferSize = offset + length;
this.input = null;
}
private CodedInputStream(Stream input)
{
this.buffer = new byte[BufferSize];
this.bufferSize = 0;
this.input = input;
}
#endregion
void ICodedInputStream.ReadMessageStart() { }
void ICodedInputStream.ReadMessageEnd() { }
#region Validation
///
/// Verifies that the last call to ReadTag() returned the given tag value.
/// This is used to verify that a nested group ended with the correct
/// end tag.
///
/// The last
/// tag read was not the one specified
[CLSCompliant(false)]
public void CheckLastTagWas(uint value)
{
if (lastTag != value)
{
throw InvalidProtocolBufferException.InvalidEndTag();
}
}
#endregion
#region Reading of tags etc
///
/// Attempt to peek at the next field tag.
///
[CLSCompliant(false)]
public bool PeekNextTag(out uint fieldTag, out string fieldName)
{
if (hasNextTag)
{
fieldName = null;
fieldTag = nextTag;
return true;
}
uint savedLast = lastTag;
hasNextTag = ReadTag(out nextTag, out fieldName);
lastTag = savedLast;
fieldTag = nextTag;
return hasNextTag;
}
///
/// Attempt to read a field tag, returning false if we have reached the end
/// of the input data.
///
/// The 'tag' of the field (id * 8 + wire-format)
/// Not Supported - For protobuffer streams, this parameter is always null
/// true if the next fieldTag was read
[CLSCompliant(false)]
public bool ReadTag(out uint fieldTag, out string fieldName)
{
fieldName = null;
if (hasNextTag)
{
fieldTag = nextTag;
lastTag = fieldTag;
hasNextTag = false;
return true;
}
if (IsAtEnd)
{
fieldTag = 0;
lastTag = fieldTag;
return false;
}
fieldTag = ReadRawVarint32();
lastTag = fieldTag;
if (lastTag == 0)
{
// If we actually read zero, that's not a valid tag.
throw InvalidProtocolBufferException.InvalidTag();
}
return true;
}
///
/// Read a double field from the stream.
///
public bool ReadDouble(ref double value)
{
#if SILVERLIGHT || COMPACT_FRAMEWORK_35
if (BitConverter.IsLittleEndian && 8 <= bufferSize - bufferPos)
{
value = BitConverter.ToDouble(buffer, bufferPos);
bufferPos += 8;
}
else
{
byte[] rawBytes = ReadRawBytes(8);
if (!BitConverter.IsLittleEndian)
ByteArray.Reverse(rawBytes);
value = BitConverter.ToDouble(rawBytes, 0);
}
#else
value = BitConverter.Int64BitsToDouble((long) ReadRawLittleEndian64());
#endif
return true;
}
///
/// Read a float field from the stream.
///
public bool ReadFloat(ref float value)
{
if (BitConverter.IsLittleEndian && 4 <= bufferSize - bufferPos)
{
value = BitConverter.ToSingle(buffer, bufferPos);
bufferPos += 4;
}
else
{
byte[] rawBytes = ReadRawBytes(4);
if (!BitConverter.IsLittleEndian)
{
ByteArray.Reverse(rawBytes);
}
value = BitConverter.ToSingle(rawBytes, 0);
}
return true;
}
///
/// Read a uint64 field from the stream.
///
[CLSCompliant(false)]
public bool ReadUInt64(ref ulong value)
{
value = ReadRawVarint64();
return true;
}
///
/// Read an int64 field from the stream.
///
public bool ReadInt64(ref long value)
{
value = (long) ReadRawVarint64();
return true;
}
///
/// Read an int32 field from the stream.
///
public bool ReadInt32(ref int value)
{
value = (int) ReadRawVarint32();
return true;
}
///
/// Read a fixed64 field from the stream.
///
[CLSCompliant(false)]
public bool ReadFixed64(ref ulong value)
{
value = ReadRawLittleEndian64();
return true;
}
///
/// Read a fixed32 field from the stream.
///
[CLSCompliant(false)]
public bool ReadFixed32(ref uint value)
{
value = ReadRawLittleEndian32();
return true;
}
///
/// Read a bool field from the stream.
///
public bool ReadBool(ref bool value)
{
value = ReadRawVarint32() != 0;
return true;
}
///
/// Reads a string field from the stream.
///
public bool ReadString(ref string value)
{
int size = (int) ReadRawVarint32();
// No need to read any data for an empty string.
if (size == 0)
{
value = "";
return true;
}
if (size <= bufferSize - bufferPos)
{
// Fast path: We already have the bytes in a contiguous buffer, so
// just copy directly from it.
String result = Encoding.UTF8.GetString(buffer, bufferPos, size);
bufferPos += size;
value = result;
return true;
}
// Slow path: Build a byte array first then copy it.
value = Encoding.UTF8.GetString(ReadRawBytes(size), 0, size);
return true;
}
///
/// Reads a group field value from the stream.
///
public void ReadGroup(int fieldNumber, IBuilderLite builder,
ExtensionRegistry extensionRegistry)
{
if (recursionDepth >= recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
++recursionDepth;
builder.WeakMergeFrom(this, extensionRegistry);
CheckLastTagWas(WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
--recursionDepth;
}
///
/// Reads a group field value from the stream and merges it into the given
/// UnknownFieldSet.
///
[Obsolete]
public void ReadUnknownGroup(int fieldNumber, IBuilderLite builder)
{
if (recursionDepth >= recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
++recursionDepth;
builder.WeakMergeFrom(this);
CheckLastTagWas(WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
--recursionDepth;
}
///
/// Reads an embedded message field value from the stream.
///
public void ReadMessage(IBuilderLite builder, ExtensionRegistry extensionRegistry)
{
int length = (int) ReadRawVarint32();
if (recursionDepth >= recursionLimit)
{
throw InvalidProtocolBufferException.RecursionLimitExceeded();
}
int oldLimit = PushLimit(length);
++recursionDepth;
builder.WeakMergeFrom(this, extensionRegistry);
CheckLastTagWas(0);
--recursionDepth;
PopLimit(oldLimit);
}
///
/// Reads a bytes field value from the stream.
///
public bool ReadBytes(ref ByteString value)
{
int size = (int) ReadRawVarint32();
if (size < bufferSize - bufferPos && size > 0)
{
// Fast path: We already have the bytes in a contiguous buffer, so
// just copy directly from it.
ByteString result = ByteString.CopyFrom(buffer, bufferPos, size);
bufferPos += size;
value = result;
return true;
}
else
{
// Slow path: Build a byte array and attach it to a new ByteString.
value = ByteString.AttachBytes(ReadRawBytes(size));
return true;
}
}
///
/// Reads a uint32 field value from the stream.
///
[CLSCompliant(false)]
public bool ReadUInt32(ref uint value)
{
value = ReadRawVarint32();
return true;
}
///
/// Reads an enum field value from the stream. The caller is responsible
/// for converting the numeric value to an actual enum.
///
public bool ReadEnum(ref IEnumLite value, out object unknown, IEnumLiteMap mapping)
{
int rawValue = (int) ReadRawVarint32();
value = mapping.FindValueByNumber(rawValue);
if (value != null)
{
unknown = null;
return true;
}
unknown = rawValue;
return false;
}
///
/// Reads an enum field value from the stream. If the enum is valid for type T,
/// then the ref value is set and it returns true. Otherwise the unkown output
/// value is set and this method returns false.
///
[CLSCompliant(false)]
public bool ReadEnum(ref T value, out object unknown)
where T : struct, IComparable, IFormattable, IConvertible
{
int number = (int) ReadRawVarint32();
if (Enum.IsDefined(typeof (T), number))
{
unknown = null;
value = (T) (object) number;
return true;
}
unknown = number;
return false;
}
///
/// Reads an sfixed32 field value from the stream.
///
public bool ReadSFixed32(ref int value)
{
value = (int) ReadRawLittleEndian32();
return true;
}
///
/// Reads an sfixed64 field value from the stream.
///
public bool ReadSFixed64(ref long value)
{
value = (long) ReadRawLittleEndian64();
return true;
}
///
/// Reads an sint32 field value from the stream.
///
public bool ReadSInt32(ref int value)
{
value = DecodeZigZag32(ReadRawVarint32());
return true;
}
///
/// Reads an sint64 field value from the stream.
///
public bool ReadSInt64(ref long value)
{
value = DecodeZigZag64(ReadRawVarint64());
return true;
}
private bool BeginArray(uint fieldTag, out bool isPacked, out int oldLimit)
{
isPacked = WireFormat.GetTagWireType(fieldTag) == WireFormat.WireType.LengthDelimited;
if (isPacked)
{
int length = (int) (ReadRawVarint32() & int.MaxValue);
if (length > 0)
{
oldLimit = PushLimit(length);
return true;
}
oldLimit = -1;
return false; //packed but empty
}
oldLimit = -1;
return true;
}
///
/// Returns true if the next tag is also part of the same unpacked array.
///
private bool ContinueArray(uint currentTag)
{
string ignore;
uint next;
if (PeekNextTag(out next, out ignore))
{
if (next == currentTag)
{
hasNextTag = false;
return true;
}
}
return false;
}
///
/// Returns true if the next tag is also part of the same array, which may or may not be packed.
///
private bool ContinueArray(uint currentTag, bool packed, int oldLimit)
{
if (packed)
{
if (ReachedLimit)
{
PopLimit(oldLimit);
return false;
}
return true;
}
string ignore;
uint next;
if (PeekNextTag(out next, out ignore))
{
if (next == currentTag)
{
hasNextTag = false;
return true;
}
}
return false;
}
[CLSCompliant(false)]
public void ReadPrimitiveArray(FieldType fieldType, uint fieldTag, string fieldName, ICollection