blob: b7fe9c3394ee62f7a1e5edb9829002a814ae4b14 [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
64 const int DefaultRecursionLimit = 64;
65 const int DefaultSizeLimit = 64 << 20; // 64MB
66 const int BufferSize = 4096;
67
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>
129 public void CheckLastTagWas(uint value) {
130 if (lastTag != value) {
131 throw InvalidProtocolBufferException.InvalidEndTag();
132 }
133 }
134 #endregion
135
136 #region Reading of tags etc
137 /// <summary>
138 /// Attempt to read a field tag, returning 0 if we have reached the end
139 /// of the input data. Protocol message parsers use this to read tags,
140 /// since a protocol message may legally end wherever a tag occurs, and
141 /// zero is not a valid tag number.
142 /// </summary>
143 public uint ReadTag() {
144 if (bufferPos == bufferSize && !RefillBuffer(false)) {
145 lastTag = 0;
146 return 0;
147 }
148
149 lastTag = ReadRawVarint32();
150 if (lastTag == 0) {
151 // If we actually read zero, that's not a valid tag.
152 throw InvalidProtocolBufferException.InvalidTag();
153 }
154 return lastTag;
155 }
156
157 /// <summary>
158 /// Read a double field from the stream.
159 /// </summary>
160 public double ReadDouble() {
161 // TODO(jonskeet): Test this on different endiannesses
162 return BitConverter.Int64BitsToDouble((long) ReadRawLittleEndian64());
163 }
164
165 /// <summary>
166 /// Read a float field from the stream.
167 /// </summary>
168 public float ReadFloat() {
169 // TODO(jonskeet): Test this on different endiannesses
170 uint raw = ReadRawLittleEndian32();
171 byte[] rawBytes = BitConverter.GetBytes(raw);
172 return BitConverter.ToSingle(rawBytes, 0);
173 }
174
175 /// <summary>
176 /// Read a uint64 field from the stream.
177 /// </summary>
178 public ulong ReadUInt64() {
179 return ReadRawVarint64();
180 }
181
182 /// <summary>
183 /// Read an int64 field from the stream.
184 /// </summary>
185 public long ReadInt64() {
186 return (long) ReadRawVarint64();
187 }
188
189 /// <summary>
190 /// Read an int32 field from the stream.
191 /// </summary>
192 public int ReadInt32() {
193 return (int) ReadRawVarint32();
194 }
195
196 /// <summary>
197 /// Read a fixed64 field from the stream.
198 /// </summary>
199 public ulong ReadFixed64() {
200 return ReadRawLittleEndian64();
201 }
202
203 /// <summary>
204 /// Read a fixed32 field from the stream.
205 /// </summary>
206 public uint ReadFixed32() {
207 return ReadRawLittleEndian32();
208 }
209
210 /// <summary>
211 /// Read a bool field from the stream.
212 /// </summary>
213 public bool ReadBool() {
214 return ReadRawVarint32() != 0;
215 }
216
217 /// <summary>
218 /// Reads a string field from the stream.
219 /// </summary>
220 public String ReadString() {
221 int size = (int) ReadRawVarint32();
222 if (size < bufferSize - bufferPos && size > 0) {
223 // Fast path: We already have the bytes in a contiguous buffer, so
224 // just copy directly from it.
225 String result = Encoding.UTF8.GetString(buffer, bufferPos, size);
226 bufferPos += size;
227 return result;
228 } else {
229 // Slow path: Build a byte array first then copy it.
230 return Encoding.UTF8.GetString(ReadRawBytes(size));
231 }
232 }
233
234 /// <summary>
235 /// Reads a group field value from the stream.
236 /// </summary>
237 public void ReadGroup(int fieldNumber, IBuilder builder,
238 ExtensionRegistry extensionRegistry) {
239 if (recursionDepth >= recursionLimit) {
240 throw InvalidProtocolBufferException.RecursionLimitExceeded();
241 }
242 ++recursionDepth;
243 builder.WeakMergeFrom(this, extensionRegistry);
244 CheckLastTagWas(WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
245 --recursionDepth;
246 }
247
248 /// <summary>
249 /// Reads a group field value from the stream and merges it into the given
250 /// UnknownFieldSet.
251 /// </summary>
252 public void ReadUnknownGroup(int fieldNumber, UnknownFieldSet.Builder builder) {
253 if (recursionDepth >= recursionLimit) {
254 throw InvalidProtocolBufferException.RecursionLimitExceeded();
255 }
256 ++recursionDepth;
257 builder.MergeFrom(this);
258 CheckLastTagWas(WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
259 --recursionDepth;
260 }
261
262 /// <summary>
263 /// Reads an embedded message field value from the stream.
264 /// </summary>
265 public void ReadMessage(IBuilder builder, ExtensionRegistry extensionRegistry) {
266 int length = (int) ReadRawVarint32();
267 if (recursionDepth >= recursionLimit) {
268 throw InvalidProtocolBufferException.RecursionLimitExceeded();
269 }
270 int oldLimit = PushLimit(length);
271 ++recursionDepth;
272 builder.WeakMergeFrom(this, extensionRegistry);
273 CheckLastTagWas(0);
274 --recursionDepth;
275 PopLimit(oldLimit);
276 }
277
278 /// <summary>
279 /// Reads a bytes field value from the stream.
280 /// </summary>
281 public ByteString ReadBytes() {
282 int size = (int) ReadRawVarint32();
283 if (size < bufferSize - bufferPos && size > 0) {
284 // Fast path: We already have the bytes in a contiguous buffer, so
285 // just copy directly from it.
286 ByteString result = ByteString.CopyFrom(buffer, bufferPos, size);
287 bufferPos += size;
288 return result;
289 } else {
290 // Slow path: Build a byte array first then copy it.
291 return ByteString.CopyFrom(ReadRawBytes(size));
292 }
293 }
294
295 /// <summary>
296 /// Reads a uint32 field value from the stream.
297 /// </summary>
298 public uint ReadUInt32() {
299 return ReadRawVarint32();
300 }
301
302 /// <summary>
303 /// Reads an enum field value from the stream. The caller is responsible
304 /// for converting the numeric value to an actual enum.
305 /// </summary>
306 public int ReadEnum() {
307 return (int) ReadRawVarint32();
308 }
309
310 /// <summary>
311 /// Reads an sfixed32 field value from the stream.
312 /// </summary>
313 public int ReadSFixed32() {
314 return (int) ReadRawLittleEndian32();
315 }
316
317 /// <summary>
318 /// Reads an sfixed64 field value from the stream.
319 /// </summary>
320 public long ReadSFixed64() {
321 return (long) ReadRawLittleEndian64();
322 }
323
324 /// <summary>
325 /// Reads an sint32 field value from the stream.
326 /// </summary>
327 public int ReadSInt32() {
328 return DecodeZigZag32(ReadRawVarint32());
329 }
330
331 /// <summary>
332 /// Reads an sint64 field value from the stream.
333 /// </summary>
334 public long ReadSInt64() {
335 return DecodeZigZag64(ReadRawVarint64());
336 }
337
338 /// <summary>
339 /// Reads a field of any primitive type. Enums, groups and embedded
340 /// messages are not handled by this method.
341 /// </summary>
342 public object ReadPrimitiveField(FieldType fieldType) {
343 switch (fieldType) {
344 case FieldType.Double: return ReadDouble();
345 case FieldType.Float: return ReadFloat();
346 case FieldType.Int64: return ReadInt64();
347 case FieldType.UInt64: return ReadUInt64();
348 case FieldType.Int32: return ReadInt32();
349 case FieldType.Fixed64: return ReadFixed64();
350 case FieldType.Fixed32: return ReadFixed32();
351 case FieldType.Bool: return ReadBool();
352 case FieldType.String: return ReadString();
353 case FieldType.Bytes: return ReadBytes();
354 case FieldType.UInt32: return ReadUInt32();
355 case FieldType.SFixed32: return ReadSFixed32();
356 case FieldType.SFixed64: return ReadSFixed64();
357 case FieldType.SInt32: return ReadSInt32();
358 case FieldType.SInt64: return ReadSInt64();
359 case FieldType.Group:
360 throw new ArgumentException("ReadPrimitiveField() cannot handle nested groups.");
361 case FieldType.Message:
362 throw new ArgumentException("ReadPrimitiveField() cannot handle embedded messages.");
363 // We don't handle enums because we don't know what to do if the
364 // value is not recognized.
365 case FieldType.Enum:
366 throw new ArgumentException("ReadPrimitiveField() cannot handle enums.");
367 default:
368 throw new ArgumentOutOfRangeException("Invalid field type " + fieldType);
369 }
370 }
371
372 #endregion
373
374 #region Underlying reading primitives
375
376 /// <summary>
377 /// Same code as ReadRawVarint32, but read each byte individually, checking for
378 /// buffer overflow.
379 /// </summary>
380 private uint SlowReadRawVarint32() {
381 int tmp = ReadRawByte();
382 if (tmp < 128) {
383 return (uint)tmp;
384 }
385 int result = tmp & 0x7f;
386 if ((tmp = ReadRawByte()) < 128) {
387 result |= tmp << 7;
388 } else {
389 result |= (tmp & 0x7f) << 7;
390 if ((tmp = ReadRawByte()) < 128) {
391 result |= tmp << 14;
392 } else {
393 result |= (tmp & 0x7f) << 14;
394 if ((tmp = ReadRawByte()) < 128) {
395 result |= tmp << 21;
396 } else {
397 result |= (tmp & 0x7f) << 21;
398 result |= (tmp = ReadRawByte()) << 28;
399 if (tmp >= 128) {
400 // Discard upper 32 bits.
401 for (int i = 0; i < 5; i++) {
402 if (ReadRawByte() < 128) return (uint)result;
403 }
404 throw InvalidProtocolBufferException.MalformedVarint();
405 }
406 }
407 }
408 }
409 return (uint)result;
410 }
411
412 /// <summary>
413 /// Read a raw Varint from the stream. If larger than 32 bits, discard the upper bits.
414 /// This method is optimised for the case where we've got lots of data in the buffer.
415 /// That means we can check the size just once, then just read directly from the buffer
416 /// without constant rechecking of the buffer length.
417 /// </summary>
418 public uint ReadRawVarint32() {
419 if (bufferPos + 5 > bufferSize) {
420 return SlowReadRawVarint32();
421 }
422
423 int tmp = buffer[bufferPos++];
424 if (tmp < 128) {
425 return (uint)tmp;
426 }
427 int result = tmp & 0x7f;
428 if ((tmp = buffer[bufferPos++]) < 128) {
429 result |= tmp << 7;
430 } else {
431 result |= (tmp & 0x7f) << 7;
432 if ((tmp = buffer[bufferPos++]) < 128) {
433 result |= tmp << 14;
434 } else {
435 result |= (tmp & 0x7f) << 14;
436 if ((tmp = buffer[bufferPos++]) < 128) {
437 result |= tmp << 21;
438 } else {
439 result |= (tmp & 0x7f) << 21;
440 result |= (tmp = buffer[bufferPos++]) << 28;
441 if (tmp >= 128) {
442 // Discard upper 32 bits.
443 // Note that this has to use ReadRawByte() as we only ensure we've
444 // got at least 5 bytes at the start of the method. This lets us
445 // use the fast path in more cases, and we rarely hit this section of code.
446 for (int i = 0; i < 5; i++) {
447 if (ReadRawByte() < 128) return (uint)result;
448 }
449 throw InvalidProtocolBufferException.MalformedVarint();
450 }
451 }
452 }
453 }
454 return (uint)result;
455 }
456
457 /// <summary>
458 /// Read a raw varint from the stream.
459 /// </summary>
460 public ulong ReadRawVarint64() {
461 int shift = 0;
462 ulong result = 0;
463 while (shift < 64) {
464 byte b = ReadRawByte();
465 result |= (ulong)(b & 0x7F) << shift;
466 if ((b & 0x80) == 0) {
467 return result;
468 }
469 shift += 7;
470 }
471 throw InvalidProtocolBufferException.MalformedVarint();
472 }
473
474 /// <summary>
475 /// Read a 32-bit little-endian integer from the stream.
476 /// </summary>
477 public uint ReadRawLittleEndian32() {
478 uint b1 = ReadRawByte();
479 uint b2 = ReadRawByte();
480 uint b3 = ReadRawByte();
481 uint b4 = ReadRawByte();
482 return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
483 }
484
485 /// <summary>
486 /// Read a 64-bit little-endian integer from the stream.
487 /// </summary>
488 public ulong ReadRawLittleEndian64() {
489 ulong b1 = ReadRawByte();
490 ulong b2 = ReadRawByte();
491 ulong b3 = ReadRawByte();
492 ulong b4 = ReadRawByte();
493 ulong b5 = ReadRawByte();
494 ulong b6 = ReadRawByte();
495 ulong b7 = ReadRawByte();
496 ulong b8 = ReadRawByte();
497 return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24)
498 | (b5 << 32) | (b6 << 40) | (b7 << 48) | (b8 << 56);
499 }
500 #endregion
501
502 /// <summary>
503 /// Decode a 32-bit value with ZigZag encoding.
504 /// </summary>
505 /// <remarks>
506 /// ZigZag encodes signed integers into values that can be efficiently
507 /// encoded with varint. (Otherwise, negative values must be
508 /// sign-extended to 64 bits to be varint encoded, thus always taking
509 /// 10 bytes on the wire.)
510 /// </remarks>
511 public static int DecodeZigZag32(uint n) {
512 return (int)(n >> 1) ^ -(int)(n & 1);
513 }
514
515 /// <summary>
516 /// Decode a 32-bit value with ZigZag encoding.
517 /// </summary>
518 /// <remarks>
519 /// ZigZag encodes signed integers into values that can be efficiently
520 /// encoded with varint. (Otherwise, negative values must be
521 /// sign-extended to 64 bits to be varint encoded, thus always taking
522 /// 10 bytes on the wire.)
523 /// </remarks>
524 public static long DecodeZigZag64(ulong n) {
525 return (long)(n >> 1) ^ -(long)(n & 1);
526 }
527
528 /// <summary>
529 /// Set the maximum message recursion depth.
530 /// </summary>
531 /// <remarks>
532 /// In order to prevent malicious
533 /// messages from causing stack overflows, CodedInputStream limits
534 /// how deeply messages may be nested. The default limit is 64.
535 /// </remarks>
536 public int SetRecursionLimit(int limit) {
537 if (limit < 0) {
538 throw new ArgumentOutOfRangeException("Recursion limit cannot be negative: " + limit);
539 }
540 int oldLimit = recursionLimit;
541 recursionLimit = limit;
542 return oldLimit;
543 }
544
545 /// <summary>
546 /// Set the maximum message size.
547 /// </summary>
548 /// <remarks>
549 /// In order to prevent malicious messages from exhausting memory or
550 /// causing integer overflows, CodedInputStream limits how large a message may be.
551 /// The default limit is 64MB. You should set this limit as small
552 /// as you can without harming your app's functionality. Note that
553 /// size limits only apply when reading from an InputStream, not
554 /// when constructed around a raw byte array (nor with ByteString.NewCodedInput).
555 /// </remarks>
556 public int SetSizeLimit(int limit) {
557 if (limit < 0) {
558 throw new ArgumentOutOfRangeException("Size limit cannot be negative: " + limit);
559 }
560 int oldLimit = sizeLimit;
561 sizeLimit = limit;
562 return oldLimit;
563 }
564
565 #region Internal reading and buffer management
566 /// <summary>
567 /// Sets currentLimit to (current position) + byteLimit. This is called
568 /// when descending into a length-delimited embedded message. The previous
569 /// limit is returned.
570 /// </summary>
571 /// <returns>The old limit.</returns>
572 public int PushLimit(int byteLimit) {
573 if (byteLimit < 0) {
574 throw InvalidProtocolBufferException.NegativeSize();
575 }
576 byteLimit += totalBytesRetired + bufferPos;
577 int oldLimit = currentLimit;
578 if (byteLimit > oldLimit) {
579 throw InvalidProtocolBufferException.TruncatedMessage();
580 }
581 currentLimit = byteLimit;
582
583 RecomputeBufferSizeAfterLimit();
584
585 return oldLimit;
586 }
587
588 private void RecomputeBufferSizeAfterLimit() {
589 bufferSize += bufferSizeAfterLimit;
590 int bufferEnd = totalBytesRetired + bufferSize;
591 if (bufferEnd > currentLimit) {
592 // Limit is in current buffer.
593 bufferSizeAfterLimit = bufferEnd - currentLimit;
594 bufferSize -= bufferSizeAfterLimit;
595 } else {
596 bufferSizeAfterLimit = 0;
597 }
598 }
599
600 /// <summary>
601 /// Discards the current limit, returning the previous limit.
602 /// </summary>
603 public void PopLimit(int oldLimit) {
604 currentLimit = oldLimit;
605 RecomputeBufferSizeAfterLimit();
606 }
607
608 /// <summary>
609 /// Called when buffer is empty to read more bytes from the
610 /// input. If <paramref name="mustSucceed"/> is true, RefillBuffer() gurantees that
611 /// either there will be at least one byte in the buffer when it returns
612 /// or it will throw an exception. If <paramref name="mustSucceed"/> is false,
613 /// RefillBuffer() returns false if no more bytes were available.
614 /// </summary>
615 /// <param name="mustSucceed"></param>
616 /// <returns></returns>
617 private bool RefillBuffer(bool mustSucceed) {
618 if (bufferPos < bufferSize) {
619 throw new InvalidOperationException("RefillBuffer() called when buffer wasn't empty.");
620 }
621
622 if (totalBytesRetired + bufferSize == currentLimit) {
623 // Oops, we hit a limit.
624 if (mustSucceed) {
625 throw InvalidProtocolBufferException.TruncatedMessage();
626 } else {
627 return false;
628 }
629 }
630
631 totalBytesRetired += bufferSize;
632
633 bufferPos = 0;
634 bufferSize = (input == null) ? 0 : input.Read(buffer, 0, buffer.Length);
635 if (bufferSize == 0) {
636 if (mustSucceed) {
637 throw InvalidProtocolBufferException.TruncatedMessage();
638 } else {
639 return false;
640 }
641 } else {
642 RecomputeBufferSizeAfterLimit();
643 int totalBytesRead =
644 totalBytesRetired + bufferSize + bufferSizeAfterLimit;
645 if (totalBytesRead > sizeLimit || totalBytesRead < 0) {
646 throw InvalidProtocolBufferException.SizeLimitExceeded();
647 }
648 return true;
649 }
650 }
651
652 /// <summary>
653 /// Read one byte from the input.
654 /// </summary>
655 /// <exception cref="InvalidProtocolBufferException">
656 /// he end of the stream or the current limit was reached
657 /// </exception>
658 public byte ReadRawByte() {
659 if (bufferPos == bufferSize) {
660 RefillBuffer(true);
661 }
662 return buffer[bufferPos++];
663 }
664
665 /// <summary>
666 /// Read a fixed size of bytes from the input.
667 /// </summary>
668 /// <exception cref="InvalidProtocolBufferException">
669 /// the end of the stream or the current limit was reached
670 /// </exception>
671 public byte[] ReadRawBytes(int size) {
672 if (size < 0) {
673 throw InvalidProtocolBufferException.NegativeSize();
674 }
675
676 if (totalBytesRetired + bufferPos + size > currentLimit) {
677 // Read to the end of the stream anyway.
678 SkipRawBytes(currentLimit - totalBytesRetired - bufferPos);
679 // Then fail.
680 throw InvalidProtocolBufferException.TruncatedMessage();
681 }
682
683 if (size <= bufferSize - bufferPos) {
684 // We have all the bytes we need already.
685 byte[] bytes = new byte[size];
686 Array.Copy(buffer, bufferPos, bytes, 0, size);
687 bufferPos += size;
688 return bytes;
689 } else if (size < BufferSize) {
690 // Reading more bytes than are in the buffer, but not an excessive number
691 // of bytes. We can safely allocate the resulting array ahead of time.
692
693 // First copy what we have.
694 byte[] bytes = new byte[size];
695 int pos = bufferSize - bufferPos;
696 Array.Copy(buffer, bufferPos, bytes, 0, pos);
697 bufferPos = bufferSize;
698
699 // We want to use RefillBuffer() and then copy from the buffer into our
700 // byte array rather than reading directly into our byte array because
701 // the input may be unbuffered.
702 RefillBuffer(true);
703
704 while (size - pos > bufferSize) {
705 Array.Copy(buffer, 0, bytes, pos, bufferSize);
706 pos += bufferSize;
707 bufferPos = bufferSize;
708 RefillBuffer(true);
709 }
710
711 Array.Copy(buffer, 0, bytes, pos, size - pos);
712 bufferPos = size - pos;
713
714 return bytes;
715 } else {
716 // The size is very large. For security reasons, we can't allocate the
717 // entire byte array yet. The size comes directly from the input, so a
718 // maliciously-crafted message could provide a bogus very large size in
719 // order to trick the app into allocating a lot of memory. We avoid this
720 // by allocating and reading only a small chunk at a time, so that the
721 // malicious message must actually *be* extremely large to cause
722 // problems. Meanwhile, we limit the allowed size of a message elsewhere.
723
724 // Remember the buffer markers since we'll have to copy the bytes out of
725 // it later.
726 int originalBufferPos = bufferPos;
727 int originalBufferSize = bufferSize;
728
729 // Mark the current buffer consumed.
730 totalBytesRetired += bufferSize;
731 bufferPos = 0;
732 bufferSize = 0;
733
734 // Read all the rest of the bytes we need.
735 int sizeLeft = size - (originalBufferSize - originalBufferPos);
736 List<byte[]> chunks = new List<byte[]>();
737
738 while (sizeLeft > 0) {
739 byte[] chunk = new byte[Math.Min(sizeLeft, BufferSize)];
740 int pos = 0;
741 while (pos < chunk.Length) {
742 int n = (input == null) ? -1 : input.Read(chunk, pos, chunk.Length - pos);
743 if (n <= 0) {
744 throw InvalidProtocolBufferException.TruncatedMessage();
745 }
746 totalBytesRetired += n;
747 pos += n;
748 }
749 sizeLeft -= chunk.Length;
750 chunks.Add(chunk);
751 }
752
753 // OK, got everything. Now concatenate it all into one buffer.
754 byte[] bytes = new byte[size];
755
756 // Start by copying the leftover bytes from this.buffer.
757 int newPos = originalBufferSize - originalBufferPos;
758 Array.Copy(buffer, originalBufferPos, bytes, 0, newPos);
759
760 // And now all the chunks.
761 foreach (byte[] chunk in chunks) {
762 Array.Copy(chunk, 0, bytes, newPos, chunk.Length);
763 newPos += chunk.Length;
764 }
765
766 // Done.
767 return bytes;
768 }
769 }
770
771 /// <summary>
772 /// Reads and discards a single field, given its tag value.
773 /// </summary>
774 /// <returns>false if the tag is an end-group tag, in which case
775 /// nothing is skipped. Otherwise, returns true.</returns>
776 public bool SkipField(uint tag) {
777 switch (WireFormat.GetTagWireType(tag)) {
778 case WireFormat.WireType.Varint:
779 ReadInt32();
780 return true;
781 case WireFormat.WireType.Fixed64:
782 ReadRawLittleEndian64();
783 return true;
784 case WireFormat.WireType.LengthDelimited:
785 SkipRawBytes((int) ReadRawVarint32());
786 return true;
787 case WireFormat.WireType.StartGroup:
788 SkipMessage();
789 CheckLastTagWas(
790 WireFormat.MakeTag(WireFormat.GetTagFieldNumber(tag),
791 WireFormat.WireType.EndGroup));
792 return true;
793 case WireFormat.WireType.EndGroup:
794 return false;
795 case WireFormat.WireType.Fixed32:
796 ReadRawLittleEndian32();
797 return true;
798 default:
799 throw InvalidProtocolBufferException.InvalidWireType();
800 }
801 }
802
803 /// <summary>
804 /// Reads and discards an entire message. This will read either until EOF
805 /// or until an endgroup tag, whichever comes first.
806 /// </summary>
807 public void SkipMessage() {
808 while (true) {
809 uint tag = ReadTag();
810 if (tag == 0 || !SkipField(tag)) {
811 return;
812 }
813 }
814 }
815
816 /// <summary>
817 /// Reads and discards <paramref name="size"/> bytes.
818 /// </summary>
819 /// <exception cref="InvalidProtocolBufferException">the end of the stream
820 /// or the current limit was reached</exception>
821 public void SkipRawBytes(int size) {
822 if (size < 0) {
823 throw InvalidProtocolBufferException.NegativeSize();
824 }
825
826 if (totalBytesRetired + bufferPos + size > currentLimit) {
827 // Read to the end of the stream anyway.
828 SkipRawBytes(currentLimit - totalBytesRetired - bufferPos);
829 // Then fail.
830 throw InvalidProtocolBufferException.TruncatedMessage();
831 }
832
833 if (size < bufferSize - bufferPos) {
834 // We have all the bytes we need already.
835 bufferPos += size;
836 } else {
837 // Skipping more bytes than are in the buffer. First skip what we have.
838 int pos = bufferSize - bufferPos;
839 totalBytesRetired += pos;
840 bufferPos = 0;
841 bufferSize = 0;
842
843 // Then skip directly from the InputStream for the rest.
844 if (pos < size) {
845 // TODO(jonskeet): Java implementation uses skip(). Not sure whether this is really equivalent...
846 if (input == null) {
847 throw InvalidProtocolBufferException.TruncatedMessage();
848 }
849 input.Seek(size - pos, SeekOrigin.Current);
850 if (input.Position > input.Length) {
851 throw InvalidProtocolBufferException.TruncatedMessage();
852 }
853 totalBytesRetired += size - pos;
854 }
855 }
856 }
857 #endregion
858 }
859}