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