From f34d37a3d4d64621bc87aa0a65a05cab64062399 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Tue, 30 Jun 2015 13:16:20 +0100 Subject: Tidying up and extra tests. This is mostly just making things internal instead of public, removing and reordering a bunch of code in CodedInputStream/CodedOutputStream, and generally tidying up. --- csharp/src/ProtocolBuffers/CodedInputStream.cs | 302 ++++++++++++------------- 1 file changed, 145 insertions(+), 157 deletions(-) (limited to 'csharp/src/ProtocolBuffers/CodedInputStream.cs') diff --git a/csharp/src/ProtocolBuffers/CodedInputStream.cs b/csharp/src/ProtocolBuffers/CodedInputStream.cs index 75178d14..5c64fd97 100644 --- a/csharp/src/ProtocolBuffers/CodedInputStream.cs +++ b/csharp/src/ProtocolBuffers/CodedInputStream.cs @@ -37,7 +37,6 @@ using System; using System.Collections.Generic; using System.IO; -using Google.Protobuf.Collections; namespace Google.Protobuf { @@ -178,8 +177,61 @@ namespace Google.Protobuf /// internal uint LastTag { get { return lastTag; } } - #region Validation + #region Limits for recursion and length + /// + /// Set the maximum message recursion depth. + /// + /// + /// In order to prevent malicious + /// messages from causing stack overflows, CodedInputStream limits + /// how deeply messages may be nested. The default limit is 64. + /// + public int SetRecursionLimit(int limit) + { + if (limit < 0) + { + throw new ArgumentOutOfRangeException("Recursion limit cannot be negative: " + limit); + } + int oldLimit = recursionLimit; + recursionLimit = limit; + return oldLimit; + } + + /// + /// Set the maximum message size. + /// + /// + /// In order to prevent malicious messages from exhausting memory or + /// causing integer overflows, CodedInputStream limits how large a message may be. + /// The default limit is 64MB. You should set this limit as small + /// as you can without harming your app's functionality. Note that + /// size limits only apply when reading from an InputStream, not + /// when constructed around a raw byte array (nor with ByteString.NewCodedInput). + /// If you want to read several messages from a single CodedInputStream, you + /// can call ResetSizeCounter() after each message to avoid hitting the + /// size limit. + /// + public int SetSizeLimit(int limit) + { + if (limit < 0) + { + throw new ArgumentOutOfRangeException("Size limit cannot be negative: " + limit); + } + int oldLimit = sizeLimit; + sizeLimit = limit; + return oldLimit; + } + + /// + /// Resets the current size counter to zero (see ). + /// + public void ResetSizeCounter() + { + totalBytesRetired = 0; + } + #endregion + #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 @@ -194,13 +246,12 @@ namespace Google.Protobuf throw InvalidProtocolBufferException.InvalidEndTag(); } } - #endregion #region Reading of tags etc /// - /// Attempt to peek at the next field tag. + /// Attempts to peek at the next field tag. /// public bool PeekNextTag(out uint fieldTag) { @@ -218,7 +269,7 @@ namespace Google.Protobuf } /// - /// Attempt to read a field tag, returning false if we have reached the end + /// Attempts 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) @@ -233,14 +284,42 @@ namespace Google.Protobuf return true; } - if (IsAtEnd) + // Optimize for the incredibly common case of having at least two bytes left in the buffer, + // and those two bytes being enough to get the tag. This will be true for fields up to 4095. + if (bufferPos + 2 <= bufferSize) { - fieldTag = 0; - lastTag = fieldTag; - return false; + int tmp = buffer[bufferPos++]; + if (tmp < 128) + { + fieldTag = (uint)tmp; + } + else + { + int result = tmp & 0x7f; + if ((tmp = buffer[bufferPos++]) < 128) + { + result |= tmp << 7; + fieldTag = (uint) result; + } + else + { + // Nope, rewind and go the potentially slow route. + bufferPos -= 2; + fieldTag = ReadRawVarint32(); + } + } } + else + { + if (IsAtEnd) + { + fieldTag = 0; + lastTag = fieldTag; + return false; + } - fieldTag = ReadRawVarint32(); + fieldTag = ReadRawVarint32(); + } lastTag = fieldTag; if (lastTag == 0) { @@ -251,7 +330,7 @@ namespace Google.Protobuf } /// - /// Read a double field from the stream. + /// Reads a double field from the stream. /// public double ReadDouble() { @@ -259,7 +338,7 @@ namespace Google.Protobuf } /// - /// Read a float field from the stream. + /// Reads a float field from the stream. /// public float ReadFloat() { @@ -281,7 +360,7 @@ namespace Google.Protobuf } /// - /// Read a uint64 field from the stream. + /// Reads a uint64 field from the stream. /// public ulong ReadUInt64() { @@ -289,7 +368,7 @@ namespace Google.Protobuf } /// - /// Read an int64 field from the stream. + /// Reads an int64 field from the stream. /// public long ReadInt64() { @@ -297,7 +376,7 @@ namespace Google.Protobuf } /// - /// Read an int32 field from the stream. + /// Reads an int32 field from the stream. /// public int ReadInt32() { @@ -305,7 +384,7 @@ namespace Google.Protobuf } /// - /// Read a fixed64 field from the stream. + /// Reads a fixed64 field from the stream. /// public ulong ReadFixed64() { @@ -313,7 +392,7 @@ namespace Google.Protobuf } /// - /// Read a fixed32 field from the stream. + /// Reads a fixed32 field from the stream. /// public uint ReadFixed32() { @@ -321,7 +400,7 @@ namespace Google.Protobuf } /// - /// Read a bool field from the stream. + /// Reads a bool field from the stream. /// public bool ReadBool() { @@ -333,22 +412,22 @@ namespace Google.Protobuf /// public string ReadString() { - int size = (int) ReadRawVarint32(); + int length = ReadLength(); // No need to read any data for an empty string. - if (size == 0) + if (length == 0) { return ""; } - if (size <= bufferSize - bufferPos) + if (length <= bufferSize - bufferPos) { // Fast path: We already have the bytes in a contiguous buffer, so // just copy directly from it. - String result = CodedOutputStream.Utf8Encoding.GetString(buffer, bufferPos, size); - bufferPos += size; + String result = CodedOutputStream.Utf8Encoding.GetString(buffer, bufferPos, length); + bufferPos += length; return result; } // Slow path: Build a byte array first then copy it. - return CodedOutputStream.Utf8Encoding.GetString(ReadRawBytes(size), 0, size); + return CodedOutputStream.Utf8Encoding.GetString(ReadRawBytes(length), 0, length); } /// @@ -356,7 +435,7 @@ namespace Google.Protobuf /// public void ReadMessage(IMessage builder) { - int length = (int) ReadRawVarint32(); + int length = ReadLength(); if (recursionDepth >= recursionLimit) { throw InvalidProtocolBufferException.RecursionLimitExceeded(); @@ -374,19 +453,19 @@ namespace Google.Protobuf /// public ByteString ReadBytes() { - int size = (int) ReadRawVarint32(); - if (size <= bufferSize - bufferPos && size > 0) + int length = ReadLength(); + if (length <= bufferSize - bufferPos && length > 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; + ByteString result = ByteString.CopyFrom(buffer, bufferPos, length); + bufferPos += length; return result; } else { // Slow path: Build a byte array and attach it to a new ByteString. - return ByteString.AttachBytes(ReadRawBytes(size)); + return ByteString.AttachBytes(ReadRawBytes(length)); } } @@ -441,6 +520,18 @@ namespace Google.Protobuf return DecodeZigZag64(ReadRawVarint64()); } + /// + /// Reads a length for length-delimited data. + /// + /// + /// This is internally just reading a varint, but this method exists + /// to make the calling code clearer. + /// + public int ReadLength() + { + return (int) ReadRawVarint32(); + } + /// /// Peeks at the next tag in the stream. If it matches , /// the tag is consumed and the method returns true; otherwise, the @@ -517,12 +608,12 @@ namespace Google.Protobuf } /// - /// Read a raw Varint from the stream. If larger than 32 bits, discard the upper bits. + /// Reads a raw Varint from the stream. If larger than 32 bits, discard the upper bits. /// This method is optimised for the case where we've got lots of data in the buffer. /// That means we can check the size just once, then just read directly from the buffer /// without constant rechecking of the buffer length. /// - public uint ReadRawVarint32() + internal uint ReadRawVarint32() { if (bufferPos + 5 > bufferSize) { @@ -581,13 +672,13 @@ namespace Google.Protobuf /// /// Reads a varint from the input one byte at a time, so that it does not /// read any bytes after the end of the varint. If you simply wrapped the - /// stream in a CodedInputStream and used ReadRawVarint32(Stream)} + /// stream in a CodedInputStream and used ReadRawVarint32(Stream) /// then you would probably end up reading past the end of the varint since /// CodedInputStream buffers its input. /// /// /// - public static uint ReadRawVarint32(Stream input) + internal static uint ReadRawVarint32(Stream input) { int result = 0; int offset = 0; @@ -621,9 +712,9 @@ namespace Google.Protobuf } /// - /// Read a raw varint from the stream. + /// Reads a raw varint from the stream. /// - public ulong ReadRawVarint64() + internal ulong ReadRawVarint64() { int shift = 0; ulong result = 0; @@ -641,9 +732,9 @@ namespace Google.Protobuf } /// - /// Read a 32-bit little-endian integer from the stream. + /// Reads a 32-bit little-endian integer from the stream. /// - public uint ReadRawLittleEndian32() + internal uint ReadRawLittleEndian32() { uint b1 = ReadRawByte(); uint b2 = ReadRawByte(); @@ -653,9 +744,9 @@ namespace Google.Protobuf } /// - /// Read a 64-bit little-endian integer from the stream. + /// Reads a 64-bit little-endian integer from the stream. /// - public ulong ReadRawLittleEndian64() + internal ulong ReadRawLittleEndian64() { ulong b1 = ReadRawByte(); ulong b2 = ReadRawByte(); @@ -669,8 +760,6 @@ namespace Google.Protobuf | (b5 << 32) | (b6 << 40) | (b7 << 48) | (b8 << 56); } - #endregion - /// /// Decode a 32-bit value with ZigZag encoding. /// @@ -680,9 +769,9 @@ namespace Google.Protobuf /// sign-extended to 64 bits to be varint encoded, thus always taking /// 10 bytes on the wire.) /// - public static int DecodeZigZag32(uint n) + internal static int DecodeZigZag32(uint n) { - return (int) (n >> 1) ^ -(int) (n & 1); + return (int)(n >> 1) ^ -(int)(n & 1); } /// @@ -694,72 +783,21 @@ namespace Google.Protobuf /// sign-extended to 64 bits to be varint encoded, thus always taking /// 10 bytes on the wire.) /// - public static long DecodeZigZag64(ulong n) - { - return (long) (n >> 1) ^ -(long) (n & 1); - } - - /// - /// Set the maximum message recursion depth. - /// - /// - /// In order to prevent malicious - /// messages from causing stack overflows, CodedInputStream limits - /// how deeply messages may be nested. The default limit is 64. - /// - public int SetRecursionLimit(int limit) - { - if (limit < 0) - { - throw new ArgumentOutOfRangeException("Recursion limit cannot be negative: " + limit); - } - int oldLimit = recursionLimit; - recursionLimit = limit; - return oldLimit; - } - - /// - /// Set the maximum message size. - /// - /// - /// In order to prevent malicious messages from exhausting memory or - /// causing integer overflows, CodedInputStream limits how large a message may be. - /// The default limit is 64MB. You should set this limit as small - /// as you can without harming your app's functionality. Note that - /// size limits only apply when reading from an InputStream, not - /// when constructed around a raw byte array (nor with ByteString.NewCodedInput). - /// If you want to read several messages from a single CodedInputStream, you - /// can call ResetSizeCounter() after each message to avoid hitting the - /// size limit. - /// - public int SetSizeLimit(int limit) + internal static long DecodeZigZag64(ulong n) { - if (limit < 0) - { - throw new ArgumentOutOfRangeException("Size limit cannot be negative: " + limit); - } - int oldLimit = sizeLimit; - sizeLimit = limit; - return oldLimit; + return (long)(n >> 1) ^ -(long)(n & 1); } + #endregion #region Internal reading and buffer management - /// - /// Resets the current size counter to zero (see SetSizeLimit). - /// - public void ResetSizeCounter() - { - totalBytesRetired = 0; - } - /// /// Sets currentLimit to (current position) + byteLimit. This is called /// when descending into a length-delimited embedded message. The previous /// limit is returned. /// /// The old limit. - public int PushLimit(int byteLimit) + internal int PushLimit(int byteLimit) { if (byteLimit < 0) { @@ -797,7 +835,7 @@ namespace Google.Protobuf /// /// Discards the current limit, returning the previous limit. /// - public void PopLimit(int oldLimit) + internal void PopLimit(int oldLimit) { currentLimit = oldLimit; RecomputeBufferSizeAfterLimit(); @@ -807,7 +845,7 @@ namespace Google.Protobuf /// Returns whether or not all the data before the limit has been read. /// /// - public bool ReachedLimit + internal bool ReachedLimit { get { @@ -897,7 +935,7 @@ namespace Google.Protobuf /// /// the end of the stream or the current limit was reached /// - public byte ReadRawByte() + internal byte ReadRawByte() { if (bufferPos == bufferSize) { @@ -907,12 +945,12 @@ namespace Google.Protobuf } /// - /// Read a fixed size of bytes from the input. + /// Reads a fixed size of bytes from the input. /// /// /// the end of the stream or the current limit was reached /// - public byte[] ReadRawBytes(int size) + internal byte[] ReadRawBytes(int size) { if (size < 0) { @@ -921,7 +959,8 @@ namespace Google.Protobuf if (totalBytesRetired + bufferPos + size > currentLimit) { - // Read to the end of the stream anyway. + // Read to the end of the stream (up to the current limit) anyway. + // TODO(jonskeet): This is the only usage of SkipRawBytes. Do we really need to do it? SkipRawBytes(currentLimit - totalBytesRetired - bufferPos); // Then fail. throw InvalidProtocolBufferException.TruncatedMessage(); @@ -1025,63 +1064,12 @@ namespace Google.Protobuf } } - /// - /// Reads and discards a single field, given its tag value. - /// - /// false if the tag is an end-group tag, in which case - /// nothing is skipped. Otherwise, returns true. - public bool SkipField() - { - uint tag = lastTag; - switch (WireFormat.GetTagWireType(tag)) - { - case WireFormat.WireType.Varint: - ReadRawVarint64(); - return true; - case WireFormat.WireType.Fixed64: - ReadRawLittleEndian64(); - return true; - case WireFormat.WireType.LengthDelimited: - SkipRawBytes((int) ReadRawVarint32()); - return true; - case WireFormat.WireType.StartGroup: - SkipMessage(); - CheckLastTagWas( - WireFormat.MakeTag(WireFormat.GetTagFieldNumber(tag), - WireFormat.WireType.EndGroup)); - return true; - case WireFormat.WireType.EndGroup: - return false; - case WireFormat.WireType.Fixed32: - ReadRawLittleEndian32(); - return true; - default: - throw InvalidProtocolBufferException.InvalidWireType(); - } - } - - /// - /// Reads and discards an entire message. This will read either until EOF - /// or until an endgroup tag, whichever comes first. - /// - public void SkipMessage() - { - uint tag; - while (ReadTag(out tag)) - { - if (!SkipField()) - { - return; - } - } - } - /// /// Reads and discards bytes. /// /// the end of the stream /// or the current limit was reached - public void SkipRawBytes(int size) + private void SkipRawBytes(int size) { if (size < 0) { -- cgit v1.2.3