• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2008 Google Inc.  All rights reserved.
4 // https://developers.google.com/protocol-buffers/
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
8 // met:
9 //
10 //     * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 //     * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following disclaimer
14 // in the documentation and/or other materials provided with the
15 // distribution.
16 //     * Neither the name of Google Inc. nor the names of its
17 // contributors may be used to endorse or promote products derived from
18 // this software without specific prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #endregion
32 
33 using System;
34 using System.Buffers;
35 using System.IO;
36 using Google.Protobuf.TestProtos;
37 using Proto2 = Google.Protobuf.TestProtos.Proto2;
38 using NUnit.Framework;
39 
40 namespace Google.Protobuf
41 {
42     public class CodedInputStreamTest
43     {
44         /// <summary>
45         /// Helper to construct a byte array from a bunch of bytes.  The inputs are
46         /// actually ints so that I can use hex notation and not get stupid errors
47         /// about precision.
48         /// </summary>
Bytes(params int[] bytesAsInts)49         private static byte[] Bytes(params int[] bytesAsInts)
50         {
51             byte[] bytes = new byte[bytesAsInts.Length];
52             for (int i = 0; i < bytesAsInts.Length; i++)
53             {
54                 bytes[i] = (byte) bytesAsInts[i];
55             }
56             return bytes;
57         }
58 
59         /// <summary>
60         /// Parses the given bytes using ReadRawVarint32() and ReadRawVarint64()
61         /// </summary>
AssertReadVarint(byte[] data, ulong value)62         private static void AssertReadVarint(byte[] data, ulong value)
63         {
64             CodedInputStream input = new CodedInputStream(data);
65             Assert.AreEqual((uint) value, input.ReadRawVarint32());
66             Assert.IsTrue(input.IsAtEnd);
67 
68             input = new CodedInputStream(data);
69             Assert.AreEqual(value, input.ReadRawVarint64());
70             Assert.IsTrue(input.IsAtEnd);
71 
72             AssertReadFromParseContext(new ReadOnlySequence<byte>(data), (ref ParseContext ctx) =>
73             {
74                 Assert.AreEqual((uint) value, ctx.ReadUInt32());
75             }, true);
76 
77             AssertReadFromParseContext(new ReadOnlySequence<byte>(data), (ref ParseContext ctx) =>
78             {
79                 Assert.AreEqual(value, ctx.ReadUInt64());
80             }, true);
81 
82             // Try different block sizes.
83             for (int bufferSize = 1; bufferSize <= 16; bufferSize *= 2)
84             {
85                 input = new CodedInputStream(new SmallBlockInputStream(data, bufferSize));
86                 Assert.AreEqual((uint) value, input.ReadRawVarint32());
87 
88                 input = new CodedInputStream(new SmallBlockInputStream(data, bufferSize));
89                 Assert.AreEqual(value, input.ReadRawVarint64());
90                 Assert.IsTrue(input.IsAtEnd);
91 
92                 AssertReadFromParseContext(ReadOnlySequenceFactory.CreateWithContent(data, bufferSize), (ref ParseContext ctx) =>
93                 {
94                     Assert.AreEqual((uint) value, ctx.ReadUInt32());
95                 }, true);
96 
97                 AssertReadFromParseContext(ReadOnlySequenceFactory.CreateWithContent(data, bufferSize), (ref ParseContext ctx) =>
98                 {
99                     Assert.AreEqual(value, ctx.ReadUInt64());
100                 }, true);
101             }
102 
103             // Try reading directly from a MemoryStream. We want to verify that it
104             // doesn't read past the end of the input, so write an extra byte - this
105             // lets us test the position at the end.
106             MemoryStream memoryStream = new MemoryStream();
107             memoryStream.Write(data, 0, data.Length);
108             memoryStream.WriteByte(0);
109             memoryStream.Position = 0;
110             Assert.AreEqual((uint) value, CodedInputStream.ReadRawVarint32(memoryStream));
111             Assert.AreEqual(data.Length, memoryStream.Position);
112         }
113 
114         /// <summary>
115         /// Parses the given bytes using ReadRawVarint32() and ReadRawVarint64() and
116         /// expects them to fail with an InvalidProtocolBufferException whose
117         /// description matches the given one.
118         /// </summary>
AssertReadVarintFailure(InvalidProtocolBufferException expected, byte[] data)119         private static void AssertReadVarintFailure(InvalidProtocolBufferException expected, byte[] data)
120         {
121             CodedInputStream input = new CodedInputStream(data);
122             var exception = Assert.Throws<InvalidProtocolBufferException>(() => input.ReadRawVarint32());
123             Assert.AreEqual(expected.Message, exception.Message);
124 
125             input = new CodedInputStream(data);
126             exception = Assert.Throws<InvalidProtocolBufferException>(() => input.ReadRawVarint64());
127             Assert.AreEqual(expected.Message, exception.Message);
128 
129             AssertReadFromParseContext(new ReadOnlySequence<byte>(data), (ref ParseContext ctx) =>
130             {
131                 try
132                 {
133                     ctx.ReadUInt32();
134                     Assert.Fail();
135                 }
136                 catch (InvalidProtocolBufferException ex)
137                 {
138                     Assert.AreEqual(expected.Message, ex.Message);
139                 }
140             }, false);
141 
142             AssertReadFromParseContext(new ReadOnlySequence<byte>(data), (ref ParseContext ctx) =>
143             {
144                 try
145                 {
146                     ctx.ReadUInt64();
147                     Assert.Fail();
148                 }
149                 catch (InvalidProtocolBufferException ex)
150                 {
151                     Assert.AreEqual(expected.Message, ex.Message);
152                 }
153             }, false);
154 
155             // Make sure we get the same error when reading directly from a Stream.
156             exception = Assert.Throws<InvalidProtocolBufferException>(() => CodedInputStream.ReadRawVarint32(new MemoryStream(data)));
157             Assert.AreEqual(expected.Message, exception.Message);
158         }
159 
ParseContextAssertAction(ref ParseContext ctx)160         private delegate void ParseContextAssertAction(ref ParseContext ctx);
161 
AssertReadFromParseContext(ReadOnlySequence<byte> input, ParseContextAssertAction assertAction, bool assertIsAtEnd)162         private static void AssertReadFromParseContext(ReadOnlySequence<byte> input, ParseContextAssertAction assertAction, bool assertIsAtEnd)
163         {
164             ParseContext.Initialize(input, out ParseContext parseCtx);
165             assertAction(ref parseCtx);
166             if (assertIsAtEnd)
167             {
168                 Assert.IsTrue(SegmentedBufferHelper.IsAtEnd(ref parseCtx.buffer, ref parseCtx.state));
169             }
170         }
171 
172         [Test]
ReadVarint()173         public void ReadVarint()
174         {
175             AssertReadVarint(Bytes(0x00), 0);
176             AssertReadVarint(Bytes(0x01), 1);
177             AssertReadVarint(Bytes(0x7f), 127);
178             // 14882
179             AssertReadVarint(Bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7));
180             // 2961488830
181             AssertReadVarint(Bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b),
182                              (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
183                              (0x0bL << 28));
184 
185             // 64-bit
186             // 7256456126
187             AssertReadVarint(Bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b),
188                              (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
189                              (0x1bL << 28));
190             // 41256202580718336
191             AssertReadVarint(Bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49),
192                              (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) |
193                              (0x43L << 28) | (0x49L << 35) | (0x24L << 42) | (0x49L << 49));
194             // 11964378330978735131
195             AssertReadVarint(Bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01),
196                              (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) |
197                              (0x3bUL << 28) | (0x56UL << 35) | (0x00UL << 42) |
198                              (0x05UL << 49) | (0x26UL << 56) | (0x01UL << 63));
199 
200             // Failures
201             AssertReadVarintFailure(
202                 InvalidProtocolBufferException.MalformedVarint(),
203                 Bytes(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
204                       0x00));
205             AssertReadVarintFailure(
206                 InvalidProtocolBufferException.TruncatedMessage(),
207                 Bytes(0x80));
208         }
209 
210         /// <summary>
211         /// Parses the given bytes using ReadRawLittleEndian32() and checks
212         /// that the result matches the given value.
213         /// </summary>
AssertReadLittleEndian32(byte[] data, uint value)214         private static void AssertReadLittleEndian32(byte[] data, uint value)
215         {
216             CodedInputStream input = new CodedInputStream(data);
217             Assert.AreEqual(value, input.ReadRawLittleEndian32());
218             Assert.IsTrue(input.IsAtEnd);
219 
220             AssertReadFromParseContext(new ReadOnlySequence<byte>(data), (ref ParseContext ctx) =>
221             {
222                 Assert.AreEqual(value, ctx.ReadFixed32());
223             }, true);
224 
225             // Try different block sizes.
226             for (int blockSize = 1; blockSize <= 16; blockSize *= 2)
227             {
228                 input = new CodedInputStream(
229                     new SmallBlockInputStream(data, blockSize));
230                 Assert.AreEqual(value, input.ReadRawLittleEndian32());
231                 Assert.IsTrue(input.IsAtEnd);
232 
233                 AssertReadFromParseContext(ReadOnlySequenceFactory.CreateWithContent(data, blockSize), (ref ParseContext ctx) =>
234                 {
235                     Assert.AreEqual(value, ctx.ReadFixed32());
236                 }, true);
237             }
238         }
239 
240         /// <summary>
241         /// Parses the given bytes using ReadRawLittleEndian64() and checks
242         /// that the result matches the given value.
243         /// </summary>
AssertReadLittleEndian64(byte[] data, ulong value)244         private static void AssertReadLittleEndian64(byte[] data, ulong value)
245         {
246             CodedInputStream input = new CodedInputStream(data);
247             Assert.AreEqual(value, input.ReadRawLittleEndian64());
248             Assert.IsTrue(input.IsAtEnd);
249 
250             AssertReadFromParseContext(new ReadOnlySequence<byte>(data), (ref ParseContext ctx) =>
251             {
252                 Assert.AreEqual(value, ctx.ReadFixed64());
253             }, true);
254 
255             // Try different block sizes.
256             for (int blockSize = 1; blockSize <= 16; blockSize *= 2)
257             {
258                 input = new CodedInputStream(
259                     new SmallBlockInputStream(data, blockSize));
260                 Assert.AreEqual(value, input.ReadRawLittleEndian64());
261                 Assert.IsTrue(input.IsAtEnd);
262 
263                 AssertReadFromParseContext(ReadOnlySequenceFactory.CreateWithContent(data, blockSize), (ref ParseContext ctx) =>
264                 {
265                     Assert.AreEqual(value, ctx.ReadFixed64());
266                 }, true);
267             }
268         }
269 
270         [Test]
ReadLittleEndian()271         public void ReadLittleEndian()
272         {
273             AssertReadLittleEndian32(Bytes(0x78, 0x56, 0x34, 0x12), 0x12345678);
274             AssertReadLittleEndian32(Bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0);
275 
276             AssertReadLittleEndian64(Bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12),
277                                      0x123456789abcdef0L);
278             AssertReadLittleEndian64(
279                 Bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef012345678UL);
280         }
281 
282         [Test]
DecodeZigZag32()283         public void DecodeZigZag32()
284         {
285             Assert.AreEqual(0, ParsingPrimitives.DecodeZigZag32(0));
286             Assert.AreEqual(-1, ParsingPrimitives.DecodeZigZag32(1));
287             Assert.AreEqual(1, ParsingPrimitives.DecodeZigZag32(2));
288             Assert.AreEqual(-2, ParsingPrimitives.DecodeZigZag32(3));
289             Assert.AreEqual(0x3FFFFFFF, ParsingPrimitives.DecodeZigZag32(0x7FFFFFFE));
290             Assert.AreEqual(unchecked((int) 0xC0000000), ParsingPrimitives.DecodeZigZag32(0x7FFFFFFF));
291             Assert.AreEqual(0x7FFFFFFF, ParsingPrimitives.DecodeZigZag32(0xFFFFFFFE));
292             Assert.AreEqual(unchecked((int) 0x80000000), ParsingPrimitives.DecodeZigZag32(0xFFFFFFFF));
293         }
294 
295         [Test]
DecodeZigZag64()296         public void DecodeZigZag64()
297         {
298             Assert.AreEqual(0, ParsingPrimitives.DecodeZigZag64(0));
299             Assert.AreEqual(-1, ParsingPrimitives.DecodeZigZag64(1));
300             Assert.AreEqual(1, ParsingPrimitives.DecodeZigZag64(2));
301             Assert.AreEqual(-2, ParsingPrimitives.DecodeZigZag64(3));
302             Assert.AreEqual(0x000000003FFFFFFFL, ParsingPrimitives.DecodeZigZag64(0x000000007FFFFFFEL));
303             Assert.AreEqual(unchecked((long) 0xFFFFFFFFC0000000L), ParsingPrimitives.DecodeZigZag64(0x000000007FFFFFFFL));
304             Assert.AreEqual(0x000000007FFFFFFFL, ParsingPrimitives.DecodeZigZag64(0x00000000FFFFFFFEL));
305             Assert.AreEqual(unchecked((long) 0xFFFFFFFF80000000L), ParsingPrimitives.DecodeZigZag64(0x00000000FFFFFFFFL));
306             Assert.AreEqual(0x7FFFFFFFFFFFFFFFL, ParsingPrimitives.DecodeZigZag64(0xFFFFFFFFFFFFFFFEL));
307             Assert.AreEqual(unchecked((long) 0x8000000000000000L), ParsingPrimitives.DecodeZigZag64(0xFFFFFFFFFFFFFFFFL));
308         }
309 
310         [Test]
ReadWholeMessage_VaryingBlockSizes()311         public void ReadWholeMessage_VaryingBlockSizes()
312         {
313             TestAllTypes message = SampleMessages.CreateFullTestAllTypes();
314 
315             byte[] rawBytes = message.ToByteArray();
316             Assert.AreEqual(rawBytes.Length, message.CalculateSize());
317             TestAllTypes message2 = TestAllTypes.Parser.ParseFrom(rawBytes);
318             Assert.AreEqual(message, message2);
319 
320             // Try different block sizes.
321             for (int blockSize = 1; blockSize < 256; blockSize *= 2)
322             {
323                 message2 = TestAllTypes.Parser.ParseFrom(new SmallBlockInputStream(rawBytes, blockSize));
324                 Assert.AreEqual(message, message2);
325             }
326         }
327 
328         [Test]
ReadWholeMessage_VaryingBlockSizes_FromSequence()329         public void ReadWholeMessage_VaryingBlockSizes_FromSequence()
330         {
331             TestAllTypes message = SampleMessages.CreateFullTestAllTypes();
332 
333             byte[] rawBytes = message.ToByteArray();
334             Assert.AreEqual(rawBytes.Length, message.CalculateSize());
335             TestAllTypes message2 = TestAllTypes.Parser.ParseFrom(rawBytes);
336             Assert.AreEqual(message, message2);
337 
338             // Try different block sizes.
339             for (int blockSize = 1; blockSize < 256; blockSize *= 2)
340             {
341                 message2 = TestAllTypes.Parser.ParseFrom(ReadOnlySequenceFactory.CreateWithContent(rawBytes, blockSize));
342                 Assert.AreEqual(message, message2);
343             }
344         }
345 
346         [Test]
ReadHugeBlob()347         public void ReadHugeBlob()
348         {
349             // Allocate and initialize a 1MB blob.
350             byte[] blob = new byte[1 << 20];
351             for (int i = 0; i < blob.Length; i++)
352             {
353                 blob[i] = (byte) i;
354             }
355 
356             // Make a message containing it.
357             var message = new TestAllTypes { SingleBytes = ByteString.CopyFrom(blob) };
358 
359             // Serialize and parse it.  Make sure to parse from an InputStream, not
360             // directly from a ByteString, so that CodedInputStream uses buffered
361             // reading.
362             TestAllTypes message2 = TestAllTypes.Parser.ParseFrom(message.ToByteString());
363 
364             Assert.AreEqual(message, message2);
365         }
366 
367         [Test]
ReadMaliciouslyLargeBlob()368         public void ReadMaliciouslyLargeBlob()
369         {
370             MemoryStream ms = new MemoryStream();
371             CodedOutputStream output = new CodedOutputStream(ms);
372 
373             uint tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
374             output.WriteRawVarint32(tag);
375             output.WriteRawVarint32(0x7FFFFFFF);
376             output.WriteRawBytes(new byte[32]); // Pad with a few random bytes.
377             output.Flush();
378             ms.Position = 0;
379 
380             CodedInputStream input = new CodedInputStream(ms);
381             Assert.AreEqual(tag, input.ReadTag());
382 
383             Assert.Throws<InvalidProtocolBufferException>(() => input.ReadBytes());
384         }
385 
386         [Test]
ReadBlobGreaterThanCurrentLimit()387         public void ReadBlobGreaterThanCurrentLimit()
388         {
389             MemoryStream ms = new MemoryStream();
390             CodedOutputStream output = new CodedOutputStream(ms);
391             uint tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
392             output.WriteRawVarint32(tag);
393             output.WriteRawVarint32(4);
394             output.WriteRawBytes(new byte[4]); // Pad with a few random bytes.
395             output.Flush();
396             ms.Position = 0;
397 
398             CodedInputStream input = new CodedInputStream(ms);
399             Assert.AreEqual(tag, input.ReadTag());
400 
401             // Specify limit smaller than data length
402             input.PushLimit(3);
403             Assert.Throws<InvalidProtocolBufferException>(() => input.ReadBytes());
404 
405             AssertReadFromParseContext(new ReadOnlySequence<byte>(ms.ToArray()), (ref ParseContext ctx) =>
406             {
407                 Assert.AreEqual(tag, ctx.ReadTag());
408                 SegmentedBufferHelper.PushLimit(ref ctx.state, 3);
409                 try
410                 {
411                     ctx.ReadBytes();
412                     Assert.Fail();
413                 }
414                 catch (InvalidProtocolBufferException) {}
415             }, true);
416         }
417 
418         [Test]
ReadStringGreaterThanCurrentLimit()419         public void ReadStringGreaterThanCurrentLimit()
420         {
421             MemoryStream ms = new MemoryStream();
422             CodedOutputStream output = new CodedOutputStream(ms);
423             uint tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
424             output.WriteRawVarint32(tag);
425             output.WriteRawVarint32(4);
426             output.WriteRawBytes(new byte[4]); // Pad with a few random bytes.
427             output.Flush();
428             ms.Position = 0;
429 
430             CodedInputStream input = new CodedInputStream(ms.ToArray());
431             Assert.AreEqual(tag, input.ReadTag());
432 
433             // Specify limit smaller than data length
434             input.PushLimit(3);
435             Assert.Throws<InvalidProtocolBufferException>(() => input.ReadString());
436 
437             AssertReadFromParseContext(new ReadOnlySequence<byte>(ms.ToArray()), (ref ParseContext ctx) =>
438             {
439                 Assert.AreEqual(tag, ctx.ReadTag());
440                 SegmentedBufferHelper.PushLimit(ref ctx.state, 3);
441                 try
442                 {
443                     ctx.ReadString();
444                     Assert.Fail();
445                 }
446                 catch (InvalidProtocolBufferException) { }
447             }, true);
448         }
449 
450         // Representations of a tag for field 0 with various wire types
451         [Test]
452         [TestCase(0)]
453         [TestCase(1)]
454         [TestCase(2)]
455         [TestCase(3)]
456         [TestCase(4)]
457         [TestCase(5)]
ReadTag_ZeroFieldRejected(byte tag)458         public void ReadTag_ZeroFieldRejected(byte tag)
459         {
460             CodedInputStream cis = new CodedInputStream(new byte[] { tag });
461             Assert.Throws<InvalidProtocolBufferException>(() => cis.ReadTag());
462         }
463 
MakeRecursiveMessage(int depth)464         internal static TestRecursiveMessage MakeRecursiveMessage(int depth)
465         {
466             if (depth == 0)
467             {
468                 return new TestRecursiveMessage { I = 5 };
469             }
470             else
471             {
472                 return new TestRecursiveMessage { A = MakeRecursiveMessage(depth - 1) };
473             }
474         }
475 
AssertMessageDepth(TestRecursiveMessage message, int depth)476         internal static void AssertMessageDepth(TestRecursiveMessage message, int depth)
477         {
478             if (depth == 0)
479             {
480                 Assert.IsNull(message.A);
481                 Assert.AreEqual(5, message.I);
482             }
483             else
484             {
485                 Assert.IsNotNull(message.A);
486                 AssertMessageDepth(message.A, depth - 1);
487             }
488         }
489 
490         [Test]
MaliciousRecursion()491         public void MaliciousRecursion()
492         {
493             ByteString atRecursiveLimit = MakeRecursiveMessage(CodedInputStream.DefaultRecursionLimit).ToByteString();
494             ByteString beyondRecursiveLimit = MakeRecursiveMessage(CodedInputStream.DefaultRecursionLimit + 1).ToByteString();
495 
496             AssertMessageDepth(TestRecursiveMessage.Parser.ParseFrom(atRecursiveLimit), CodedInputStream.DefaultRecursionLimit);
497 
498             Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(beyondRecursiveLimit));
499 
500             CodedInputStream input = CodedInputStream.CreateWithLimits(new MemoryStream(atRecursiveLimit.ToByteArray()), 1000000, CodedInputStream.DefaultRecursionLimit - 1);
501             Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(input));
502         }
503 
MakeMaliciousRecursionUnknownFieldsPayload(int recursionDepth)504         private static byte[] MakeMaliciousRecursionUnknownFieldsPayload(int recursionDepth)
505         {
506             // generate recursively nested groups that will be parsed as unknown fields
507             int unknownFieldNumber = 14;  // an unused field number
508             MemoryStream ms = new MemoryStream();
509             CodedOutputStream output = new CodedOutputStream(ms);
510             for (int i = 0; i < recursionDepth; i++)
511             {
512                 output.WriteTag(WireFormat.MakeTag(unknownFieldNumber, WireFormat.WireType.StartGroup));
513             }
514             for (int i = 0; i < recursionDepth; i++)
515             {
516                 output.WriteTag(WireFormat.MakeTag(unknownFieldNumber, WireFormat.WireType.EndGroup));
517             }
518             output.Flush();
519             return ms.ToArray();
520         }
521 
522         [Test]
MaliciousRecursion_UnknownFields()523         public void MaliciousRecursion_UnknownFields()
524         {
525             byte[] payloadAtRecursiveLimit = MakeMaliciousRecursionUnknownFieldsPayload(CodedInputStream.DefaultRecursionLimit);
526             byte[] payloadBeyondRecursiveLimit = MakeMaliciousRecursionUnknownFieldsPayload(CodedInputStream.DefaultRecursionLimit + 1);
527 
528             Assert.DoesNotThrow(() => TestRecursiveMessage.Parser.ParseFrom(payloadAtRecursiveLimit));
529             Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(payloadBeyondRecursiveLimit));
530         }
531 
532         [Test]
ReadGroup_WrongEndGroupTag()533         public void ReadGroup_WrongEndGroupTag()
534         {
535             int groupFieldNumber = Proto2.TestAllTypes.OptionalGroupFieldNumber;
536 
537             // write Proto2.TestAllTypes with "optional_group" set, but use wrong EndGroup closing tag
538             MemoryStream ms = new MemoryStream();
539             CodedOutputStream output = new CodedOutputStream(ms);
540             output.WriteTag(WireFormat.MakeTag(groupFieldNumber, WireFormat.WireType.StartGroup));
541             output.WriteGroup(new Proto2.TestAllTypes.Types.OptionalGroup { A = 12345 });
542             // end group with different field number
543             output.WriteTag(WireFormat.MakeTag(groupFieldNumber + 1, WireFormat.WireType.EndGroup));
544             output.Flush();
545             var payload = ms.ToArray();
546 
547             Assert.Throws<InvalidProtocolBufferException>(() => Proto2.TestAllTypes.Parser.ParseFrom(payload));
548         }
549 
550         [Test]
ReadGroup_UnknownFields_WrongEndGroupTag()551         public void ReadGroup_UnknownFields_WrongEndGroupTag()
552         {
553             MemoryStream ms = new MemoryStream();
554             CodedOutputStream output = new CodedOutputStream(ms);
555             output.WriteTag(WireFormat.MakeTag(14, WireFormat.WireType.StartGroup));
556             // end group with different field number
557             output.WriteTag(WireFormat.MakeTag(15, WireFormat.WireType.EndGroup));
558             output.Flush();
559             var payload = ms.ToArray();
560 
561             Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(payload));
562         }
563 
564         [Test]
SizeLimit()565         public void SizeLimit()
566         {
567             // Have to use a Stream rather than ByteString.CreateCodedInput as SizeLimit doesn't
568             // apply to the latter case.
569             MemoryStream ms = new MemoryStream(SampleMessages.CreateFullTestAllTypes().ToByteArray());
570             CodedInputStream input = CodedInputStream.CreateWithLimits(ms, 16, 100);
571             Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseFrom(input));
572         }
573 
574         /// <summary>
575         /// Tests that if we read an string that contains invalid UTF-8, no exception
576         /// is thrown.  Instead, the invalid bytes are replaced with the Unicode
577         /// "replacement character" U+FFFD.
578         /// </summary>
579         [Test]
ReadInvalidUtf8()580         public void ReadInvalidUtf8()
581         {
582             MemoryStream ms = new MemoryStream();
583             CodedOutputStream output = new CodedOutputStream(ms);
584 
585             uint tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
586             output.WriteRawVarint32(tag);
587             output.WriteRawVarint32(1);
588             output.WriteRawBytes(new byte[] {0x80});
589             output.Flush();
590             ms.Position = 0;
591 
592             CodedInputStream input = new CodedInputStream(ms);
593 
594             Assert.AreEqual(tag, input.ReadTag());
595             string text = input.ReadString();
596             Assert.AreEqual('\ufffd', text[0]);
597         }
598 
599         [Test]
ReadNegativeSizedStringThrowsInvalidProtocolBufferException()600         public void ReadNegativeSizedStringThrowsInvalidProtocolBufferException()
601         {
602             MemoryStream ms = new MemoryStream();
603             CodedOutputStream output = new CodedOutputStream(ms);
604 
605             uint tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
606             output.WriteRawVarint32(tag);
607             output.WriteLength(-1);
608             output.Flush();
609             ms.Position = 0;
610 
611             CodedInputStream input = new CodedInputStream(ms);
612 
613             Assert.AreEqual(tag, input.ReadTag());
614             Assert.Throws<InvalidProtocolBufferException>(() => input.ReadString());
615         }
616 
617         [Test]
ReadNegativeSizedBytesThrowsInvalidProtocolBufferException()618         public void ReadNegativeSizedBytesThrowsInvalidProtocolBufferException()
619         {
620             MemoryStream ms = new MemoryStream();
621             CodedOutputStream output = new CodedOutputStream(ms);
622 
623             uint tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
624             output.WriteRawVarint32(tag);
625             output.WriteLength(-1);
626             output.Flush();
627             ms.Position = 0;
628 
629             CodedInputStream input = new CodedInputStream(ms);
630 
631             Assert.AreEqual(tag, input.ReadTag());
632             Assert.Throws<InvalidProtocolBufferException>(() => input.ReadBytes());
633         }
634 
635         /// <summary>
636         /// A stream which limits the number of bytes it reads at a time.
637         /// We use this to make sure that CodedInputStream doesn't screw up when
638         /// reading in small blocks.
639         /// </summary>
640         private sealed class SmallBlockInputStream : MemoryStream
641         {
642             private readonly int blockSize;
643 
SmallBlockInputStream(byte[] data, int blockSize)644             public SmallBlockInputStream(byte[] data, int blockSize)
645                 : base(data)
646             {
647                 this.blockSize = blockSize;
648             }
649 
Read(byte[] buffer, int offset, int count)650             public override int Read(byte[] buffer, int offset, int count)
651             {
652                 return base.Read(buffer, offset, Math.Min(count, blockSize));
653             }
654         }
655 
656         [Test]
TestNegativeEnum()657         public void TestNegativeEnum()
658         {
659             byte[] bytes = { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 };
660             CodedInputStream input = new CodedInputStream(bytes);
661             Assert.AreEqual((int)SampleEnum.NegativeValue, input.ReadEnum());
662             Assert.IsTrue(input.IsAtEnd);
663         }
664 
665         //Issue 71:	CodedInputStream.ReadBytes go to slow path unnecessarily
666         [Test]
TestSlowPathAvoidance()667         public void TestSlowPathAvoidance()
668         {
669             using (var ms = new MemoryStream())
670             {
671                 CodedOutputStream output = new CodedOutputStream(ms);
672                 output.WriteTag(1, WireFormat.WireType.LengthDelimited);
673                 output.WriteBytes(ByteString.CopyFrom(new byte[100]));
674                 output.WriteTag(2, WireFormat.WireType.LengthDelimited);
675                 output.WriteBytes(ByteString.CopyFrom(new byte[100]));
676                 output.Flush();
677 
678                 ms.Position = 0;
679                 CodedInputStream input = new CodedInputStream(ms, new byte[ms.Length / 2], 0, 0, false);
680 
681                 uint tag = input.ReadTag();
682                 Assert.AreEqual(1, WireFormat.GetTagFieldNumber(tag));
683                 Assert.AreEqual(100, input.ReadBytes().Length);
684 
685                 tag = input.ReadTag();
686                 Assert.AreEqual(2, WireFormat.GetTagFieldNumber(tag));
687                 Assert.AreEqual(100, input.ReadBytes().Length);
688             }
689         }
690 
691         [Test]
Tag0Throws()692         public void Tag0Throws()
693         {
694             var input = new CodedInputStream(new byte[] { 0 });
695             Assert.Throws<InvalidProtocolBufferException>(() => input.ReadTag());
696         }
697 
698         [Test]
SkipGroup()699         public void SkipGroup()
700         {
701             // Create an output stream with a group in:
702             // Field 1: string "field 1"
703             // Field 2: group containing:
704             //   Field 1: fixed int32 value 100
705             //   Field 2: string "ignore me"
706             //   Field 3: nested group containing
707             //      Field 1: fixed int64 value 1000
708             // Field 3: string "field 3"
709             var stream = new MemoryStream();
710             var output = new CodedOutputStream(stream);
711             output.WriteTag(1, WireFormat.WireType.LengthDelimited);
712             output.WriteString("field 1");
713 
714             // The outer group...
715             output.WriteTag(2, WireFormat.WireType.StartGroup);
716             output.WriteTag(1, WireFormat.WireType.Fixed32);
717             output.WriteFixed32(100);
718             output.WriteTag(2, WireFormat.WireType.LengthDelimited);
719             output.WriteString("ignore me");
720             // The nested group...
721             output.WriteTag(3, WireFormat.WireType.StartGroup);
722             output.WriteTag(1, WireFormat.WireType.Fixed64);
723             output.WriteFixed64(1000);
724             // Note: Not sure the field number is relevant for end group...
725             output.WriteTag(3, WireFormat.WireType.EndGroup);
726 
727             // End the outer group
728             output.WriteTag(2, WireFormat.WireType.EndGroup);
729 
730             output.WriteTag(3, WireFormat.WireType.LengthDelimited);
731             output.WriteString("field 3");
732             output.Flush();
733             stream.Position = 0;
734 
735             // Now act like a generated client
736             var input = new CodedInputStream(stream);
737             Assert.AreEqual(WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited), input.ReadTag());
738             Assert.AreEqual("field 1", input.ReadString());
739             Assert.AreEqual(WireFormat.MakeTag(2, WireFormat.WireType.StartGroup), input.ReadTag());
740             input.SkipLastField(); // Should consume the whole group, including the nested one.
741             Assert.AreEqual(WireFormat.MakeTag(3, WireFormat.WireType.LengthDelimited), input.ReadTag());
742             Assert.AreEqual("field 3", input.ReadString());
743         }
744 
745         [Test]
SkipGroup_WrongEndGroupTag()746         public void SkipGroup_WrongEndGroupTag()
747         {
748             // Create an output stream with:
749             // Field 1: string "field 1"
750             // Start group 2
751             //   Field 3: fixed int32
752             // End group 4 (should give an error)
753             var stream = new MemoryStream();
754             var output = new CodedOutputStream(stream);
755             output.WriteTag(1, WireFormat.WireType.LengthDelimited);
756             output.WriteString("field 1");
757 
758             // The outer group...
759             output.WriteTag(2, WireFormat.WireType.StartGroup);
760             output.WriteTag(3, WireFormat.WireType.Fixed32);
761             output.WriteFixed32(100);
762             output.WriteTag(4, WireFormat.WireType.EndGroup);
763             output.Flush();
764             stream.Position = 0;
765 
766             // Now act like a generated client
767             var input = new CodedInputStream(stream);
768             Assert.AreEqual(WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited), input.ReadTag());
769             Assert.AreEqual("field 1", input.ReadString());
770             Assert.AreEqual(WireFormat.MakeTag(2, WireFormat.WireType.StartGroup), input.ReadTag());
771             Assert.Throws<InvalidProtocolBufferException>(input.SkipLastField);
772         }
773 
774         [Test]
RogueEndGroupTag()775         public void RogueEndGroupTag()
776         {
777             // If we have an end-group tag without a leading start-group tag, generated
778             // code will just call SkipLastField... so that should fail.
779 
780             var stream = new MemoryStream();
781             var output = new CodedOutputStream(stream);
782             output.WriteTag(1, WireFormat.WireType.EndGroup);
783             output.Flush();
784             stream.Position = 0;
785 
786             var input = new CodedInputStream(stream);
787             Assert.AreEqual(WireFormat.MakeTag(1, WireFormat.WireType.EndGroup), input.ReadTag());
788             Assert.Throws<InvalidProtocolBufferException>(input.SkipLastField);
789         }
790 
791         [Test]
EndOfStreamReachedWhileSkippingGroup()792         public void EndOfStreamReachedWhileSkippingGroup()
793         {
794             var stream = new MemoryStream();
795             var output = new CodedOutputStream(stream);
796             output.WriteTag(1, WireFormat.WireType.StartGroup);
797             output.WriteTag(2, WireFormat.WireType.StartGroup);
798             output.WriteTag(2, WireFormat.WireType.EndGroup);
799 
800             output.Flush();
801             stream.Position = 0;
802 
803             // Now act like a generated client
804             var input = new CodedInputStream(stream);
805             input.ReadTag();
806             Assert.Throws<InvalidProtocolBufferException>(input.SkipLastField);
807         }
808 
809         [Test]
RecursionLimitAppliedWhileSkippingGroup()810         public void RecursionLimitAppliedWhileSkippingGroup()
811         {
812             var stream = new MemoryStream();
813             var output = new CodedOutputStream(stream);
814             for (int i = 0; i < CodedInputStream.DefaultRecursionLimit + 1; i++)
815             {
816                 output.WriteTag(1, WireFormat.WireType.StartGroup);
817             }
818             for (int i = 0; i < CodedInputStream.DefaultRecursionLimit + 1; i++)
819             {
820                 output.WriteTag(1, WireFormat.WireType.EndGroup);
821             }
822             output.Flush();
823             stream.Position = 0;
824 
825             // Now act like a generated client
826             var input = new CodedInputStream(stream);
827             Assert.AreEqual(WireFormat.MakeTag(1, WireFormat.WireType.StartGroup), input.ReadTag());
828             Assert.Throws<InvalidProtocolBufferException>(input.SkipLastField);
829         }
830 
831         [Test]
Construction_Invalid()832         public void Construction_Invalid()
833         {
834             Assert.Throws<ArgumentNullException>(() => new CodedInputStream((byte[]) null));
835             Assert.Throws<ArgumentNullException>(() => new CodedInputStream(null, 0, 0));
836             Assert.Throws<ArgumentNullException>(() => new CodedInputStream((Stream) null));
837             Assert.Throws<ArgumentOutOfRangeException>(() => new CodedInputStream(new byte[10], 100, 0));
838             Assert.Throws<ArgumentOutOfRangeException>(() => new CodedInputStream(new byte[10], 5, 10));
839         }
840 
841         [Test]
CreateWithLimits_InvalidLimits()842         public void CreateWithLimits_InvalidLimits()
843         {
844             var stream = new MemoryStream();
845             Assert.Throws<ArgumentOutOfRangeException>(() => CodedInputStream.CreateWithLimits(stream, 0, 1));
846             Assert.Throws<ArgumentOutOfRangeException>(() => CodedInputStream.CreateWithLimits(stream, 1, 0));
847         }
848 
849         [Test]
Dispose_DisposesUnderlyingStream()850         public void Dispose_DisposesUnderlyingStream()
851         {
852             var memoryStream = new MemoryStream();
853             Assert.IsTrue(memoryStream.CanRead);
854             using (var cis = new CodedInputStream(memoryStream))
855             {
856             }
857             Assert.IsFalse(memoryStream.CanRead); // Disposed
858         }
859 
860         [Test]
Dispose_WithLeaveOpen()861         public void Dispose_WithLeaveOpen()
862         {
863             var memoryStream = new MemoryStream();
864             Assert.IsTrue(memoryStream.CanRead);
865             using (var cis = new CodedInputStream(memoryStream, true))
866             {
867             }
868             Assert.IsTrue(memoryStream.CanRead); // We left the stream open
869         }
870 
871         [Test]
Dispose_FromByteArray()872         public void Dispose_FromByteArray()
873         {
874             var stream = new CodedInputStream(new byte[10]);
875             stream.Dispose();
876         }
877 
878         [Test]
TestParseMessagesCloseTo2G()879         public void TestParseMessagesCloseTo2G()
880         {
881             byte[] serializedMessage = GenerateBigSerializedMessage();
882             // How many of these big messages do we need to take us near our 2GB limit?
883             int count = Int32.MaxValue / serializedMessage.Length;
884             // Now make a MemoryStream that will fake a near-2GB stream of messages by returning
885             // our big serialized message 'count' times.
886             using (RepeatingMemoryStream stream = new RepeatingMemoryStream(serializedMessage, count))
887             {
888                 Assert.DoesNotThrow(()=>TestAllTypes.Parser.ParseFrom(stream));
889             }
890         }
891 
892         [Test]
TestParseMessagesOver2G()893         public void TestParseMessagesOver2G()
894         {
895             byte[] serializedMessage = GenerateBigSerializedMessage();
896             // How many of these big messages do we need to take us near our 2GB limit?
897             int count = Int32.MaxValue / serializedMessage.Length;
898             // Now add one to take us over the 2GB limit
899             count++;
900             // Now make a MemoryStream that will fake a near-2GB stream of messages by returning
901             // our big serialized message 'count' times.
902             using (RepeatingMemoryStream stream = new RepeatingMemoryStream(serializedMessage, count))
903             {
904                 Assert.Throws<InvalidProtocolBufferException>(() => TestAllTypes.Parser.ParseFrom(stream),
905                     "Protocol message was too large.  May be malicious.  " +
906                     "Use CodedInputStream.SetSizeLimit() to increase the size limit.");
907             }
908         }
909 
910         /// <returns>A serialized big message</returns>
GenerateBigSerializedMessage()911         private static byte[] GenerateBigSerializedMessage()
912         {
913             byte[] value = new byte[16 * 1024 * 1024];
914             TestAllTypes message = SampleMessages.CreateFullTestAllTypes();
915             message.SingleBytes = ByteString.CopyFrom(value);
916             return message.ToByteArray();
917         }
918 
919         /// <summary>
920         /// A MemoryStream that repeats a byte arrays' content a number of times.
921         /// Simulates really large input without consuming loads of memory. Used above
922         /// to test the parsing behavior when the input size exceeds 2GB or close to it.
923         /// </summary>
924         private class RepeatingMemoryStream: MemoryStream
925         {
926             private readonly byte[] bytes;
927             private readonly int maxIterations;
928             private int index = 0;
929 
RepeatingMemoryStream(byte[] bytes, int maxIterations)930             public RepeatingMemoryStream(byte[] bytes, int maxIterations)
931             {
932                 this.bytes = bytes;
933                 this.maxIterations = maxIterations;
934             }
935 
Read(byte[] buffer, int offset, int count)936             public override int Read(byte[] buffer, int offset, int count)
937             {
938                 if (bytes.Length == 0)
939                 {
940                     return 0;
941                 }
942                 int numBytesCopiedTotal = 0;
943                 while (numBytesCopiedTotal < count && index < maxIterations)
944                 {
945                     int numBytesToCopy = Math.Min(bytes.Length - (int)Position, count);
946                     Array.Copy(bytes, (int)Position, buffer, offset, numBytesToCopy);
947                     numBytesCopiedTotal += numBytesToCopy;
948                     offset += numBytesToCopy;
949                     count -= numBytesCopiedTotal;
950                     Position += numBytesToCopy;
951                     if (Position >= bytes.Length)
952                     {
953                         Position = 0;
954                         index++;
955                     }
956                 }
957                 return numBytesCopiedTotal;
958             }
959         }
960     }
961 }
962