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