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