Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.ProtobufCS/0.9.1/ProtobufCS/src/ProtocolBuffers/CodedInputStream.cs @ 5728

Last change on this file since 5728 was 3857, checked in by abeham, 15 years ago

#866

  • Added protobuf-csharp-port project source to ExtLibs
File size: 33.4 KB
Line 
1#region Copyright notice and license
2// Protocol Buffers - Google's data interchange format
3// Copyright 2008 Google Inc.  All rights reserved.
4// http://github.com/jskeet/dotnet-protobufs/
5// Original C++/Java/Python code:
6// http://code.google.com/p/protobuf/
7//
8// Redistribution and use in source and binary forms, with or without
9// modification, are permitted provided that the following conditions are
10// met:
11//
12//     * Redistributions of source code must retain the above copyright
13// notice, this list of conditions and the following disclaimer.
14//     * Redistributions in binary form must reproduce the above
15// copyright notice, this list of conditions and the following disclaimer
16// in the documentation and/or other materials provided with the
17// distribution.
18//     * Neither the name of Google Inc. nor the names of its
19// contributors may be used to endorse or promote products derived from
20// this software without specific prior written permission.
21//
22// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33#endregion
34
35using System;
36using System.Collections.Generic;
37using System.IO;
38using System.Text;
39using Google.ProtocolBuffers.Descriptors;
40
41namespace Google.ProtocolBuffers {
42
43  /// <summary>
44  /// Readings and decodes protocol message fields.
45  /// </summary>
46  /// <remarks>
47  /// This class contains two kinds of methods:  methods that read specific
48  /// protocol message constructs and field types (e.g. ReadTag and
49  /// ReadInt32) and methods that read low-level values (e.g.
50  /// ReadRawVarint32 and ReadRawBytes).  If you are reading encoded protocol
51  /// messages, you should use the former methods, but if you are reading some
52  /// other format of your own design, use the latter. The names of the former
53  /// methods are taken from the protocol buffer type names, not .NET types.
54  /// (Hence ReadFloat instead of ReadSingle, and ReadBool instead of ReadBoolean.)
55  ///
56  /// TODO(jonskeet): Consider whether recursion and size limits shouldn't be readonly,
57  /// set at construction time.
58  /// </remarks>
59  public sealed class CodedInputStream {
60    private readonly byte[] buffer;
61    private int bufferSize;
62    private int bufferSizeAfterLimit = 0;
63    private int bufferPos = 0;
64    private readonly Stream input;
65    private uint lastTag = 0;
66
67    internal const int DefaultRecursionLimit = 64;
68    internal const int DefaultSizeLimit = 64 << 20; // 64MB
69    internal const int BufferSize = 4096;
70   
71    /// <summary>
72    /// The total number of bytes read before the current buffer. The
73    /// total bytes read up to the current position can be computed as
74    /// totalBytesRetired + bufferPos.
75    /// </summary>
76    private int totalBytesRetired = 0;
77
78    /// <summary>
79    /// The absolute position of the end of the current message.
80    /// </summary>
81    private int currentLimit = int.MaxValue;
82
83    /// <summary>
84    /// <see cref="SetRecursionLimit"/>
85    /// </summary>
86    private int recursionDepth = 0;
87    private int recursionLimit = DefaultRecursionLimit;
88
89    /// <summary>
90    /// <see cref="SetSizeLimit"/>
91    /// </summary>
92    private int sizeLimit = DefaultSizeLimit;
93
94    #region Construction
95    /// <summary>
96    /// Creates a new CodedInputStream reading data from the given
97    /// stream.
98    /// </summary>
99    public static CodedInputStream CreateInstance(Stream input) {
100      return new CodedInputStream(input);
101    }
102
103    /// <summary>
104    /// Creates a new CodedInputStream reading data from the given
105    /// byte array.
106    /// </summary>
107    public static CodedInputStream CreateInstance(byte[] buf) {
108      return new CodedInputStream(buf);
109    }
110
111    private CodedInputStream(byte[] buffer) {
112      this.buffer = buffer;
113      this.bufferSize = buffer.Length;
114      this.input = null;
115    }
116
117    private CodedInputStream(Stream input) {
118      this.buffer = new byte[BufferSize];
119      this.bufferSize = 0;
120      this.input = input;
121    }
122    #endregion
123
124    #region Validation
125    /// <summary>
126    /// Verifies that the last call to ReadTag() returned the given tag value.
127    /// This is used to verify that a nested group ended with the correct
128    /// end tag.
129    /// </summary>
130    /// <exception cref="InvalidProtocolBufferException">The last
131    /// tag read was not the one specified</exception>
132    [CLSCompliant(false)]
133    public void CheckLastTagWas(uint value) {
134      if (lastTag != value) {
135        throw InvalidProtocolBufferException.InvalidEndTag();
136      }
137    }
138    #endregion
139
140    #region Reading of tags etc
141    /// <summary>
142    /// Attempt to read a field tag, returning 0 if we have reached the end
143    /// of the input data. Protocol message parsers use this to read tags,
144    /// since a protocol message may legally end wherever a tag occurs, and
145    /// zero is not a valid tag number.
146    /// </summary>
147    [CLSCompliant(false)]
148    public uint ReadTag() {
149      if (IsAtEnd) {
150        lastTag = 0;
151        return 0;
152      }
153
154      lastTag = ReadRawVarint32();
155      if (lastTag == 0) {
156        // If we actually read zero, that's not a valid tag.
157        throw InvalidProtocolBufferException.InvalidTag();
158      }
159      return lastTag;
160    }
161
162    /// <summary>
163    /// Read a double field from the stream.
164    /// </summary>
165    public double ReadDouble() {
166#if SILVERLIGHT2 || COMPACT_FRAMEWORK_35
167      byte[] bytes = ReadRawBytes(8);
168      return BitConverter.ToDouble(bytes, 0);
169#else
170      return BitConverter.Int64BitsToDouble((long) ReadRawLittleEndian64());
171#endif
172    }
173
174    /// <summary>
175    /// Read a float field from the stream.
176    /// </summary>
177    public float ReadFloat() {
178      // TODO(jonskeet): Test this on different endiannesses
179      uint raw = ReadRawLittleEndian32();
180      byte[] rawBytes = BitConverter.GetBytes(raw);
181      return BitConverter.ToSingle(rawBytes, 0);
182    }
183
184    /// <summary>
185    /// Read a uint64 field from the stream.
186    /// </summary>
187    [CLSCompliant(false)]
188    public ulong ReadUInt64() {
189      return ReadRawVarint64();
190    }
191
192    /// <summary>
193    /// Read an int64 field from the stream.
194    /// </summary>
195    public long ReadInt64() {
196      return (long) ReadRawVarint64();
197    }
198
199    /// <summary>
200    /// Read an int32 field from the stream.
201    /// </summary>
202    public int ReadInt32() {
203      return (int) ReadRawVarint32();
204    }
205
206    /// <summary>
207    /// Read a fixed64 field from the stream.
208    /// </summary>
209    [CLSCompliant(false)]
210    public ulong ReadFixed64() {
211      return ReadRawLittleEndian64();
212    }
213
214    /// <summary>
215    /// Read a fixed32 field from the stream.
216    /// </summary>
217    [CLSCompliant(false)]
218    public uint ReadFixed32() {
219      return ReadRawLittleEndian32();
220    }
221
222    /// <summary>
223    /// Read a bool field from the stream.
224    /// </summary>
225    public bool ReadBool() {
226      return ReadRawVarint32() != 0;
227    }
228
229    /// <summary>
230    /// Reads a string field from the stream.
231    /// </summary>
232    public String ReadString() {
233      int size = (int) ReadRawVarint32();
234      // No need to read any data for an empty string.
235      if (size == 0) {
236        return "";
237      }
238      if (size <= bufferSize - bufferPos) {
239        // Fast path:  We already have the bytes in a contiguous buffer, so
240        //   just copy directly from it.
241        String result = Encoding.UTF8.GetString(buffer, bufferPos, size);
242        bufferPos += size;
243        return result;
244      }
245      // Slow path: Build a byte array first then copy it.
246      return Encoding.UTF8.GetString(ReadRawBytes(size), 0, size);
247    }
248
249    /// <summary>
250    /// Reads a group field value from the stream.
251    /// </summary>   
252    public void ReadGroup(int fieldNumber, IBuilder builder,
253                          ExtensionRegistry extensionRegistry) {
254      if (recursionDepth >= recursionLimit) {
255        throw InvalidProtocolBufferException.RecursionLimitExceeded();
256      }
257      ++recursionDepth;
258      builder.WeakMergeFrom(this, extensionRegistry);
259      CheckLastTagWas(WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
260      --recursionDepth;
261    }
262
263    /// <summary>
264    /// Reads a group field value from the stream and merges it into the given
265    /// UnknownFieldSet.
266    /// </summary>   
267    public void ReadUnknownGroup(int fieldNumber, UnknownFieldSet.Builder builder) {
268      if (recursionDepth >= recursionLimit) {
269        throw InvalidProtocolBufferException.RecursionLimitExceeded();
270      }
271      ++recursionDepth;
272      builder.MergeFrom(this);
273      CheckLastTagWas(WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
274      --recursionDepth;
275    }
276
277    /// <summary>
278    /// Reads an embedded message field value from the stream.
279    /// </summary>   
280    public void ReadMessage(IBuilder builder, ExtensionRegistry extensionRegistry) {
281      int length = (int) ReadRawVarint32();
282      if (recursionDepth >= recursionLimit) {
283        throw InvalidProtocolBufferException.RecursionLimitExceeded();
284      }
285      int oldLimit = PushLimit(length);
286      ++recursionDepth;
287      builder.WeakMergeFrom(this, extensionRegistry);
288      CheckLastTagWas(0);
289      --recursionDepth;
290      PopLimit(oldLimit);
291    }
292
293    /// <summary>
294    /// Reads a bytes field value from the stream.
295    /// </summary>   
296    public ByteString ReadBytes() {
297      int size = (int) ReadRawVarint32();
298      if (size < bufferSize - bufferPos && size > 0) {
299        // Fast path:  We already have the bytes in a contiguous buffer, so
300        //   just copy directly from it.
301        ByteString result = ByteString.CopyFrom(buffer, bufferPos, size);
302        bufferPos += size;
303        return result;
304      } else {
305        // Slow path:  Build a byte array first then copy it.
306        return ByteString.CopyFrom(ReadRawBytes(size));
307      }
308    }
309
310    /// <summary>
311    /// Reads a uint32 field value from the stream.
312    /// </summary>   
313    [CLSCompliant(false)]
314    public uint ReadUInt32() {
315      return ReadRawVarint32();
316    }
317
318    /// <summary>
319    /// Reads an enum field value from the stream. The caller is responsible
320    /// for converting the numeric value to an actual enum.
321    /// </summary>   
322    public int ReadEnum() {
323      return (int) ReadRawVarint32();
324    }
325
326    /// <summary>
327    /// Reads an sfixed32 field value from the stream.
328    /// </summary>   
329    public int ReadSFixed32() {
330      return (int) ReadRawLittleEndian32();
331    }
332
333    /// <summary>
334    /// Reads an sfixed64 field value from the stream.
335    /// </summary>   
336    public long ReadSFixed64() {
337      return (long) ReadRawLittleEndian64();
338    }
339
340    /// <summary>
341    /// Reads an sint32 field value from the stream.
342    /// </summary>   
343    public int ReadSInt32() {
344      return DecodeZigZag32(ReadRawVarint32());
345    }
346
347    /// <summary>
348    /// Reads an sint64 field value from the stream.
349    /// </summary>   
350    public long ReadSInt64() {
351      return DecodeZigZag64(ReadRawVarint64());
352    }
353
354    /// <summary>
355    /// Reads a field of any primitive type. Enums, groups and embedded
356    /// messages are not handled by this method.
357    /// </summary>
358    public object ReadPrimitiveField(FieldType fieldType) {
359      switch (fieldType) {
360        case FieldType.Double:   return ReadDouble();
361        case FieldType.Float:    return ReadFloat();
362        case FieldType.Int64:    return ReadInt64();
363        case FieldType.UInt64:   return ReadUInt64();
364        case FieldType.Int32:    return ReadInt32();
365        case FieldType.Fixed64:  return ReadFixed64();
366        case FieldType.Fixed32:  return ReadFixed32();
367        case FieldType.Bool:     return ReadBool();
368        case FieldType.String:   return ReadString();
369        case FieldType.Bytes:    return ReadBytes();
370        case FieldType.UInt32:   return ReadUInt32();
371        case FieldType.SFixed32: return ReadSFixed32();
372        case FieldType.SFixed64: return ReadSFixed64();
373        case FieldType.SInt32:   return ReadSInt32();
374        case FieldType.SInt64:   return ReadSInt64();
375        case FieldType.Group:
376            throw new ArgumentException("ReadPrimitiveField() cannot handle nested groups.");
377        case FieldType.Message:
378            throw new ArgumentException("ReadPrimitiveField() cannot handle embedded messages.");
379        // We don't handle enums because we don't know what to do if the
380        // value is not recognized.
381        case FieldType.Enum:
382            throw new ArgumentException("ReadPrimitiveField() cannot handle enums.");
383        default:
384          throw new ArgumentOutOfRangeException("Invalid field type " + fieldType);
385      }
386    }
387
388    #endregion
389
390    #region Underlying reading primitives
391
392    /// <summary>
393    /// Same code as ReadRawVarint32, but read each byte individually, checking for
394    /// buffer overflow.
395    /// </summary>
396    private uint SlowReadRawVarint32() {
397      int tmp = ReadRawByte();
398      if (tmp < 128) {
399        return (uint)tmp;
400      }
401      int result = tmp & 0x7f;
402      if ((tmp = ReadRawByte()) < 128) {
403        result |= tmp << 7;
404      } else {
405        result |= (tmp & 0x7f) << 7;
406        if ((tmp = ReadRawByte()) < 128) {
407          result |= tmp << 14;
408        } else {
409          result |= (tmp & 0x7f) << 14;
410          if ((tmp = ReadRawByte()) < 128) {
411            result |= tmp << 21;
412          } else {
413            result |= (tmp & 0x7f) << 21;
414            result |= (tmp = ReadRawByte()) << 28;
415            if (tmp >= 128) {
416              // Discard upper 32 bits.
417              for (int i = 0; i < 5; i++) {
418                if (ReadRawByte() < 128) return (uint)result;
419              }
420              throw InvalidProtocolBufferException.MalformedVarint();
421            }
422          }
423        }
424      }
425      return (uint)result;
426    }
427
428    /// <summary>
429    /// Read a raw Varint from the stream.  If larger than 32 bits, discard the upper bits.
430    /// This method is optimised for the case where we've got lots of data in the buffer.
431    /// That means we can check the size just once, then just read directly from the buffer
432    /// without constant rechecking of the buffer length.
433    /// </summary>
434    [CLSCompliant(false)]
435    public uint ReadRawVarint32() {
436      if (bufferPos + 5 > bufferSize) {
437        return SlowReadRawVarint32();
438      }
439
440      int tmp = buffer[bufferPos++];
441      if (tmp < 128) {
442        return (uint)tmp;
443      }
444      int result = tmp & 0x7f;
445      if ((tmp = buffer[bufferPos++]) < 128) {
446        result |= tmp << 7;
447      } else {
448        result |= (tmp & 0x7f) << 7;
449        if ((tmp = buffer[bufferPos++]) < 128) {
450          result |= tmp << 14;
451        } else {
452          result |= (tmp & 0x7f) << 14;
453          if ((tmp = buffer[bufferPos++]) < 128) {
454            result |= tmp << 21;
455          } else {
456            result |= (tmp & 0x7f) << 21;
457            result |= (tmp = buffer[bufferPos++]) << 28;
458            if (tmp >= 128) {
459              // Discard upper 32 bits.
460              // Note that this has to use ReadRawByte() as we only ensure we've
461              // got at least 5 bytes at the start of the method. This lets us
462              // use the fast path in more cases, and we rarely hit this section of code.
463              for (int i = 0; i < 5; i++) {
464                if (ReadRawByte() < 128) return (uint)result;
465              }
466              throw InvalidProtocolBufferException.MalformedVarint();
467            }
468          }
469        }
470      }
471      return (uint)result;
472    }
473
474    /// <summary>
475    /// Reads a varint from the input one byte at a time, so that it does not
476    /// read any bytes after the end of the varint. If you simply wrapped the
477    /// stream in a CodedInputStream and used ReadRawVarint32(Stream)}
478    /// then you would probably end up reading past the end of the varint since
479    /// CodedInputStream buffers its input.
480    /// </summary>
481    /// <param name="input"></param>
482    /// <returns></returns>
483    internal static uint ReadRawVarint32(Stream input) {
484      int result = 0;
485      int offset = 0;
486      for (; offset < 32; offset += 7) {
487        int b = input.ReadByte();
488        if (b == -1) {
489          throw InvalidProtocolBufferException.TruncatedMessage();
490        }
491        result |= (b & 0x7f) << offset;
492        if ((b & 0x80) == 0) {
493          return (uint) result;
494        }
495      }
496      // Keep reading up to 64 bits.
497      for (; offset < 64; offset += 7) {
498        int b = input.ReadByte();
499        if (b == -1) {
500          throw InvalidProtocolBufferException.TruncatedMessage();
501        }
502        if ((b & 0x80) == 0) {
503          return (uint) result;
504        }
505      }
506      throw InvalidProtocolBufferException.MalformedVarint();
507    }
508
509    /// <summary>
510    /// Read a raw varint from the stream.
511    /// </summary>
512    [CLSCompliant(false)]
513    public ulong ReadRawVarint64() {
514      int shift = 0;
515      ulong result = 0;
516      while (shift < 64) {
517        byte b = ReadRawByte();
518        result |= (ulong)(b & 0x7F) << shift;
519        if ((b & 0x80) == 0) {
520          return result;
521        }
522        shift += 7;
523      }
524      throw InvalidProtocolBufferException.MalformedVarint();
525    }
526
527    /// <summary>
528    /// Read a 32-bit little-endian integer from the stream.
529    /// </summary>
530    [CLSCompliant(false)]
531    public uint ReadRawLittleEndian32() {
532      uint b1 = ReadRawByte();
533      uint b2 = ReadRawByte();
534      uint b3 = ReadRawByte();
535      uint b4 = ReadRawByte();
536      return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
537    }
538
539    /// <summary>
540    /// Read a 64-bit little-endian integer from the stream.
541    /// </summary>
542    [CLSCompliant(false)]
543    public ulong ReadRawLittleEndian64() {
544      ulong b1 = ReadRawByte();
545      ulong b2 = ReadRawByte();
546      ulong b3 = ReadRawByte();
547      ulong b4 = ReadRawByte();
548      ulong b5 = ReadRawByte();
549      ulong b6 = ReadRawByte();
550      ulong b7 = ReadRawByte();
551      ulong b8 = ReadRawByte();
552      return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24)
553          | (b5 << 32) | (b6 << 40) | (b7 << 48) | (b8 << 56);
554    }
555    #endregion
556
557    /// <summary>
558    /// Decode a 32-bit value with ZigZag encoding.
559    /// </summary>
560    /// <remarks>
561    /// ZigZag encodes signed integers into values that can be efficiently
562    /// encoded with varint.  (Otherwise, negative values must be
563    /// sign-extended to 64 bits to be varint encoded, thus always taking
564    /// 10 bytes on the wire.)
565    /// </remarks>
566    [CLSCompliant(false)]
567    public static int DecodeZigZag32(uint n) {
568      return (int)(n >> 1) ^ -(int)(n & 1);
569    }
570
571    /// <summary>
572    /// Decode a 32-bit value with ZigZag encoding.
573    /// </summary>
574    /// <remarks>
575    /// ZigZag encodes signed integers into values that can be efficiently
576    /// encoded with varint.  (Otherwise, negative values must be
577    /// sign-extended to 64 bits to be varint encoded, thus always taking
578    /// 10 bytes on the wire.)
579    /// </remarks>
580    [CLSCompliant(false)]
581    public static long DecodeZigZag64(ulong n) {
582      return (long)(n >> 1) ^ -(long)(n & 1);
583    }
584
585    /// <summary>
586    /// Set the maximum message recursion depth.
587    /// </summary>
588    /// <remarks>
589    /// In order to prevent malicious
590    /// messages from causing stack overflows, CodedInputStream limits
591    /// how deeply messages may be nested.  The default limit is 64.
592    /// </remarks>
593    public int SetRecursionLimit(int limit) {
594      if (limit < 0) {
595        throw new ArgumentOutOfRangeException("Recursion limit cannot be negative: " + limit);
596      }
597      int oldLimit = recursionLimit;
598      recursionLimit = limit;
599      return oldLimit;
600    }
601
602    /// <summary>
603    /// Set the maximum message size.
604    /// </summary>
605    /// <remarks>
606    /// In order to prevent malicious messages from exhausting memory or
607    /// causing integer overflows, CodedInputStream limits how large a message may be.
608    /// The default limit is 64MB.  You should set this limit as small
609    /// as you can without harming your app's functionality.  Note that
610    /// size limits only apply when reading from an InputStream, not
611    /// when constructed around a raw byte array (nor with ByteString.NewCodedInput).
612    /// If you want to read several messages from a single CodedInputStream, you
613    /// can call ResetSizeCounter() after each message to avoid hitting the
614    /// size limit.
615    /// </remarks>
616    public int SetSizeLimit(int limit) {
617      if (limit < 0) {
618        throw new ArgumentOutOfRangeException("Size limit cannot be negative: " + limit);
619      }
620      int oldLimit = sizeLimit;
621      sizeLimit = limit;
622      return oldLimit;
623    }
624
625    #region Internal reading and buffer management
626    /// <summary>
627    /// Resets the current size counter to zero (see SetSizeLimit).
628    /// </summary>
629    public void ResetSizeCounter() {
630      totalBytesRetired = 0;
631    }
632
633    /// <summary>
634    /// Sets currentLimit to (current position) + byteLimit. This is called
635    /// when descending into a length-delimited embedded message. The previous
636    /// limit is returned.
637    /// </summary>
638    /// <returns>The old limit.</returns>
639    public int PushLimit(int byteLimit) {
640      if (byteLimit < 0) {
641        throw InvalidProtocolBufferException.NegativeSize();
642      }
643      byteLimit += totalBytesRetired + bufferPos;
644      int oldLimit = currentLimit;
645      if (byteLimit > oldLimit) {
646        throw InvalidProtocolBufferException.TruncatedMessage();
647      }
648      currentLimit = byteLimit;
649
650      RecomputeBufferSizeAfterLimit();
651
652      return oldLimit;
653    }
654
655    private void RecomputeBufferSizeAfterLimit() {
656      bufferSize += bufferSizeAfterLimit;
657      int bufferEnd = totalBytesRetired + bufferSize;
658      if (bufferEnd > currentLimit) {
659        // Limit is in current buffer.
660        bufferSizeAfterLimit = bufferEnd - currentLimit;
661        bufferSize -= bufferSizeAfterLimit;
662      } else {
663        bufferSizeAfterLimit = 0;
664      }
665    }
666
667    /// <summary>
668    /// Discards the current limit, returning the previous limit.
669    /// </summary>
670    public void PopLimit(int oldLimit) {
671      currentLimit = oldLimit;
672      RecomputeBufferSizeAfterLimit();
673    }
674
675    /// <summary>
676    /// Returns whether or not all the data before the limit has been read.
677    /// </summary>
678    /// <returns></returns>
679    public bool ReachedLimit {
680      get {
681        if (currentLimit == int.MaxValue) {
682          return false;
683        }
684        int currentAbsolutePosition = totalBytesRetired + bufferPos;
685        return currentAbsolutePosition >= currentLimit;
686      }
687    }
688
689    /// <summary>
690    /// Returns true if the stream has reached the end of the input. This is the
691    /// case if either the end of the underlying input source has been reached or
692    /// the stream has reached a limit created using PushLimit.
693    /// </summary>
694    public bool IsAtEnd {
695      get {
696        return bufferPos == bufferSize && !RefillBuffer(false);
697      }
698    }
699   
700    /// <summary>
701    /// Called when buffer is empty to read more bytes from the
702    /// input.  If <paramref name="mustSucceed"/> is true, RefillBuffer() gurantees that
703    /// either there will be at least one byte in the buffer when it returns
704    /// or it will throw an exception.  If <paramref name="mustSucceed"/> is false,
705    /// RefillBuffer() returns false if no more bytes were available.
706    /// </summary>
707    /// <param name="mustSucceed"></param>
708    /// <returns></returns>
709    private bool RefillBuffer(bool mustSucceed)  {
710      if (bufferPos < bufferSize) {
711        throw new InvalidOperationException("RefillBuffer() called when buffer wasn't empty.");
712      }
713
714      if (totalBytesRetired + bufferSize == currentLimit) {
715        // Oops, we hit a limit.
716        if (mustSucceed) {
717          throw InvalidProtocolBufferException.TruncatedMessage();
718        } else {
719          return false;
720        }
721      }
722
723      totalBytesRetired += bufferSize;
724
725      bufferPos = 0;
726      bufferSize = (input == null) ? 0 : input.Read(buffer, 0, buffer.Length);
727      if (bufferSize < 0) {
728        throw new InvalidOperationException("Stream.Read returned a negative count");
729      }
730      if (bufferSize == 0) {
731        if (mustSucceed) {
732          throw InvalidProtocolBufferException.TruncatedMessage();
733        } else {
734          return false;
735        }
736      } else {
737        RecomputeBufferSizeAfterLimit();
738        int totalBytesRead =
739          totalBytesRetired + bufferSize + bufferSizeAfterLimit;
740        if (totalBytesRead > sizeLimit || totalBytesRead < 0) {
741          throw InvalidProtocolBufferException.SizeLimitExceeded();
742        }
743        return true;
744      }
745    }
746
747    /// <summary>
748    /// Read one byte from the input.
749    /// </summary>
750    /// <exception cref="InvalidProtocolBufferException">
751    /// the end of the stream or the current limit was reached
752    /// </exception>
753    public byte ReadRawByte() {
754      if (bufferPos == bufferSize) {
755        RefillBuffer(true);
756      }
757      return buffer[bufferPos++];
758    }
759   
760    /// <summary>
761    /// Read a fixed size of bytes from the input.
762    /// </summary>
763    /// <exception cref="InvalidProtocolBufferException">
764    /// the end of the stream or the current limit was reached
765    /// </exception>
766    public byte[] ReadRawBytes(int size) {
767      if (size < 0) {
768        throw InvalidProtocolBufferException.NegativeSize();
769      }
770
771      if (totalBytesRetired + bufferPos + size > currentLimit) {
772        // Read to the end of the stream anyway.
773        SkipRawBytes(currentLimit - totalBytesRetired - bufferPos);
774        // Then fail.
775        throw InvalidProtocolBufferException.TruncatedMessage();
776      }
777
778      if (size <= bufferSize - bufferPos) {
779        // We have all the bytes we need already.
780        byte[] bytes = new byte[size];
781        Array.Copy(buffer, bufferPos, bytes, 0, size);
782        bufferPos += size;
783        return bytes;
784      } else if (size < BufferSize) {
785        // Reading more bytes than are in the buffer, but not an excessive number
786        // of bytes.  We can safely allocate the resulting array ahead of time.
787
788        // First copy what we have.
789        byte[] bytes = new byte[size];
790        int pos = bufferSize - bufferPos;
791        Array.Copy(buffer, bufferPos, bytes, 0, pos);
792        bufferPos = bufferSize;
793
794        // We want to use RefillBuffer() and then copy from the buffer into our
795        // byte array rather than reading directly into our byte array because
796        // the input may be unbuffered.
797        RefillBuffer(true);
798
799        while (size - pos > bufferSize) {
800          Array.Copy(buffer, 0, bytes, pos, bufferSize);
801          pos += bufferSize;
802          bufferPos = bufferSize;
803          RefillBuffer(true);
804        }
805
806        Array.Copy(buffer, 0, bytes, pos, size - pos);
807        bufferPos = size - pos;
808
809        return bytes;
810      } else {
811        // The size is very large.  For security reasons, we can't allocate the
812        // entire byte array yet.  The size comes directly from the input, so a
813        // maliciously-crafted message could provide a bogus very large size in
814        // order to trick the app into allocating a lot of memory.  We avoid this
815        // by allocating and reading only a small chunk at a time, so that the
816        // malicious message must actually *be* extremely large to cause
817        // problems.  Meanwhile, we limit the allowed size of a message elsewhere.
818
819        // Remember the buffer markers since we'll have to copy the bytes out of
820        // it later.
821        int originalBufferPos = bufferPos;
822        int originalBufferSize = bufferSize;
823
824        // Mark the current buffer consumed.
825        totalBytesRetired += bufferSize;
826        bufferPos = 0;
827        bufferSize = 0;
828
829        // Read all the rest of the bytes we need.
830        int sizeLeft = size - (originalBufferSize - originalBufferPos);
831        List<byte[]> chunks = new List<byte[]>();
832
833        while (sizeLeft > 0) {
834          byte[] chunk = new byte[Math.Min(sizeLeft, BufferSize)];
835          int pos = 0;
836          while (pos < chunk.Length) {
837            int n = (input == null) ? -1 : input.Read(chunk, pos, chunk.Length - pos);
838            if (n <= 0) {
839              throw InvalidProtocolBufferException.TruncatedMessage();
840            }
841            totalBytesRetired += n;
842            pos += n;
843          }
844          sizeLeft -= chunk.Length;
845          chunks.Add(chunk);
846        }
847
848        // OK, got everything.  Now concatenate it all into one buffer.
849        byte[] bytes = new byte[size];
850
851        // Start by copying the leftover bytes from this.buffer.
852        int newPos = originalBufferSize - originalBufferPos;
853        Array.Copy(buffer, originalBufferPos, bytes, 0, newPos);
854
855        // And now all the chunks.
856        foreach (byte[] chunk in chunks) {
857          Array.Copy(chunk, 0, bytes, newPos, chunk.Length);
858          newPos += chunk.Length;
859        }
860
861        // Done.
862        return bytes;
863      }
864    }
865
866    /// <summary>
867    /// Reads and discards a single field, given its tag value.
868    /// </summary>
869    /// <returns>false if the tag is an end-group tag, in which case
870    /// nothing is skipped. Otherwise, returns true.</returns>
871    [CLSCompliant(false)]
872    public bool SkipField(uint tag) {
873      switch (WireFormat.GetTagWireType(tag)) {
874        case WireFormat.WireType.Varint:
875          ReadInt32();
876          return true;
877        case WireFormat.WireType.Fixed64:
878          ReadRawLittleEndian64();
879          return true;
880        case WireFormat.WireType.LengthDelimited:
881          SkipRawBytes((int) ReadRawVarint32());
882          return true;
883        case WireFormat.WireType.StartGroup:
884          SkipMessage();
885          CheckLastTagWas(
886            WireFormat.MakeTag(WireFormat.GetTagFieldNumber(tag),
887                               WireFormat.WireType.EndGroup));
888          return true;
889        case WireFormat.WireType.EndGroup:
890          return false;
891        case WireFormat.WireType.Fixed32:
892          ReadRawLittleEndian32();
893          return true;
894        default:
895          throw InvalidProtocolBufferException.InvalidWireType();
896      }
897    }
898
899    /// <summary>
900    /// Reads and discards an entire message.  This will read either until EOF
901    /// or until an endgroup tag, whichever comes first.
902    /// </summary>
903    public void SkipMessage() {
904      while (true) {
905        uint tag = ReadTag();
906        if (tag == 0 || !SkipField(tag)) {
907          return;
908        }
909      }
910    }
911
912    /// <summary>
913    /// Reads and discards <paramref name="size"/> bytes.
914    /// </summary>
915    /// <exception cref="InvalidProtocolBufferException">the end of the stream
916    /// or the current limit was reached</exception>
917    public void SkipRawBytes(int size) {
918      if (size < 0) {
919        throw InvalidProtocolBufferException.NegativeSize();
920      }
921
922      if (totalBytesRetired + bufferPos + size > currentLimit) {
923        // Read to the end of the stream anyway.
924        SkipRawBytes(currentLimit - totalBytesRetired - bufferPos);
925        // Then fail.
926        throw InvalidProtocolBufferException.TruncatedMessage();
927      }
928
929      if (size <= bufferSize - bufferPos) {
930        // We have all the bytes we need already.
931        bufferPos += size;
932      } else {
933        // Skipping more bytes than are in the buffer.  First skip what we have.
934        int pos = bufferSize - bufferPos;
935        totalBytesRetired += pos;
936        bufferPos = 0;
937        bufferSize = 0;
938
939        // Then skip directly from the InputStream for the rest.
940        if (pos < size) {
941          if (input == null) {
942            throw InvalidProtocolBufferException.TruncatedMessage();
943          }
944          SkipImpl(size - pos);
945          totalBytesRetired += size - pos;
946        }
947      }
948    }
949
950    /// <summary>
951    /// Abstraction of skipping to cope with streams which can't really skip.
952    /// </summary>
953    private void SkipImpl(int amountToSkip) {
954      if (input.CanSeek) {
955        long previousPosition = input.Position;
956        input.Position += amountToSkip;
957        if (input.Position != previousPosition + amountToSkip) {
958          throw InvalidProtocolBufferException.TruncatedMessage();
959        }
960      } else {
961        byte[] skipBuffer = new byte[1024];
962        while (amountToSkip > 0) {
963          int bytesRead = input.Read(skipBuffer, 0, skipBuffer.Length);
964          if (bytesRead <= 0) {
965            throw InvalidProtocolBufferException.TruncatedMessage();
966          }
967          amountToSkip -= bytesRead;
968        }
969      }
970    }
971    #endregion
972  }
973}
Note: See TracBrowser for help on using the repository browser.