blob: c3ca0a10ffae2225146a43f5abbc793632053138 [file] [log] [blame]
csharptest71f662c2011-05-20 15:15:34 -05001#region Copyright notice and license
2
3// Protocol Buffers - Google's data interchange format
4// Copyright 2008 Google Inc. All rights reserved.
5// http://github.com/jskeet/dotnet-protobufs/
6// Original C++/Java/Python code:
7// http://code.google.com/p/protobuf/
8//
9// Redistribution and use in source and binary forms, with or without
10// modification, are permitted provided that the following conditions are
11// met:
12//
13// * Redistributions of source code must retain the above copyright
14// notice, this list of conditions and the following disclaimer.
15// * Redistributions in binary form must reproduce the above
16// copyright notice, this list of conditions and the following disclaimer
17// in the documentation and/or other materials provided with the
18// distribution.
19// * Neither the name of Google Inc. nor the names of its
20// contributors may be used to endorse or promote products derived from
21// this software without specific prior written permission.
22//
23// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
35#endregion
36
37using System;
38using System.IO;
39using Google.ProtocolBuffers.TestProtos;
40using NUnit.Framework;
41using System.Diagnostics;
42
43namespace Google.ProtocolBuffers
44{
45 [TestFixture]
46 public class CodedInputStreamTest
47 {
48 /// <summary>
49 /// Helper to construct a byte array from a bunch of bytes. The inputs are
50 /// actually ints so that I can use hex notation and not get stupid errors
51 /// about precision.
52 /// </summary>
53 private static byte[] Bytes(params int[] bytesAsInts)
54 {
55 byte[] bytes = new byte[bytesAsInts.Length];
56 for (int i = 0; i < bytesAsInts.Length; i++)
57 {
58 bytes[i] = (byte) bytesAsInts[i];
59 }
60 return bytes;
61 }
62
63 /// <summary>
64 /// Parses the given bytes using ReadRawVarint32() and ReadRawVarint64() and
65 /// </summary>
66 private static void AssertReadVarint(byte[] data, ulong value)
67 {
68 CodedInputStream input = CodedInputStream.CreateInstance(data);
69 Assert.AreEqual((uint) value, input.ReadRawVarint32());
70
71 input = CodedInputStream.CreateInstance(data);
72 Assert.AreEqual(value, input.ReadRawVarint64());
73 Assert.IsTrue(input.IsAtEnd);
74
75 // Try different block sizes.
76 for (int bufferSize = 1; bufferSize <= 16; bufferSize *= 2)
77 {
78 input = CodedInputStream.CreateInstance(new SmallBlockInputStream(data, bufferSize));
79 Assert.AreEqual((uint) value, input.ReadRawVarint32());
80
81 input = CodedInputStream.CreateInstance(new SmallBlockInputStream(data, bufferSize));
82 Assert.AreEqual(value, input.ReadRawVarint64());
83 Assert.IsTrue(input.IsAtEnd);
84 }
85
86 // Try reading directly from a MemoryStream. We want to verify that it
87 // doesn't read past the end of the input, so write an extra byte - this
88 // lets us test the position at the end.
89 MemoryStream memoryStream = new MemoryStream();
90 memoryStream.Write(data, 0, data.Length);
91 memoryStream.WriteByte(0);
92 memoryStream.Position = 0;
93 Assert.AreEqual((uint) value, CodedInputStream.ReadRawVarint32(memoryStream));
94 Assert.AreEqual(data.Length, memoryStream.Position);
95 }
96
97 /// <summary>
98 /// Parses the given bytes using ReadRawVarint32() and ReadRawVarint64() and
99 /// expects them to fail with an InvalidProtocolBufferException whose
100 /// description matches the given one.
101 /// </summary>
102 private static void AssertReadVarintFailure(InvalidProtocolBufferException expected, byte[] data)
103 {
104 CodedInputStream input = CodedInputStream.CreateInstance(data);
105 try
106 {
107 input.ReadRawVarint32();
108 Assert.Fail("Should have thrown an exception.");
109 }
110 catch (InvalidProtocolBufferException e)
111 {
112 Assert.AreEqual(expected.Message, e.Message);
113 }
114
115 input = CodedInputStream.CreateInstance(data);
116 try
117 {
118 input.ReadRawVarint64();
119 Assert.Fail("Should have thrown an exception.");
120 }
121 catch (InvalidProtocolBufferException e)
122 {
123 Assert.AreEqual(expected.Message, e.Message);
124 }
125
126 // Make sure we get the same error when reading directly from a Stream.
127 try
128 {
129 CodedInputStream.ReadRawVarint32(new MemoryStream(data));
130 Assert.Fail("Should have thrown an exception.");
131 }
132 catch (InvalidProtocolBufferException e)
133 {
134 Assert.AreEqual(expected.Message, e.Message);
135 }
136 }
137
138 [Test]
139 public void ReadVarint()
140 {
141 AssertReadVarint(Bytes(0x00), 0);
142 AssertReadVarint(Bytes(0x01), 1);
143 AssertReadVarint(Bytes(0x7f), 127);
144 // 14882
145 AssertReadVarint(Bytes(0xa2, 0x74), (0x22 << 0) | (0x74 << 7));
146 // 2961488830
147 AssertReadVarint(Bytes(0xbe, 0xf7, 0x92, 0x84, 0x0b),
148 (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
149 (0x0bL << 28));
150
151 // 64-bit
152 // 7256456126
153 AssertReadVarint(Bytes(0xbe, 0xf7, 0x92, 0x84, 0x1b),
154 (0x3e << 0) | (0x77 << 7) | (0x12 << 14) | (0x04 << 21) |
155 (0x1bL << 28));
156 // 41256202580718336
157 AssertReadVarint(Bytes(0x80, 0xe6, 0xeb, 0x9c, 0xc3, 0xc9, 0xa4, 0x49),
158 (0x00 << 0) | (0x66 << 7) | (0x6b << 14) | (0x1c << 21) |
159 (0x43L << 28) | (0x49L << 35) | (0x24L << 42) | (0x49L << 49));
160 // 11964378330978735131
161 AssertReadVarint(Bytes(0x9b, 0xa8, 0xf9, 0xc2, 0xbb, 0xd6, 0x80, 0x85, 0xa6, 0x01),
162 (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) |
163 (0x3bUL << 28) | (0x56UL << 35) | (0x00UL << 42) |
164 (0x05UL << 49) | (0x26UL << 56) | (0x01UL << 63));
165
166 // Failures
167 AssertReadVarintFailure(
168 InvalidProtocolBufferException.MalformedVarint(),
169 Bytes(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
170 0x00));
171 AssertReadVarintFailure(
172 InvalidProtocolBufferException.TruncatedMessage(),
173 Bytes(0x80));
174 }
175
176 /// <summary>
177 /// Parses the given bytes using ReadRawLittleEndian32() and checks
178 /// that the result matches the given value.
179 /// </summary>
180 private static void AssertReadLittleEndian32(byte[] data, uint value)
181 {
182 CodedInputStream input = CodedInputStream.CreateInstance(data);
183 Assert.AreEqual(value, input.ReadRawLittleEndian32());
184 Assert.IsTrue(input.IsAtEnd);
185
186 // Try different block sizes.
187 for (int blockSize = 1; blockSize <= 16; blockSize *= 2)
188 {
189 input = CodedInputStream.CreateInstance(
190 new SmallBlockInputStream(data, blockSize));
191 Assert.AreEqual(value, input.ReadRawLittleEndian32());
192 Assert.IsTrue(input.IsAtEnd);
193 }
194 }
195
196 /// <summary>
197 /// Parses the given bytes using ReadRawLittleEndian64() and checks
198 /// that the result matches the given value.
199 /// </summary>
200 private static void AssertReadLittleEndian64(byte[] data, ulong value)
201 {
202 CodedInputStream input = CodedInputStream.CreateInstance(data);
203 Assert.AreEqual(value, input.ReadRawLittleEndian64());
204 Assert.IsTrue(input.IsAtEnd);
205
206 // Try different block sizes.
207 for (int blockSize = 1; blockSize <= 16; blockSize *= 2)
208 {
209 input = CodedInputStream.CreateInstance(
210 new SmallBlockInputStream(data, blockSize));
211 Assert.AreEqual(value, input.ReadRawLittleEndian64());
212 Assert.IsTrue(input.IsAtEnd);
213 }
214 }
215
216 [Test]
217 public void ReadLittleEndian()
218 {
219 AssertReadLittleEndian32(Bytes(0x78, 0x56, 0x34, 0x12), 0x12345678);
220 AssertReadLittleEndian32(Bytes(0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef0);
221
222 AssertReadLittleEndian64(Bytes(0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12),
223 0x123456789abcdef0L);
224 AssertReadLittleEndian64(
225 Bytes(0x78, 0x56, 0x34, 0x12, 0xf0, 0xde, 0xbc, 0x9a), 0x9abcdef012345678UL);
226 }
227
228 [Test]
229 public void DecodeZigZag32()
230 {
231 Assert.AreEqual(0, CodedInputStream.DecodeZigZag32(0));
232 Assert.AreEqual(-1, CodedInputStream.DecodeZigZag32(1));
233 Assert.AreEqual(1, CodedInputStream.DecodeZigZag32(2));
234 Assert.AreEqual(-2, CodedInputStream.DecodeZigZag32(3));
235 Assert.AreEqual(0x3FFFFFFF, CodedInputStream.DecodeZigZag32(0x7FFFFFFE));
236 Assert.AreEqual(unchecked((int) 0xC0000000), CodedInputStream.DecodeZigZag32(0x7FFFFFFF));
237 Assert.AreEqual(0x7FFFFFFF, CodedInputStream.DecodeZigZag32(0xFFFFFFFE));
238 Assert.AreEqual(unchecked((int) 0x80000000), CodedInputStream.DecodeZigZag32(0xFFFFFFFF));
239 }
240
241 [Test]
242 public void DecodeZigZag64()
243 {
244 Assert.AreEqual(0, CodedInputStream.DecodeZigZag64(0));
245 Assert.AreEqual(-1, CodedInputStream.DecodeZigZag64(1));
246 Assert.AreEqual(1, CodedInputStream.DecodeZigZag64(2));
247 Assert.AreEqual(-2, CodedInputStream.DecodeZigZag64(3));
248 Assert.AreEqual(0x000000003FFFFFFFL, CodedInputStream.DecodeZigZag64(0x000000007FFFFFFEL));
249 Assert.AreEqual(unchecked((long) 0xFFFFFFFFC0000000L), CodedInputStream.DecodeZigZag64(0x000000007FFFFFFFL));
250 Assert.AreEqual(0x000000007FFFFFFFL, CodedInputStream.DecodeZigZag64(0x00000000FFFFFFFEL));
251 Assert.AreEqual(unchecked((long) 0xFFFFFFFF80000000L), CodedInputStream.DecodeZigZag64(0x00000000FFFFFFFFL));
252 Assert.AreEqual(0x7FFFFFFFFFFFFFFFL, CodedInputStream.DecodeZigZag64(0xFFFFFFFFFFFFFFFEL));
253 Assert.AreEqual(unchecked((long) 0x8000000000000000L), CodedInputStream.DecodeZigZag64(0xFFFFFFFFFFFFFFFFL));
254 }
255
256 [Test]
257 public void ReadWholeMessage()
258 {
259 TestAllTypes message = TestUtil.GetAllSet();
260
261 byte[] rawBytes = message.ToByteArray();
262 Assert.AreEqual(rawBytes.Length, message.SerializedSize);
263 TestAllTypes message2 = TestAllTypes.ParseFrom(rawBytes);
264 TestUtil.AssertAllFieldsSet(message2);
265
266 // Try different block sizes.
267 for (int blockSize = 1; blockSize < 256; blockSize *= 2)
268 {
269 message2 = TestAllTypes.ParseFrom(new SmallBlockInputStream(rawBytes, blockSize));
270 TestUtil.AssertAllFieldsSet(message2);
271 }
272 }
273
274 [Test]
275 public void SkipWholeMessage()
276 {
277 TestAllTypes message = TestUtil.GetAllSet();
278 byte[] rawBytes = message.ToByteArray();
279
280 // Create two parallel inputs. Parse one as unknown fields while using
281 // skipField() to skip each field on the other. Expect the same tags.
282 CodedInputStream input1 = CodedInputStream.CreateInstance(rawBytes);
283 CodedInputStream input2 = CodedInputStream.CreateInstance(rawBytes);
284 UnknownFieldSet.Builder unknownFields = UnknownFieldSet.CreateBuilder();
285
csharptest123e5342011-06-03 14:15:21 -0500286 uint tag;
287 string name;
288 while (input1.ReadTag(out tag, out name))
csharptest71f662c2011-05-20 15:15:34 -0500289 {
csharptest123e5342011-06-03 14:15:21 -0500290 uint tag2;
291 Assert.IsTrue(input2.ReadTag(out tag2, out name));
292 Assert.AreEqual(tag, tag2);
293
csharptest71f662c2011-05-20 15:15:34 -0500294 unknownFields.MergeFieldFrom(tag, input1);
csharptest123e5342011-06-03 14:15:21 -0500295 input2.SkipField();
csharptest71f662c2011-05-20 15:15:34 -0500296 }
297 }
298
299 /// <summary>
300 /// Test that a bug in SkipRawBytes has been fixed: if the skip
301 /// skips exactly up to a limit, this should bnot break things
302 /// </summary>
303 [Test]
304 public void SkipRawBytesBug()
305 {
306 byte[] rawBytes = new byte[] {1, 2};
307 CodedInputStream input = CodedInputStream.CreateInstance(rawBytes);
308
309 int limit = input.PushLimit(1);
310 input.SkipRawBytes(1);
311 input.PopLimit(limit);
312 Assert.AreEqual(2, input.ReadRawByte());
313 }
314
315 public void ReadHugeBlob()
316 {
317 // Allocate and initialize a 1MB blob.
318 byte[] blob = new byte[1 << 20];
319 for (int i = 0; i < blob.Length; i++)
320 {
321 blob[i] = (byte) i;
322 }
323
324 // Make a message containing it.
325 TestAllTypes.Builder builder = TestAllTypes.CreateBuilder();
326 TestUtil.SetAllFields(builder);
327 builder.SetOptionalBytes(ByteString.CopyFrom(blob));
328 TestAllTypes message = builder.Build();
329
330 // Serialize and parse it. Make sure to parse from an InputStream, not
331 // directly from a ByteString, so that CodedInputStream uses buffered
332 // reading.
333 TestAllTypes message2 = TestAllTypes.ParseFrom(message.ToByteString().CreateCodedInput());
334
335 Assert.AreEqual(message.OptionalBytes, message2.OptionalBytes);
336
337 // Make sure all the other fields were parsed correctly.
338 TestAllTypes message3 = TestAllTypes.CreateBuilder(message2)
339 .SetOptionalBytes(TestUtil.GetAllSet().OptionalBytes)
340 .Build();
341 TestUtil.AssertAllFieldsSet(message3);
342 }
343
344 [Test]
345 public void ReadMaliciouslyLargeBlob()
346 {
347 MemoryStream ms = new MemoryStream();
348 CodedOutputStream output = CodedOutputStream.CreateInstance(ms);
349
350 uint tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
351 output.WriteRawVarint32(tag);
352 output.WriteRawVarint32(0x7FFFFFFF);
353 output.WriteRawBytes(new byte[32]); // Pad with a few random bytes.
354 output.Flush();
355 ms.Position = 0;
356
357 CodedInputStream input = CodedInputStream.CreateInstance(ms);
csharptest123e5342011-06-03 14:15:21 -0500358 uint testtag;
359 string ignore;
360 Assert.IsTrue(input.ReadTag(out testtag, out ignore));
361 Assert.AreEqual(tag, testtag);
csharptest71f662c2011-05-20 15:15:34 -0500362
363 try
364 {
csharptestd2af9e92011-06-03 21:35:02 -0500365 ByteString bytes = null;
366 input.ReadBytes(ref bytes);
csharptest71f662c2011-05-20 15:15:34 -0500367 Assert.Fail("Should have thrown an exception!");
368 }
369 catch (InvalidProtocolBufferException)
370 {
371 // success.
372 }
373 }
374
375 private static TestRecursiveMessage MakeRecursiveMessage(int depth)
376 {
377 if (depth == 0)
378 {
379 return TestRecursiveMessage.CreateBuilder().SetI(5).Build();
380 }
381 else
382 {
383 return TestRecursiveMessage.CreateBuilder()
384 .SetA(MakeRecursiveMessage(depth - 1)).Build();
385 }
386 }
387
388 private static void AssertMessageDepth(TestRecursiveMessage message, int depth)
389 {
390 if (depth == 0)
391 {
392 Assert.IsFalse(message.HasA);
393 Assert.AreEqual(5, message.I);
394 }
395 else
396 {
397 Assert.IsTrue(message.HasA);
398 AssertMessageDepth(message.A, depth - 1);
399 }
400 }
401
402 [Test]
403 public void MaliciousRecursion()
404 {
405 ByteString data64 = MakeRecursiveMessage(64).ToByteString();
406 ByteString data65 = MakeRecursiveMessage(65).ToByteString();
407
408 AssertMessageDepth(TestRecursiveMessage.ParseFrom(data64), 64);
409
410 try
411 {
412 TestRecursiveMessage.ParseFrom(data65);
413 Assert.Fail("Should have thrown an exception!");
414 }
415 catch (InvalidProtocolBufferException)
416 {
417 // success.
418 }
419
420 CodedInputStream input = data64.CreateCodedInput();
421 input.SetRecursionLimit(8);
422 try
423 {
424 TestRecursiveMessage.ParseFrom(input);
425 Assert.Fail("Should have thrown an exception!");
426 }
427 catch (InvalidProtocolBufferException)
428 {
429 // success.
430 }
431 }
432
433 [Test]
434 public void SizeLimit()
435 {
436 // Have to use a Stream rather than ByteString.CreateCodedInput as SizeLimit doesn't
437 // apply to the latter case.
438 MemoryStream ms = new MemoryStream(TestUtil.GetAllSet().ToByteString().ToByteArray());
439 CodedInputStream input = CodedInputStream.CreateInstance(ms);
440 input.SetSizeLimit(16);
441
442 try
443 {
444 TestAllTypes.ParseFrom(input);
445 Assert.Fail("Should have thrown an exception!");
446 }
447 catch (InvalidProtocolBufferException)
448 {
449 // success.
450 }
451 }
452
453 [Test]
454 public void ResetSizeCounter()
455 {
456 CodedInputStream input = CodedInputStream.CreateInstance(
457 new SmallBlockInputStream(new byte[256], 8));
458 input.SetSizeLimit(16);
459 input.ReadRawBytes(16);
460
461 try
462 {
463 input.ReadRawByte();
464 Assert.Fail("Should have thrown an exception!");
465 }
466 catch (InvalidProtocolBufferException)
467 {
468 // Success.
469 }
470
471 input.ResetSizeCounter();
472 input.ReadRawByte(); // No exception thrown.
473
474 try
475 {
476 input.ReadRawBytes(16); // Hits limit again.
477 Assert.Fail("Should have thrown an exception!");
478 }
479 catch (InvalidProtocolBufferException)
480 {
481 // Success.
482 }
483 }
484
485 /// <summary>
486 /// Tests that if we read an string that contains invalid UTF-8, no exception
487 /// is thrown. Instead, the invalid bytes are replaced with the Unicode
488 /// "replacement character" U+FFFD.
489 /// </summary>
490 [Test]
491 public void ReadInvalidUtf8()
492 {
493 MemoryStream ms = new MemoryStream();
494 CodedOutputStream output = CodedOutputStream.CreateInstance(ms);
495
496 uint tag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
497 output.WriteRawVarint32(tag);
498 output.WriteRawVarint32(1);
499 output.WriteRawBytes(new byte[] {0x80});
500 output.Flush();
501 ms.Position = 0;
502
503 CodedInputStream input = CodedInputStream.CreateInstance(ms);
csharptest123e5342011-06-03 14:15:21 -0500504
505 uint testtag;
506 string ignored;
507
508 Assert.IsTrue(input.ReadTag(out testtag, out ignored));
509 Assert.AreEqual(tag, testtag);
csharptestd2af9e92011-06-03 21:35:02 -0500510 string text = null;
511 input.ReadString(ref text);
csharptest71f662c2011-05-20 15:15:34 -0500512 Assert.AreEqual('\ufffd', text[0]);
513 }
514
515 /// <summary>
516 /// A stream which limits the number of bytes it reads at a time.
517 /// We use this to make sure that CodedInputStream doesn't screw up when
518 /// reading in small blocks.
519 /// </summary>
520 private sealed class SmallBlockInputStream : MemoryStream
521 {
522 private readonly int blockSize;
523
524 public SmallBlockInputStream(byte[] data, int blockSize)
525 : base(data)
526 {
527 this.blockSize = blockSize;
528 }
529
530 public override int Read(byte[] buffer, int offset, int count)
531 {
532 return base.Read(buffer, offset, Math.Min(count, blockSize));
533 }
534 }
535 }
536}