From f34d37a3d4d64621bc87aa0a65a05cab64062399 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Tue, 30 Jun 2015 13:16:20 +0100 Subject: [PATCH] Tidying up and extra tests. This is mostly just making things internal instead of public, removing and reordering a bunch of code in CodedInputStream/CodedOutputStream, and generally tidying up. --- .../CodedInputStreamExtensions.cs | 54 ++++ .../CodedInputStreamTest.cs | 153 +-------- .../CodedOutputStreamTest.cs | 93 +----- .../Collections/RepeatedFieldTest.cs | 291 ++++++++++++++++- .../ProtocolBuffers.Test/FieldCodecTest.cs | 53 ++- .../GeneratedMessageTest.cs | 133 ++++++++ .../ProtocolBuffers.Test.csproj | 2 + csharp/src/ProtocolBuffers.Test/SampleEnum.cs | 42 +++ .../src/ProtocolBuffers/CodedInputStream.cs | 302 +++++++++--------- .../CodedOutputStream.ComputeSize.cs | 51 +-- .../src/ProtocolBuffers/CodedOutputStream.cs | 97 +++++- .../Collections/ReadOnlyDictionary.cs | 2 +- .../Collections/RepeatedField.cs | 7 +- csharp/src/ProtocolBuffers/FieldCodec.cs | 3 +- .../src/ProtocolBuffers/MessageExtensions.cs | 2 +- .../Properties/AssemblyInfo.cs | 7 + csharp/src/ProtocolBuffers/WireFormat.cs | 36 +-- 17 files changed, 871 insertions(+), 457 deletions(-) create mode 100644 csharp/src/ProtocolBuffers.Test/CodedInputStreamExtensions.cs create mode 100644 csharp/src/ProtocolBuffers.Test/SampleEnum.cs diff --git a/csharp/src/ProtocolBuffers.Test/CodedInputStreamExtensions.cs b/csharp/src/ProtocolBuffers.Test/CodedInputStreamExtensions.cs new file mode 100644 index 0000000000..408c7cb90a --- /dev/null +++ b/csharp/src/ProtocolBuffers.Test/CodedInputStreamExtensions.cs @@ -0,0 +1,54 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +using NUnit.Framework; + +namespace Google.Protobuf +{ + internal static class CodedInputStreamExtensions + { + public static void AssertNextTag(this CodedInputStream input, uint expectedTag) + { + uint tag; + Assert.IsTrue(input.ReadTag(out tag)); + Assert.AreEqual(expectedTag, tag); + } + + public static T ReadMessage(this CodedInputStream stream, MessageParser parser) + where T : IMessage + { + var message = parser.CreateTemplate(); + stream.ReadMessage(message); + return message; + } + } +} diff --git a/csharp/src/ProtocolBuffers.Test/CodedInputStreamTest.cs b/csharp/src/ProtocolBuffers.Test/CodedInputStreamTest.cs index 47b5e0ac29..a64994fd0d 100644 --- a/csharp/src/ProtocolBuffers.Test/CodedInputStreamTest.cs +++ b/csharp/src/ProtocolBuffers.Test/CodedInputStreamTest.cs @@ -35,10 +35,8 @@ #endregion using System; -using System.Collections.Generic; using System.IO; using Google.Protobuf.Collections; -using Google.Protobuf.Descriptors; using Google.Protobuf.TestProtos; using NUnit.Framework; @@ -62,7 +60,7 @@ namespace Google.Protobuf } /// - /// Parses the given bytes using ReadRawVarint32() and ReadRawVarint64() and + /// Parses the given bytes using ReadRawVarint32() and ReadRawVarint64() /// private static void AssertReadVarint(byte[] data, ulong value) { @@ -232,66 +230,26 @@ namespace Google.Protobuf Assert.AreEqual(0x7FFFFFFFFFFFFFFFL, CodedInputStream.DecodeZigZag64(0xFFFFFFFFFFFFFFFEL)); Assert.AreEqual(unchecked((long) 0x8000000000000000L), CodedInputStream.DecodeZigZag64(0xFFFFFFFFFFFFFFFFL)); } - /* + [Test] - public void ReadWholeMessage() + public void ReadWholeMessage_VaryingBlockSizes() { - TestAllTypes message = TestUtil.GetAllSet(); + TestAllTypes message = GeneratedMessageTest.GetSampleMessage(); byte[] rawBytes = message.ToByteArray(); - Assert.AreEqual(rawBytes.Length, message.SerializedSize); - TestAllTypes message2 = TestAllTypes.ParseFrom(rawBytes); - TestUtil.AssertAllFieldsSet(message2); + Assert.AreEqual(rawBytes.Length, message.CalculateSize()); + TestAllTypes message2 = TestAllTypes.Parser.ParseFrom(rawBytes); + Assert.AreEqual(message, message2); // Try different block sizes. for (int blockSize = 1; blockSize < 256; blockSize *= 2) { - message2 = TestAllTypes.ParseFrom(new SmallBlockInputStream(rawBytes, blockSize)); - TestUtil.AssertAllFieldsSet(message2); + message2 = TestAllTypes.Parser.ParseFrom(new SmallBlockInputStream(rawBytes, blockSize)); + Assert.AreEqual(message, message2); } } - + [Test] - public void SkipWholeMessage() - { - TestAllTypes message = TestUtil.GetAllSet(); - byte[] rawBytes = message.ToByteArray(); - - // Create two parallel inputs. Parse one as unknown fields while using - // skipField() to skip each field on the other. Expect the same tags. - CodedInputStream input1 = CodedInputStream.CreateInstance(rawBytes); - CodedInputStream input2 = CodedInputStream.CreateInstance(rawBytes); - UnknownFieldSet.Builder unknownFields = UnknownFieldSet.CreateBuilder(); - - uint tag; - string name; - while (input1.ReadTag(out tag, out name)) - { - uint tag2; - Assert.IsTrue(input2.ReadTag(out tag2, out name)); - Assert.AreEqual(tag, tag2); - - unknownFields.MergeFieldFrom(tag, input1); - input2.SkipField(); - } - }*/ - - /// - /// Test that a bug in SkipRawBytes has been fixed: if the skip - /// skips exactly up to a limit, this should bnot break things - /// - [Test] - public void SkipRawBytesBug() - { - byte[] rawBytes = new byte[] {1, 2}; - CodedInputStream input = CodedInputStream.CreateInstance(rawBytes); - - int limit = input.PushLimit(1); - input.SkipRawBytes(1); - input.PopLimit(limit); - Assert.AreEqual(2, input.ReadRawByte()); - } - /* public void ReadHugeBlob() { // Allocate and initialize a 1MB blob. @@ -302,24 +260,15 @@ namespace Google.Protobuf } // Make a message containing it. - TestAllTypes.Builder builder = TestAllTypes.CreateBuilder(); - TestUtil.SetAllFields(builder); - builder.SetOptionalBytes(ByteString.CopyFrom(blob)); - TestAllTypes message = builder.Build(); + var message = new TestAllTypes { SingleBytes = ByteString.CopyFrom(blob) }; // Serialize and parse it. Make sure to parse from an InputStream, not // directly from a ByteString, so that CodedInputStream uses buffered // reading. - TestAllTypes message2 = TestAllTypes.ParseFrom(message.ToByteString().CreateCodedInput()); + TestAllTypes message2 = TestAllTypes.Parser.ParseFrom(message.ToByteString()); - Assert.AreEqual(message.OptionalBytes, message2.OptionalBytes); - - // Make sure all the other fields were parsed correctly. - TestAllTypes message3 = TestAllTypes.CreateBuilder(message2) - .SetOptionalBytes(TestUtil.GetAllSet().OptionalBytes) - .Build(); - TestUtil.AssertAllFieldsSet(message3); - }*/ + Assert.AreEqual(message, message2); + } [Test] public void ReadMaliciouslyLargeBlob() @@ -461,85 +410,15 @@ namespace Google.Protobuf } } - enum TestNegEnum { None = 0, Value = -2 } - [Test] public void TestNegativeEnum() { - byte[] bytes = new byte[10] { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }; + byte[] bytes = { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01 }; CodedInputStream input = CodedInputStream.CreateInstance(bytes); - Assert.AreEqual((int)TestNegEnum.Value, input.ReadEnum()); + Assert.AreEqual((int)SampleEnum.NegativeValue, input.ReadEnum()); Assert.IsTrue(input.IsAtEnd); } - [Test] - public void TestNegativeEnumPackedArray() - { - int arraySize = 1 + (10 * 5); - int msgSize = 1 + 1 + arraySize; - byte[] bytes = new byte[msgSize]; - CodedOutputStream output = CodedOutputStream.CreateInstance(bytes); - // Length-delimited to show we want the packed representation - uint tag = WireFormat.MakeTag(8, WireFormat.WireType.LengthDelimited); - output.WriteTag(tag); - int size = 0; - for (int i = 0; i >= -5; i--) - { - size += CodedOutputStream.ComputeEnumSize(i); - } - output.WriteRawVarint32((uint) size); - for (int i = 0; i >= -5; i--) - { - output.WriteEnum(i); - } - Assert.AreEqual(0, output.SpaceLeft); - - CodedInputStream input = CodedInputStream.CreateInstance(bytes); - Assert.IsTrue(input.ReadTag(out tag)); - - RepeatedField values = new RepeatedField(); - values.AddEntriesFrom(input, FieldCodec.ForEnum(tag, x => (int) x, x => (TestNegEnum) x)); - - Assert.AreEqual(6, values.Count); - Assert.AreEqual(TestNegEnum.None, values[0]); - Assert.AreEqual(((TestNegEnum) (-1)), values[1]); - Assert.AreEqual(TestNegEnum.Value, values[2]); - Assert.AreEqual(((TestNegEnum)(-3)), values[3]); - Assert.AreEqual(((TestNegEnum)(-4)), values[4]); - Assert.AreEqual(((TestNegEnum)(-5)), values[5]); - } - - [Test] - public void TestNegativeEnumArray() - { - int arraySize = 1 + 1 + (11 * 5); - int msgSize = arraySize; - byte[] bytes = new byte[msgSize]; - CodedOutputStream output = CodedOutputStream.CreateInstance(bytes); - uint tag = WireFormat.MakeTag(8, WireFormat.WireType.Varint); - for (int i = 0; i >= -5; i--) - { - output.WriteTag(tag); - output.WriteEnum(i); - } - - Assert.AreEqual(0, output.SpaceLeft); - - CodedInputStream input = CodedInputStream.CreateInstance(bytes); - Assert.IsTrue(input.ReadTag(out tag)); - - RepeatedField values = new RepeatedField(); - values.AddEntriesFrom(input, FieldCodec.ForEnum(tag, x => (int)x, x => (TestNegEnum)x)); - - Assert.AreEqual(6, values.Count); - Assert.AreEqual(TestNegEnum.None, values[0]); - Assert.AreEqual(((TestNegEnum)(-1)), values[1]); - Assert.AreEqual(TestNegEnum.Value, values[2]); - Assert.AreEqual(((TestNegEnum)(-3)), values[3]); - Assert.AreEqual(((TestNegEnum)(-4)), values[4]); - Assert.AreEqual(((TestNegEnum)(-5)), values[5]); - } - //Issue 71: CodedInputStream.ReadBytes go to slow path unnecessarily [Test] public void TestSlowPathAvoidance() diff --git a/csharp/src/ProtocolBuffers.Test/CodedOutputStreamTest.cs b/csharp/src/ProtocolBuffers.Test/CodedOutputStreamTest.cs index dd49e3d8ea..ab5dcbd60e 100644 --- a/csharp/src/ProtocolBuffers.Test/CodedOutputStreamTest.cs +++ b/csharp/src/ProtocolBuffers.Test/CodedOutputStreamTest.cs @@ -35,9 +35,8 @@ #endregion using System; -using System.Collections.Generic; using System.IO; -using Google.Protobuf.Collections; +using Google.Protobuf.TestProtos; using NUnit.Framework; namespace Google.Protobuf @@ -195,42 +194,24 @@ namespace Google.Protobuf 0x9abcdef012345678UL); } - /* [Test] - public void WriteWholeMessage() + public void WriteWholeMessage_VaryingBlockSizes() { - TestAllTypes message = TestUtil.GetAllSet(); + TestAllTypes message = GeneratedMessageTest.GetSampleMessage(); byte[] rawBytes = message.ToByteArray(); - TestUtil.AssertEqualBytes(TestUtil.GoldenMessage.ToByteArray(), rawBytes); // Try different block sizes. for (int blockSize = 1; blockSize < 256; blockSize *= 2) { MemoryStream rawOutput = new MemoryStream(); - CodedOutputStream output = - CodedOutputStream.CreateInstance(rawOutput, blockSize); + CodedOutputStream output = CodedOutputStream.CreateInstance(rawOutput, blockSize); message.WriteTo(output); output.Flush(); - TestUtil.AssertEqualBytes(rawBytes, rawOutput.ToArray()); + Assert.AreEqual(rawBytes, rawOutput.ToArray()); } } - - /// - /// Tests writing a whole message with every packed field type. Ensures the - /// wire format of packed fields is compatible with C++. - /// - [Test] - public void WriteWholePackedFieldsMessage() - { - TestPackedTypes message = TestUtil.GetPackedSet(); - - byte[] rawBytes = message.ToByteArray(); - TestUtil.AssertEqualBytes(TestUtil.GetGoldenPackedFieldsMessage().ToByteArray(), - rawBytes); - } - */ - + [Test] public void EncodeZigZag32() { @@ -296,68 +277,16 @@ namespace Google.Protobuf public void TestNegativeEnumNoTag() { Assert.AreEqual(10, CodedOutputStream.ComputeInt32Size(-2)); - Assert.AreEqual(10, CodedOutputStream.ComputeEnumSize((int) TestNegEnum.Value)); + Assert.AreEqual(10, CodedOutputStream.ComputeEnumSize((int) SampleEnum.NegativeValue)); byte[] bytes = new byte[10]; CodedOutputStream output = CodedOutputStream.CreateInstance(bytes); - output.WriteEnum((int) TestNegEnum.Value); + output.WriteEnum((int) SampleEnum.NegativeValue); Assert.AreEqual(0, output.SpaceLeft); Assert.AreEqual("FE-FF-FF-FF-FF-FF-FF-FF-FF-01", BitConverter.ToString(bytes)); } - enum TestNegEnum { None = 0, Value = -2 } - - /* - [Test] - public void TestNegativeEnumArrayPacked() - { - int arraySize = 1 + (10 * 5); - int msgSize = 1 + 1 + arraySize; - byte[] bytes = new byte[msgSize]; - CodedOutputStream output = CodedOutputStream.CreateInstance(bytes); - output.WriteTag(8, WireFormat.WireType.LengthDelimited); - output.WritePackedEnumArray(new RepeatedField { - 0, (TestNegEnum) (-1), TestNegEnum.Value, (TestNegEnum) (-3), (TestNegEnum) (-4), (TestNegEnum) (-5) }); - - Assert.AreEqual(0, output.SpaceLeft); - - CodedInputStream input = CodedInputStream.CreateInstance(bytes); - uint tag; - Assert.IsTrue(input.ReadTag(out tag)); - - List values = new List(); - input.ReadInt32Array(values); - - Assert.AreEqual(6, values.Count); - for (int i = 0; i > -6; i--) - Assert.AreEqual(i, values[Math.Abs(i)]); - } - - [Test] - public void TestNegativeEnumArray() - { - int arraySize = 1 + 1 + (11 * 5); - int msgSize = arraySize; - byte[] bytes = new byte[msgSize]; - CodedOutputStream output = CodedOutputStream.CreateInstance(bytes); - output.WriteEnumArray(8, new RepeatedField { - 0, (TestNegEnum) (-1), TestNegEnum.Value, (TestNegEnum) (-3), (TestNegEnum) (-4), (TestNegEnum) (-5) }); - Assert.AreEqual(0, output.SpaceLeft); - - CodedInputStream input = CodedInputStream.CreateInstance(bytes); - uint tag; - Assert.IsTrue(input.ReadTag(out tag)); - - List values = new List(); - input.ReadInt32Array(values); - - Assert.AreEqual(6, values.Count); - for (int i = 0; i > -6; i--) - Assert.AreEqual(i, values[Math.Abs(i)]); - } - */ - [Test] public void TestCodedInputOutputPosition() { @@ -407,7 +336,7 @@ namespace Google.Protobuf Assert.AreEqual(130, cout.Position); cout.Flush(); } - //Now test Input stream: + // Now test Input stream: { CodedInputStream cin = CodedInputStream.CreateInstance(new MemoryStream(bytes), new byte[50]); uint tag; @@ -420,8 +349,8 @@ namespace Google.Protobuf //Field 2: Assert.IsTrue(cin.ReadTag(out tag) && tag >> 3 == 2); Assert.AreEqual(4, cin.Position); - uint childlen = cin.ReadRawVarint32(); - Assert.AreEqual(120u, childlen); + int childlen = cin.ReadLength(); + Assert.AreEqual(120, childlen); Assert.AreEqual(5, cin.Position); int oldlimit = cin.PushLimit((int)childlen); Assert.AreEqual(5, cin.Position); diff --git a/csharp/src/ProtocolBuffers.Test/Collections/RepeatedFieldTest.cs b/csharp/src/ProtocolBuffers.Test/Collections/RepeatedFieldTest.cs index 29945c3663..988801b784 100644 --- a/csharp/src/ProtocolBuffers.Test/Collections/RepeatedFieldTest.cs +++ b/csharp/src/ProtocolBuffers.Test/Collections/RepeatedFieldTest.cs @@ -1,5 +1,39 @@ -using System; +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using Google.Protobuf.TestProtos; using NUnit.Framework; @@ -89,5 +123,260 @@ namespace Google.Protobuf.Collections var clone = list.Clone(); clone[0] = 1; } + + [Test] + public void AddEntriesFrom_PackedInt32() + { + uint packedTag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited); + var stream = new MemoryStream(); + var output = CodedOutputStream.CreateInstance(stream); + var length = CodedOutputStream.ComputeInt32Size(10) + + CodedOutputStream.ComputeInt32Size(999) + + CodedOutputStream.ComputeInt32Size(-1000); + output.WriteTag(packedTag); + output.WriteRawVarint32((uint) length); + output.WriteInt32(10); + output.WriteInt32(999); + output.WriteInt32(-1000); + output.Flush(); + stream.Position = 0; + + // Deliberately "expecting" a non-packed tag, but we detect that the data is + // actually packed. + uint nonPackedTag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited); + var field = new RepeatedField(); + var input = CodedInputStream.CreateInstance(stream); + input.AssertNextTag(packedTag); + field.AddEntriesFrom(input, FieldCodec.ForInt32(nonPackedTag)); + CollectionAssert.AreEqual(new[] { 10, 999, -1000 }, field); + Assert.IsTrue(input.IsAtEnd); + } + + [Test] + public void AddEntriesFrom_NonPackedInt32() + { + uint nonPackedTag = WireFormat.MakeTag(10, WireFormat.WireType.Varint); + var stream = new MemoryStream(); + var output = CodedOutputStream.CreateInstance(stream); + output.WriteTag(nonPackedTag); + output.WriteInt32(10); + output.WriteTag(nonPackedTag); + output.WriteInt32(999); + output.WriteTag(nonPackedTag); + output.WriteInt32(-1000); // Just for variety... + output.Flush(); + stream.Position = 0; + + // Deliberately "expecting" a packed tag, but we detect that the data is + // actually not packed. + uint packedTag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited); + var field = new RepeatedField(); + var input = CodedInputStream.CreateInstance(stream); + input.AssertNextTag(nonPackedTag); + field.AddEntriesFrom(input, FieldCodec.ForInt32(packedTag)); + CollectionAssert.AreEqual(new[] { 10, 999, -1000 }, field); + Assert.IsTrue(input.IsAtEnd); + } + + [Test] + public void AddEntriesFrom_String() + { + uint tag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited); + var stream = new MemoryStream(); + var output = CodedOutputStream.CreateInstance(stream); + output.WriteTag(tag); + output.WriteString("Foo"); + output.WriteTag(tag); + output.WriteString(""); + output.WriteTag(tag); + output.WriteString("Bar"); + output.Flush(); + stream.Position = 0; + + var field = new RepeatedField(); + var input = CodedInputStream.CreateInstance(stream); + input.AssertNextTag(tag); + field.AddEntriesFrom(input, FieldCodec.ForString(tag)); + CollectionAssert.AreEqual(new[] { "Foo", "", "Bar" }, field); + Assert.IsTrue(input.IsAtEnd); + } + + [Test] + public void AddEntriesFrom_Message() + { + var message1 = new ForeignMessage { C = 2000 }; + var message2 = new ForeignMessage { C = -250 }; + + uint tag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited); + var stream = new MemoryStream(); + var output = CodedOutputStream.CreateInstance(stream); + output.WriteTag(tag); + output.WriteMessage(message1); + output.WriteTag(tag); + output.WriteMessage(message2); + output.Flush(); + stream.Position = 0; + + var field = new RepeatedField(); + var input = CodedInputStream.CreateInstance(stream); + input.AssertNextTag(tag); + field.AddEntriesFrom(input, FieldCodec.ForMessage(tag, ForeignMessage.Parser)); + CollectionAssert.AreEqual(new[] { message1, message2}, field); + Assert.IsTrue(input.IsAtEnd); + } + + [Test] + public void WriteTo_PackedInt32() + { + uint tag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited); + var field = new RepeatedField { 10, 1000, 1000000 }; + var stream = new MemoryStream(); + var output = CodedOutputStream.CreateInstance(stream); + field.WriteTo(output, FieldCodec.ForInt32(tag)); + output.Flush(); + stream.Position = 0; + + var input = CodedInputStream.CreateInstance(stream); + input.AssertNextTag(tag); + var length = input.ReadLength(); + Assert.AreEqual(10, input.ReadInt32()); + Assert.AreEqual(1000, input.ReadInt32()); + Assert.AreEqual(1000000, input.ReadInt32()); + Assert.IsTrue(input.IsAtEnd); + Assert.AreEqual(1 + CodedOutputStream.ComputeLengthSize(length) + length, stream.Length); + } + + [Test] + public void WriteTo_NonPackedInt32() + { + uint tag = WireFormat.MakeTag(10, WireFormat.WireType.Varint); + var field = new RepeatedField { 10, 1000, 1000000}; + var stream = new MemoryStream(); + var output = CodedOutputStream.CreateInstance(stream); + field.WriteTo(output, FieldCodec.ForInt32(tag)); + output.Flush(); + stream.Position = 0; + + var input = CodedInputStream.CreateInstance(stream); + input.AssertNextTag(tag); + Assert.AreEqual(10, input.ReadInt32()); + input.AssertNextTag(tag); + Assert.AreEqual(1000, input.ReadInt32()); + input.AssertNextTag(tag); + Assert.AreEqual(1000000, input.ReadInt32()); + Assert.IsTrue(input.IsAtEnd); + } + + [Test] + public void WriteTo_String() + { + uint tag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited); + var field = new RepeatedField { "Foo", "", "Bar" }; + var stream = new MemoryStream(); + var output = CodedOutputStream.CreateInstance(stream); + field.WriteTo(output, FieldCodec.ForString(tag)); + output.Flush(); + stream.Position = 0; + + var input = CodedInputStream.CreateInstance(stream); + input.AssertNextTag(tag); + Assert.AreEqual("Foo", input.ReadString()); + input.AssertNextTag(tag); + Assert.AreEqual("", input.ReadString()); + input.AssertNextTag(tag); + Assert.AreEqual("Bar", input.ReadString()); + Assert.IsTrue(input.IsAtEnd); + } + + [Test] + public void WriteTo_Message() + { + var message1 = new ForeignMessage { C = 20 }; + var message2 = new ForeignMessage { C = 25 }; + uint tag = WireFormat.MakeTag(10, WireFormat.WireType.LengthDelimited); + var field = new RepeatedField { message1, message2 }; + var stream = new MemoryStream(); + var output = CodedOutputStream.CreateInstance(stream); + field.WriteTo(output, FieldCodec.ForMessage(tag, ForeignMessage.Parser)); + output.Flush(); + stream.Position = 0; + + var input = CodedInputStream.CreateInstance(stream); + input.AssertNextTag(tag); + Assert.AreEqual(message1, input.ReadMessage(ForeignMessage.Parser)); + input.AssertNextTag(tag); + Assert.AreEqual(message2, input.ReadMessage(ForeignMessage.Parser)); + Assert.IsTrue(input.IsAtEnd); + } + + + [Test] + public void TestNegativeEnumArray() + { + int arraySize = 1 + 1 + (11 * 5); + int msgSize = arraySize; + byte[] bytes = new byte[msgSize]; + CodedOutputStream output = CodedOutputStream.CreateInstance(bytes); + uint tag = WireFormat.MakeTag(8, WireFormat.WireType.Varint); + for (int i = 0; i >= -5; i--) + { + output.WriteTag(tag); + output.WriteEnum(i); + } + + Assert.AreEqual(0, output.SpaceLeft); + + CodedInputStream input = CodedInputStream.CreateInstance(bytes); + Assert.IsTrue(input.ReadTag(out tag)); + + RepeatedField values = new RepeatedField(); + values.AddEntriesFrom(input, FieldCodec.ForEnum(tag, x => (int)x, x => (SampleEnum)x)); + + Assert.AreEqual(6, values.Count); + Assert.AreEqual(SampleEnum.None, values[0]); + Assert.AreEqual(((SampleEnum)(-1)), values[1]); + Assert.AreEqual(SampleEnum.NegativeValue, values[2]); + Assert.AreEqual(((SampleEnum)(-3)), values[3]); + Assert.AreEqual(((SampleEnum)(-4)), values[4]); + Assert.AreEqual(((SampleEnum)(-5)), values[5]); + } + + + [Test] + public void TestNegativeEnumPackedArray() + { + int arraySize = 1 + (10 * 5); + int msgSize = 1 + 1 + arraySize; + byte[] bytes = new byte[msgSize]; + CodedOutputStream output = CodedOutputStream.CreateInstance(bytes); + // Length-delimited to show we want the packed representation + uint tag = WireFormat.MakeTag(8, WireFormat.WireType.LengthDelimited); + output.WriteTag(tag); + int size = 0; + for (int i = 0; i >= -5; i--) + { + size += CodedOutputStream.ComputeEnumSize(i); + } + output.WriteRawVarint32((uint)size); + for (int i = 0; i >= -5; i--) + { + output.WriteEnum(i); + } + Assert.AreEqual(0, output.SpaceLeft); + + CodedInputStream input = CodedInputStream.CreateInstance(bytes); + Assert.IsTrue(input.ReadTag(out tag)); + + RepeatedField values = new RepeatedField(); + values.AddEntriesFrom(input, FieldCodec.ForEnum(tag, x => (int)x, x => (SampleEnum)x)); + + Assert.AreEqual(6, values.Count); + Assert.AreEqual(SampleEnum.None, values[0]); + Assert.AreEqual(((SampleEnum)(-1)), values[1]); + Assert.AreEqual(SampleEnum.NegativeValue, values[2]); + Assert.AreEqual(((SampleEnum)(-3)), values[3]); + Assert.AreEqual(((SampleEnum)(-4)), values[4]); + Assert.AreEqual(((SampleEnum)(-5)), values[5]); + } } } diff --git a/csharp/src/ProtocolBuffers.Test/FieldCodecTest.cs b/csharp/src/ProtocolBuffers.Test/FieldCodecTest.cs index 501416219d..a14040d1ac 100644 --- a/csharp/src/ProtocolBuffers.Test/FieldCodecTest.cs +++ b/csharp/src/ProtocolBuffers.Test/FieldCodecTest.cs @@ -63,15 +63,21 @@ namespace Google.Protobuf }; [Test, TestCaseSource("Codecs")] - public void RoundTrip(ICodecTestData codec) + public void RoundTripWithTag(ICodecTestData codec) { - codec.TestRoundTrip(); + codec.TestRoundTripWithTag(); + } + + [Test, TestCaseSource("Codecs")] + public void RoundTripRaw(ICodecTestData codec) + { + codec.TestRoundTripRaw(); } [Test, TestCaseSource("Codecs")] public void CalculateSize(ICodecTestData codec) { - codec.TestCalculateSize(); + codec.TestCalculateSizeWithTag(); } [Test, TestCaseSource("Codecs")] @@ -82,8 +88,9 @@ namespace Google.Protobuf public interface ICodecTestData { - void TestRoundTrip(); - void TestCalculateSize(); + void TestRoundTripRaw(); + void TestRoundTripWithTag(); + void TestCalculateSizeWithTag(); void TestDefaultValue(); } @@ -100,7 +107,19 @@ namespace Google.Protobuf this.name = name; } - public void TestRoundTrip() + public void TestRoundTripRaw() + { + var stream = new MemoryStream(); + var codedOutput = CodedOutputStream.CreateInstance(stream); + codec.ValueWriter(codedOutput, sampleValue); + codedOutput.Flush(); + stream.Position = 0; + var codedInput = CodedInputStream.CreateInstance(stream); + Assert.AreEqual(sampleValue, codec.ValueReader(codedInput)); + Assert.IsTrue(codedInput.IsAtEnd); + } + + public void TestRoundTripWithTag() { var stream = new MemoryStream(); var codedOutput = CodedOutputStream.CreateInstance(stream); @@ -108,14 +127,12 @@ namespace Google.Protobuf codedOutput.Flush(); stream.Position = 0; var codedInput = CodedInputStream.CreateInstance(stream); - uint tag; - Assert.IsTrue(codedInput.ReadTag(out tag)); - Assert.AreEqual(codec.Tag, tag); + codedInput.AssertNextTag(codec.Tag); Assert.AreEqual(sampleValue, codec.Read(codedInput)); Assert.IsTrue(codedInput.IsAtEnd); } - public void TestCalculateSize() + public void TestCalculateSizeWithTag() { var stream = new MemoryStream(); var codedOutput = CodedOutputStream.CreateInstance(stream); @@ -126,6 +143,7 @@ namespace Google.Protobuf public void TestDefaultValue() { + // WriteTagAndValue ignores default values var stream = new MemoryStream(); var codedOutput = CodedOutputStream.CreateInstance(stream); codec.WriteTagAndValue(codedOutput, codec.DefaultValue); @@ -136,9 +154,20 @@ namespace Google.Protobuf { Assert.AreEqual(default(T), codec.DefaultValue); } - } - public string Description { get { return name; } } + // The plain ValueWriter/ValueReader delegates don't. + if (codec.DefaultValue != null) // This part isn't appropriate for message types. + { + codedOutput = CodedOutputStream.CreateInstance(stream); + codec.ValueWriter(codedOutput, codec.DefaultValue); + codedOutput.Flush(); + Assert.AreNotEqual(0, stream.Position); + Assert.AreEqual(stream.Position, codec.ValueSizeCalculator(codec.DefaultValue)); + stream.Position = 0; + var codedInput = CodedInputStream.CreateInstance(stream); + Assert.AreEqual(codec.DefaultValue, codec.ValueReader(codedInput)); + } + } public override string ToString() { diff --git a/csharp/src/ProtocolBuffers.Test/GeneratedMessageTest.cs b/csharp/src/ProtocolBuffers.Test/GeneratedMessageTest.cs index f8668662b6..a094e46bfd 100644 --- a/csharp/src/ProtocolBuffers.Test/GeneratedMessageTest.cs +++ b/csharp/src/ProtocolBuffers.Test/GeneratedMessageTest.cs @@ -10,6 +10,61 @@ namespace Google.Protobuf /// public class GeneratedMessageTest { + /// + /// Returns a sample TestAllTypes with all fields populated + /// + public static TestAllTypes GetSampleMessage() + { + return new TestAllTypes + { + SingleBool = true, + SingleBytes = ByteString.CopyFrom(1, 2, 3, 4), + SingleDouble = 23.5, + SingleFixed32 = 23, + SingleFixed64 = 1234567890123, + SingleFloat = 12.25f, + SingleForeignEnum = ForeignEnum.FOREIGN_BAR, + SingleForeignMessage = new ForeignMessage { C = 10 }, + SingleImportEnum = ImportEnum.IMPORT_BAZ, + SingleImportMessage = new ImportMessage { D = 20 }, + SingleInt32 = 100, + SingleInt64 = 3210987654321, + SingleNestedEnum = TestAllTypes.Types.NestedEnum.FOO, + SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 35 }, + SinglePublicImportMessage = new PublicImportMessage { E = 54 }, + SingleSfixed32 = -123, + SingleSfixed64 = -12345678901234, + SingleSint32 = -456, + SingleSint64 = -12345678901235, + SingleString = "test", + SingleUint32 = uint.MaxValue, + SingleUint64 = ulong.MaxValue, + RepeatedBool = { true, false }, + RepeatedBytes = { ByteString.CopyFrom(1, 2, 3, 4), ByteString.CopyFrom(5, 6), ByteString.CopyFrom(new byte[1000]) }, + RepeatedDouble = { -12.25, 23.5 }, + RepeatedFixed32 = { uint.MaxValue, 23 }, + RepeatedFixed64 = { ulong.MaxValue, 1234567890123 }, + RepeatedFloat = { 100f, 12.25f }, + RepeatedForeignEnum = { ForeignEnum.FOREIGN_FOO, ForeignEnum.FOREIGN_BAR }, + RepeatedForeignMessage = { new ForeignMessage(), new ForeignMessage { C = 10 } }, + RepeatedImportEnum = { ImportEnum.IMPORT_BAZ, ImportEnum.IMPORT_ENUM_UNSPECIFIED }, + RepeatedImportMessage = { new ImportMessage { D = 20 }, new ImportMessage { D = 25 } }, + RepeatedInt32 = { 100, 200 }, + RepeatedInt64 = { 3210987654321, long.MaxValue }, + RepeatedNestedEnum = { TestAllTypes.Types.NestedEnum.FOO, TestAllTypes.Types.NestedEnum.NEG }, + RepeatedNestedMessage = { new TestAllTypes.Types.NestedMessage { Bb = 35 }, new TestAllTypes.Types.NestedMessage { Bb = 10 } }, + RepeatedPublicImportMessage = { new PublicImportMessage { E = 54 }, new PublicImportMessage { E = -1 } }, + RepeatedSfixed32 = { -123, 123 }, + RepeatedSfixed64 = { -12345678901234, 12345678901234 }, + RepeatedSint32 = { -456, 100 }, + RepeatedSint64 = { -12345678901235, 123 }, + RepeatedString = { "foo", "bar" }, + RepeatedUint32 = { uint.MaxValue, uint.MinValue }, + RepeatedUint64 = { ulong.MaxValue, uint.MinValue }, + OneofString = "Oneof string" + }; + } + [Test] public void EmptyMessageFieldDistinctFromMissingMessageField() { @@ -485,5 +540,83 @@ namespace Google.Protobuf Assert.Throws(() => frozen.RepeatedDouble.Add(0.0)); Assert.Throws(() => frozen.RepeatedNestedMessage.Add(new TestAllTypes.Types.NestedMessage())); } + + [Test] + public void OneofProperties() + { + // Switch the oneof case between each of the different options, and check everything behaves + // as expected in each case. + var message = new TestAllTypes(); + Assert.AreEqual("", message.OneofString); + Assert.AreEqual(0, message.OneofUint32); + Assert.AreEqual(ByteString.Empty, message.OneofBytes); + Assert.IsNull(message.OneofNestedMessage); + Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.None, message.OneofFieldCase); + + message.OneofString = "sample"; + Assert.AreEqual("sample", message.OneofString); + Assert.AreEqual(0, message.OneofUint32); + Assert.AreEqual(ByteString.Empty, message.OneofBytes); + Assert.IsNull(message.OneofNestedMessage); + Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofString, message.OneofFieldCase); + + var bytes = ByteString.CopyFrom(1, 2, 3); + message.OneofBytes = bytes; + Assert.AreEqual("", message.OneofString); + Assert.AreEqual(0, message.OneofUint32); + Assert.AreEqual(bytes, message.OneofBytes); + Assert.IsNull(message.OneofNestedMessage); + Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofBytes, message.OneofFieldCase); + + message.OneofUint32 = 20; + Assert.AreEqual("", message.OneofString); + Assert.AreEqual(20, message.OneofUint32); + Assert.AreEqual(ByteString.Empty, message.OneofBytes); + Assert.IsNull(message.OneofNestedMessage); + Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofUint32, message.OneofFieldCase); + + var nestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 25 }; + message.OneofNestedMessage = nestedMessage; + Assert.AreEqual("", message.OneofString); + Assert.AreEqual(0, message.OneofUint32); + Assert.AreEqual(ByteString.Empty, message.OneofBytes); + Assert.AreEqual(nestedMessage, message.OneofNestedMessage); + Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofNestedMessage, message.OneofFieldCase); + + message.ClearOneofField(); + Assert.AreEqual("", message.OneofString); + Assert.AreEqual(0, message.OneofUint32); + Assert.AreEqual(ByteString.Empty, message.OneofBytes); + Assert.IsNull(message.OneofNestedMessage); + Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.None, message.OneofFieldCase); + } + + [Test] + public void OneofSerialization_NonDefaultValue() + { + var message = new TestAllTypes(); + message.OneofString = "this would take a bit of space"; + message.OneofUint32 = 10; + var bytes = message.ToByteArray(); + Assert.AreEqual(3, bytes.Length); // 2 bytes for the tag + 1 for the value - no string! + + var message2 = TestAllTypes.Parser.ParseFrom(bytes); + Assert.AreEqual(message, message2); + Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofUint32, message2.OneofFieldCase); + } + + [Test] + public void OneofSerialization_DefaultValue() + { + var message = new TestAllTypes(); + message.OneofString = "this would take a bit of space"; + message.OneofUint32 = 0; // This is the default value for UInt32; normally wouldn't be serialized + var bytes = message.ToByteArray(); + Assert.AreEqual(3, bytes.Length); // 2 bytes for the tag + 1 for the value - it's still serialized + + var message2 = TestAllTypes.Parser.ParseFrom(bytes); + Assert.AreEqual(message, message2); + Assert.AreEqual(TestAllTypes.OneofFieldOneofCase.OneofUint32, message2.OneofFieldCase); + } } } diff --git a/csharp/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj b/csharp/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj index 80b504aa80..ae7d75756e 100644 --- a/csharp/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj +++ b/csharp/src/ProtocolBuffers.Test/ProtocolBuffers.Test.csproj @@ -72,6 +72,7 @@ + @@ -79,6 +80,7 @@ + diff --git a/csharp/src/ProtocolBuffers.Test/SampleEnum.cs b/csharp/src/ProtocolBuffers.Test/SampleEnum.cs new file mode 100644 index 0000000000..001f9b0864 --- /dev/null +++ b/csharp/src/ProtocolBuffers.Test/SampleEnum.cs @@ -0,0 +1,42 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +namespace Google.Protobuf +{ + // Just a sample enum with positive and negative values to be used in tests. + internal enum SampleEnum + { + NegativeValue = -2, + None = 0, + PositiveValue = 3 + } +} \ No newline at end of file diff --git a/csharp/src/ProtocolBuffers/CodedInputStream.cs b/csharp/src/ProtocolBuffers/CodedInputStream.cs index 75178d1437..5c64fd97a4 100644 --- a/csharp/src/ProtocolBuffers/CodedInputStream.cs +++ b/csharp/src/ProtocolBuffers/CodedInputStream.cs @@ -37,7 +37,6 @@ using System; using System.Collections.Generic; using System.IO; -using Google.Protobuf.Collections; namespace Google.Protobuf { @@ -178,8 +177,61 @@ namespace Google.Protobuf /// internal uint LastTag { get { return lastTag; } } - #region Validation + #region Limits for recursion and length + /// + /// Set the maximum message recursion depth. + /// + /// + /// In order to prevent malicious + /// messages from causing stack overflows, CodedInputStream limits + /// how deeply messages may be nested. The default limit is 64. + /// + public int SetRecursionLimit(int limit) + { + if (limit < 0) + { + throw new ArgumentOutOfRangeException("Recursion limit cannot be negative: " + limit); + } + int oldLimit = recursionLimit; + recursionLimit = limit; + return oldLimit; + } + + /// + /// Set the maximum message size. + /// + /// + /// In order to prevent malicious messages from exhausting memory or + /// causing integer overflows, CodedInputStream limits how large a message may be. + /// The default limit is 64MB. You should set this limit as small + /// as you can without harming your app's functionality. Note that + /// size limits only apply when reading from an InputStream, not + /// when constructed around a raw byte array (nor with ByteString.NewCodedInput). + /// If you want to read several messages from a single CodedInputStream, you + /// can call ResetSizeCounter() after each message to avoid hitting the + /// size limit. + /// + public int SetSizeLimit(int limit) + { + if (limit < 0) + { + throw new ArgumentOutOfRangeException("Size limit cannot be negative: " + limit); + } + int oldLimit = sizeLimit; + sizeLimit = limit; + return oldLimit; + } + + /// + /// Resets the current size counter to zero (see ). + /// + public void ResetSizeCounter() + { + totalBytesRetired = 0; + } + #endregion + #region Validation /// /// Verifies that the last call to ReadTag() returned the given tag value. /// This is used to verify that a nested group ended with the correct @@ -194,13 +246,12 @@ namespace Google.Protobuf throw InvalidProtocolBufferException.InvalidEndTag(); } } - #endregion #region Reading of tags etc /// - /// Attempt to peek at the next field tag. + /// Attempts to peek at the next field tag. /// public bool PeekNextTag(out uint fieldTag) { @@ -218,7 +269,7 @@ namespace Google.Protobuf } /// - /// Attempt to read a field tag, returning false if we have reached the end + /// Attempts to read a field tag, returning false if we have reached the end /// of the input data. /// /// The 'tag' of the field (id * 8 + wire-format) @@ -233,14 +284,42 @@ namespace Google.Protobuf return true; } - if (IsAtEnd) + // Optimize for the incredibly common case of having at least two bytes left in the buffer, + // and those two bytes being enough to get the tag. This will be true for fields up to 4095. + if (bufferPos + 2 <= bufferSize) { - fieldTag = 0; - lastTag = fieldTag; - return false; + int tmp = buffer[bufferPos++]; + if (tmp < 128) + { + fieldTag = (uint)tmp; + } + else + { + int result = tmp & 0x7f; + if ((tmp = buffer[bufferPos++]) < 128) + { + result |= tmp << 7; + fieldTag = (uint) result; + } + else + { + // Nope, rewind and go the potentially slow route. + bufferPos -= 2; + fieldTag = ReadRawVarint32(); + } + } } + else + { + if (IsAtEnd) + { + fieldTag = 0; + lastTag = fieldTag; + return false; + } - fieldTag = ReadRawVarint32(); + fieldTag = ReadRawVarint32(); + } lastTag = fieldTag; if (lastTag == 0) { @@ -251,7 +330,7 @@ namespace Google.Protobuf } /// - /// Read a double field from the stream. + /// Reads a double field from the stream. /// public double ReadDouble() { @@ -259,7 +338,7 @@ namespace Google.Protobuf } /// - /// Read a float field from the stream. + /// Reads a float field from the stream. /// public float ReadFloat() { @@ -281,7 +360,7 @@ namespace Google.Protobuf } /// - /// Read a uint64 field from the stream. + /// Reads a uint64 field from the stream. /// public ulong ReadUInt64() { @@ -289,7 +368,7 @@ namespace Google.Protobuf } /// - /// Read an int64 field from the stream. + /// Reads an int64 field from the stream. /// public long ReadInt64() { @@ -297,7 +376,7 @@ namespace Google.Protobuf } /// - /// Read an int32 field from the stream. + /// Reads an int32 field from the stream. /// public int ReadInt32() { @@ -305,7 +384,7 @@ namespace Google.Protobuf } /// - /// Read a fixed64 field from the stream. + /// Reads a fixed64 field from the stream. /// public ulong ReadFixed64() { @@ -313,7 +392,7 @@ namespace Google.Protobuf } /// - /// Read a fixed32 field from the stream. + /// Reads a fixed32 field from the stream. /// public uint ReadFixed32() { @@ -321,7 +400,7 @@ namespace Google.Protobuf } /// - /// Read a bool field from the stream. + /// Reads a bool field from the stream. /// public bool ReadBool() { @@ -333,22 +412,22 @@ namespace Google.Protobuf /// public string ReadString() { - int size = (int) ReadRawVarint32(); + int length = ReadLength(); // No need to read any data for an empty string. - if (size == 0) + if (length == 0) { return ""; } - if (size <= bufferSize - bufferPos) + if (length <= bufferSize - bufferPos) { // Fast path: We already have the bytes in a contiguous buffer, so // just copy directly from it. - String result = CodedOutputStream.Utf8Encoding.GetString(buffer, bufferPos, size); - bufferPos += size; + String result = CodedOutputStream.Utf8Encoding.GetString(buffer, bufferPos, length); + bufferPos += length; return result; } // Slow path: Build a byte array first then copy it. - return CodedOutputStream.Utf8Encoding.GetString(ReadRawBytes(size), 0, size); + return CodedOutputStream.Utf8Encoding.GetString(ReadRawBytes(length), 0, length); } /// @@ -356,7 +435,7 @@ namespace Google.Protobuf /// public void ReadMessage(IMessage builder) { - int length = (int) ReadRawVarint32(); + int length = ReadLength(); if (recursionDepth >= recursionLimit) { throw InvalidProtocolBufferException.RecursionLimitExceeded(); @@ -374,19 +453,19 @@ namespace Google.Protobuf /// public ByteString ReadBytes() { - int size = (int) ReadRawVarint32(); - if (size <= bufferSize - bufferPos && size > 0) + int length = ReadLength(); + if (length <= bufferSize - bufferPos && length > 0) { // Fast path: We already have the bytes in a contiguous buffer, so // just copy directly from it. - ByteString result = ByteString.CopyFrom(buffer, bufferPos, size); - bufferPos += size; + ByteString result = ByteString.CopyFrom(buffer, bufferPos, length); + bufferPos += length; return result; } else { // Slow path: Build a byte array and attach it to a new ByteString. - return ByteString.AttachBytes(ReadRawBytes(size)); + return ByteString.AttachBytes(ReadRawBytes(length)); } } @@ -441,6 +520,18 @@ namespace Google.Protobuf return DecodeZigZag64(ReadRawVarint64()); } + /// + /// Reads a length for length-delimited data. + /// + /// + /// This is internally just reading a varint, but this method exists + /// to make the calling code clearer. + /// + public int ReadLength() + { + return (int) ReadRawVarint32(); + } + /// /// Peeks at the next tag in the stream. If it matches , /// the tag is consumed and the method returns true; otherwise, the @@ -517,12 +608,12 @@ namespace Google.Protobuf } /// - /// Read a raw Varint from the stream. If larger than 32 bits, discard the upper bits. + /// Reads a raw Varint from the stream. If larger than 32 bits, discard the upper bits. /// This method is optimised for the case where we've got lots of data in the buffer. /// That means we can check the size just once, then just read directly from the buffer /// without constant rechecking of the buffer length. /// - public uint ReadRawVarint32() + internal uint ReadRawVarint32() { if (bufferPos + 5 > bufferSize) { @@ -581,13 +672,13 @@ namespace Google.Protobuf /// /// Reads a varint from the input one byte at a time, so that it does not /// read any bytes after the end of the varint. If you simply wrapped the - /// stream in a CodedInputStream and used ReadRawVarint32(Stream)} + /// stream in a CodedInputStream and used ReadRawVarint32(Stream) /// then you would probably end up reading past the end of the varint since /// CodedInputStream buffers its input. /// /// /// - public static uint ReadRawVarint32(Stream input) + internal static uint ReadRawVarint32(Stream input) { int result = 0; int offset = 0; @@ -621,9 +712,9 @@ namespace Google.Protobuf } /// - /// Read a raw varint from the stream. + /// Reads a raw varint from the stream. /// - public ulong ReadRawVarint64() + internal ulong ReadRawVarint64() { int shift = 0; ulong result = 0; @@ -641,9 +732,9 @@ namespace Google.Protobuf } /// - /// Read a 32-bit little-endian integer from the stream. + /// Reads a 32-bit little-endian integer from the stream. /// - public uint ReadRawLittleEndian32() + internal uint ReadRawLittleEndian32() { uint b1 = ReadRawByte(); uint b2 = ReadRawByte(); @@ -653,9 +744,9 @@ namespace Google.Protobuf } /// - /// Read a 64-bit little-endian integer from the stream. + /// Reads a 64-bit little-endian integer from the stream. /// - public ulong ReadRawLittleEndian64() + internal ulong ReadRawLittleEndian64() { ulong b1 = ReadRawByte(); ulong b2 = ReadRawByte(); @@ -669,8 +760,6 @@ namespace Google.Protobuf | (b5 << 32) | (b6 << 40) | (b7 << 48) | (b8 << 56); } - #endregion - /// /// Decode a 32-bit value with ZigZag encoding. /// @@ -680,9 +769,9 @@ namespace Google.Protobuf /// sign-extended to 64 bits to be varint encoded, thus always taking /// 10 bytes on the wire.) /// - public static int DecodeZigZag32(uint n) + internal static int DecodeZigZag32(uint n) { - return (int) (n >> 1) ^ -(int) (n & 1); + return (int)(n >> 1) ^ -(int)(n & 1); } /// @@ -694,72 +783,21 @@ namespace Google.Protobuf /// sign-extended to 64 bits to be varint encoded, thus always taking /// 10 bytes on the wire.) /// - public static long DecodeZigZag64(ulong n) - { - return (long) (n >> 1) ^ -(long) (n & 1); - } - - /// - /// Set the maximum message recursion depth. - /// - /// - /// In order to prevent malicious - /// messages from causing stack overflows, CodedInputStream limits - /// how deeply messages may be nested. The default limit is 64. - /// - public int SetRecursionLimit(int limit) - { - if (limit < 0) - { - throw new ArgumentOutOfRangeException("Recursion limit cannot be negative: " + limit); - } - int oldLimit = recursionLimit; - recursionLimit = limit; - return oldLimit; - } - - /// - /// Set the maximum message size. - /// - /// - /// In order to prevent malicious messages from exhausting memory or - /// causing integer overflows, CodedInputStream limits how large a message may be. - /// The default limit is 64MB. You should set this limit as small - /// as you can without harming your app's functionality. Note that - /// size limits only apply when reading from an InputStream, not - /// when constructed around a raw byte array (nor with ByteString.NewCodedInput). - /// If you want to read several messages from a single CodedInputStream, you - /// can call ResetSizeCounter() after each message to avoid hitting the - /// size limit. - /// - public int SetSizeLimit(int limit) + internal static long DecodeZigZag64(ulong n) { - if (limit < 0) - { - throw new ArgumentOutOfRangeException("Size limit cannot be negative: " + limit); - } - int oldLimit = sizeLimit; - sizeLimit = limit; - return oldLimit; + return (long)(n >> 1) ^ -(long)(n & 1); } + #endregion #region Internal reading and buffer management - /// - /// Resets the current size counter to zero (see SetSizeLimit). - /// - public void ResetSizeCounter() - { - totalBytesRetired = 0; - } - /// /// Sets currentLimit to (current position) + byteLimit. This is called /// when descending into a length-delimited embedded message. The previous /// limit is returned. /// /// The old limit. - public int PushLimit(int byteLimit) + internal int PushLimit(int byteLimit) { if (byteLimit < 0) { @@ -797,7 +835,7 @@ namespace Google.Protobuf /// /// Discards the current limit, returning the previous limit. /// - public void PopLimit(int oldLimit) + internal void PopLimit(int oldLimit) { currentLimit = oldLimit; RecomputeBufferSizeAfterLimit(); @@ -807,7 +845,7 @@ namespace Google.Protobuf /// Returns whether or not all the data before the limit has been read. /// /// - public bool ReachedLimit + internal bool ReachedLimit { get { @@ -897,7 +935,7 @@ namespace Google.Protobuf /// /// the end of the stream or the current limit was reached /// - public byte ReadRawByte() + internal byte ReadRawByte() { if (bufferPos == bufferSize) { @@ -907,12 +945,12 @@ namespace Google.Protobuf } /// - /// Read a fixed size of bytes from the input. + /// Reads a fixed size of bytes from the input. /// /// /// the end of the stream or the current limit was reached /// - public byte[] ReadRawBytes(int size) + internal byte[] ReadRawBytes(int size) { if (size < 0) { @@ -921,7 +959,8 @@ namespace Google.Protobuf if (totalBytesRetired + bufferPos + size > currentLimit) { - // Read to the end of the stream anyway. + // Read to the end of the stream (up to the current limit) anyway. + // TODO(jonskeet): This is the only usage of SkipRawBytes. Do we really need to do it? SkipRawBytes(currentLimit - totalBytesRetired - bufferPos); // Then fail. throw InvalidProtocolBufferException.TruncatedMessage(); @@ -1025,63 +1064,12 @@ namespace Google.Protobuf } } - /// - /// Reads and discards a single field, given its tag value. - /// - /// false if the tag is an end-group tag, in which case - /// nothing is skipped. Otherwise, returns true. - public bool SkipField() - { - uint tag = lastTag; - switch (WireFormat.GetTagWireType(tag)) - { - case WireFormat.WireType.Varint: - ReadRawVarint64(); - return true; - case WireFormat.WireType.Fixed64: - ReadRawLittleEndian64(); - return true; - case WireFormat.WireType.LengthDelimited: - SkipRawBytes((int) ReadRawVarint32()); - return true; - case WireFormat.WireType.StartGroup: - SkipMessage(); - CheckLastTagWas( - WireFormat.MakeTag(WireFormat.GetTagFieldNumber(tag), - WireFormat.WireType.EndGroup)); - return true; - case WireFormat.WireType.EndGroup: - return false; - case WireFormat.WireType.Fixed32: - ReadRawLittleEndian32(); - return true; - default: - throw InvalidProtocolBufferException.InvalidWireType(); - } - } - - /// - /// Reads and discards an entire message. This will read either until EOF - /// or until an endgroup tag, whichever comes first. - /// - public void SkipMessage() - { - uint tag; - while (ReadTag(out tag)) - { - if (!SkipField()) - { - return; - } - } - } - /// /// Reads and discards bytes. /// /// the end of the stream /// or the current limit was reached - public void SkipRawBytes(int size) + private void SkipRawBytes(int size) { if (size < 0) { diff --git a/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs b/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs index ef1f4c0c79..82aba51b21 100644 --- a/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs +++ b/csharp/src/ProtocolBuffers/CodedOutputStream.ComputeSize.cs @@ -50,7 +50,7 @@ namespace Google.Protobuf private const int LittleEndian32Size = 4; /// - /// Compute the number of bytes that would be needed to encode a + /// Computes the number of bytes that would be needed to encode a /// double field, including the tag. /// public static int ComputeDoubleSize(double value) @@ -59,7 +59,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a + /// Computes the number of bytes that would be needed to encode a /// float field, including the tag. /// public static int ComputeFloatSize(float value) @@ -68,7 +68,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a + /// Computes the number of bytes that would be needed to encode a /// uint64 field, including the tag. /// public static int ComputeUInt64Size(ulong value) @@ -77,7 +77,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode an + /// Computes the number of bytes that would be needed to encode an /// int64 field, including the tag. /// public static int ComputeInt64Size(long value) @@ -86,7 +86,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode an + /// Computes the number of bytes that would be needed to encode an /// int32 field, including the tag. /// public static int ComputeInt32Size(int value) @@ -103,7 +103,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a + /// Computes the number of bytes that would be needed to encode a /// fixed64 field, including the tag. /// public static int ComputeFixed64Size(ulong value) @@ -112,7 +112,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a + /// Computes the number of bytes that would be needed to encode a /// fixed32 field, including the tag. /// public static int ComputeFixed32Size(uint value) @@ -121,7 +121,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a + /// Computes the number of bytes that would be needed to encode a /// bool field, including the tag. /// public static int ComputeBoolSize(bool value) @@ -130,7 +130,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a + /// Computes the number of bytes that would be needed to encode a /// string field, including the tag. /// public static int ComputeStringSize(String value) @@ -141,7 +141,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a + /// Computes the number of bytes that would be needed to encode a /// group field, including the tag. /// public static int ComputeGroupSize(IMessage value) @@ -150,7 +150,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode an + /// Computes the number of bytes that would be needed to encode an /// embedded message field, including the tag. /// public static int ComputeMessageSize(IMessage value) @@ -160,7 +160,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a + /// Computes the number of bytes that would be needed to encode a /// bytes field, including the tag. /// public static int ComputeBytesSize(ByteString value) @@ -170,7 +170,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a + /// Computes the number of bytes that would be needed to encode a /// uint32 field, including the tag. /// public static int ComputeUInt32Size(uint value) @@ -179,7 +179,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a + /// Computes the number of bytes that would be needed to encode a /// enum field, including the tag. The caller is responsible for /// converting the enum value to its numeric value. /// @@ -190,7 +190,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode an + /// Computes the number of bytes that would be needed to encode an /// sfixed32 field, including the tag. /// public static int ComputeSFixed32Size(int value) @@ -199,7 +199,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode an + /// Computes the number of bytes that would be needed to encode an /// sfixed64 field, including the tag. /// public static int ComputeSFixed64Size(long value) @@ -208,7 +208,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode an + /// Computes the number of bytes that would be needed to encode an /// sint32 field, including the tag. /// public static int ComputeSInt32Size(int value) @@ -217,7 +217,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode an + /// Computes the number of bytes that would be needed to encode an /// sint64 field, including the tag. /// public static int ComputeSInt64Size(long value) @@ -226,7 +226,16 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a varint. + /// Computes the number of bytes that would be needed to encode a length, + /// as written by . + /// + public static int ComputeLengthSize(int length) + { + return ComputeRawVarint32Size((uint) length); + } + + /// + /// Computes the number of bytes that would be needed to encode a varint. /// public static int ComputeRawVarint32Size(uint value) { @@ -250,7 +259,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a varint. + /// Computes the number of bytes that would be needed to encode a varint. /// public static int ComputeRawVarint64Size(ulong value) { @@ -294,7 +303,7 @@ namespace Google.Protobuf } /// - /// Compute the number of bytes that would be needed to encode a tag. + /// Computes the number of bytes that would be needed to encode a tag. /// public static int ComputeTagSize(int fieldNumber) { diff --git a/csharp/src/ProtocolBuffers/CodedOutputStream.cs b/csharp/src/ProtocolBuffers/CodedOutputStream.cs index 99a99ae2a0..161f48f443 100644 --- a/csharp/src/ProtocolBuffers/CodedOutputStream.cs +++ b/csharp/src/ProtocolBuffers/CodedOutputStream.cs @@ -37,7 +37,6 @@ using System; using System.IO; using System.Text; -using Google.Protobuf.Collections; namespace Google.Protobuf { @@ -141,11 +140,12 @@ namespace Google.Protobuf } } - #region Writing of values without tags + #region Writing of values (not including tags) /// - /// Writes a double field value, including tag, to the stream. + /// Writes a double field value, without a tag, to the stream. /// + /// The value to write public void WriteDouble(double value) { WriteRawLittleEndian64((ulong)BitConverter.DoubleToInt64Bits(value)); @@ -154,6 +154,7 @@ namespace Google.Protobuf /// /// Writes a float field value, without a tag, to the stream. /// + /// The value to write public void WriteFloat(float value) { byte[] rawBytes = BitConverter.GetBytes(value); @@ -178,6 +179,7 @@ namespace Google.Protobuf /// /// Writes a uint64 field value, without a tag, to the stream. /// + /// The value to write public void WriteUInt64(ulong value) { WriteRawVarint64(value); @@ -186,6 +188,7 @@ namespace Google.Protobuf /// /// Writes an int64 field value, without a tag, to the stream. /// + /// The value to write public void WriteInt64(long value) { WriteRawVarint64((ulong) value); @@ -194,6 +197,7 @@ namespace Google.Protobuf /// /// Writes an int32 field value, without a tag, to the stream. /// + /// The value to write public void WriteInt32(int value) { if (value >= 0) @@ -210,6 +214,7 @@ namespace Google.Protobuf /// /// Writes a fixed64 field value, without a tag, to the stream. /// + /// The value to write public void WriteFixed64(ulong value) { WriteRawLittleEndian64(value); @@ -218,6 +223,7 @@ namespace Google.Protobuf /// /// Writes a fixed32 field value, without a tag, to the stream. /// + /// The value to write public void WriteFixed32(uint value) { WriteRawLittleEndian32(value); @@ -226,6 +232,7 @@ namespace Google.Protobuf /// /// Writes a bool field value, without a tag, to the stream. /// + /// The value to write public void WriteBool(bool value) { WriteRawByte(value ? (byte) 1 : (byte) 0); @@ -233,13 +240,15 @@ namespace Google.Protobuf /// /// Writes a string field value, without a tag, to the stream. + /// The data is length-prefixed. /// + /// The value to write public void WriteString(string value) { // Optimise the case where we have enough space to write // the string directly to the buffer, which should be common. int length = Utf8Encoding.GetByteCount(value); - WriteRawVarint32((uint)length); + WriteLength(length); if (limit - position >= length) { if (length == value.Length) // Must be all ASCII... @@ -262,23 +271,41 @@ namespace Google.Protobuf } } + /// + /// Writes a message, without a tag, to the stream. + /// The data is length-prefixed. + /// + /// The value to write public void WriteMessage(IMessage value) { WriteRawVarint32((uint) value.CalculateSize()); value.WriteTo(this); } + /// + /// Write a byte string, without a tag, to the stream. + /// The data is length-prefixed. + /// + /// The value to write public void WriteBytes(ByteString value) { WriteRawVarint32((uint) value.Length); value.WriteRawBytesTo(this); } + /// + /// Writes a uint32 value, without a tag, to the stream. + /// + /// The value to write public void WriteUInt32(uint value) { WriteRawVarint32(value); } + /// + /// Writes an enum value, without a tag, to the stream. + /// + /// The value to write public void WriteEnum(int value) { WriteInt32(value); @@ -289,27 +316,53 @@ namespace Google.Protobuf WriteRawLittleEndian32((uint) value); } + /// + /// Writes an sfixed64 value, without a tag, to the stream. + /// + /// The value to write public void WriteSFixed64(long value) { WriteRawLittleEndian64((ulong) value); } + /// + /// Writes an sint32 value, without a tag, to the stream. + /// + /// The value to write public void WriteSInt32(int value) { WriteRawVarint32(EncodeZigZag32(value)); } + /// + /// Writes an sint64 value, without a tag, to the stream. + /// + /// The value to write public void WriteSInt64(long value) { WriteRawVarint64(EncodeZigZag64(value)); } + /// + /// Writes a length (in bytes) for length-delimited data. + /// + /// + /// This method simply writes a rawint, but exists for clarity in calling code. + /// + /// Length value, in bytes. + public void WriteLength(int length) + { + WriteRawVarint32((uint) length); + } + #endregion #region Raw tag writing /// /// Encodes and writes a tag. /// + /// The number of the field to write the tag for + /// The wire format type of the tag to write public void WriteTag(int fieldNumber, WireFormat.WireType type) { WriteRawVarint32(WireFormat.MakeTag(fieldNumber, type)); @@ -318,6 +371,7 @@ namespace Google.Protobuf /// /// Writes an already-encoded tag. /// + /// The encoded tag public void WriteTag(uint tag) { WriteRawVarint32(tag); @@ -326,6 +380,7 @@ namespace Google.Protobuf /// /// Writes the given single-byte tag directly to the stream. /// + /// The encoded tag public void WriteRawTag(byte b1) { WriteRawByte(b1); @@ -334,6 +389,8 @@ namespace Google.Protobuf /// /// Writes the given two-byte tag directly to the stream. /// + /// The first byte of the encoded tag + /// The second byte of the encoded tag public void WriteRawTag(byte b1, byte b2) { WriteRawByte(b1); @@ -343,6 +400,9 @@ namespace Google.Protobuf /// /// Writes the given three-byte tag directly to the stream. /// + /// The first byte of the encoded tag + /// The second byte of the encoded tag + /// The third byte of the encoded tag public void WriteRawTag(byte b1, byte b2, byte b3) { WriteRawByte(b1); @@ -353,6 +413,10 @@ namespace Google.Protobuf /// /// Writes the given four-byte tag directly to the stream. /// + /// The first byte of the encoded tag + /// The second byte of the encoded tag + /// The third byte of the encoded tag + /// The fourth byte of the encoded tag public void WriteRawTag(byte b1, byte b2, byte b3, byte b4) { WriteRawByte(b1); @@ -364,6 +428,11 @@ namespace Google.Protobuf /// /// Writes the given five-byte tag directly to the stream. /// + /// The first byte of the encoded tag + /// The second byte of the encoded tag + /// The third byte of the encoded tag + /// The fourth byte of the encoded tag + /// The fifth byte of the encoded tag public void WriteRawTag(byte b1, byte b2, byte b3, byte b4, byte b5) { WriteRawByte(b1); @@ -380,7 +449,7 @@ namespace Google.Protobuf /// there's enough buffer space left to whizz through without checking /// for each byte; otherwise, we resort to calling WriteRawByte each time. /// - public void WriteRawVarint32(uint value) + internal void WriteRawVarint32(uint value) { // Optimize for the common case of a single byte value if (value < 128 && position < limit) @@ -409,7 +478,7 @@ namespace Google.Protobuf } } - public void WriteRawVarint64(ulong value) + internal void WriteRawVarint64(ulong value) { while (value > 127 && position < limit) { @@ -431,7 +500,7 @@ namespace Google.Protobuf } } - public void WriteRawLittleEndian32(uint value) + internal void WriteRawLittleEndian32(uint value) { if (position + 4 > limit) { @@ -449,7 +518,7 @@ namespace Google.Protobuf } } - public void WriteRawLittleEndian64(ulong value) + internal void WriteRawLittleEndian64(ulong value) { if (position + 8 > limit) { @@ -475,7 +544,7 @@ namespace Google.Protobuf } } - public void WriteRawByte(byte value) + internal void WriteRawByte(byte value) { if (position == limit) { @@ -485,7 +554,7 @@ namespace Google.Protobuf buffer[position++] = value; } - public void WriteRawByte(uint value) + internal void WriteRawByte(uint value) { WriteRawByte((byte) value); } @@ -493,7 +562,7 @@ namespace Google.Protobuf /// /// Writes out an array of bytes. /// - public void WriteRawBytes(byte[] value) + internal void WriteRawBytes(byte[] value) { WriteRawBytes(value, 0, value.Length); } @@ -501,7 +570,7 @@ namespace Google.Protobuf /// /// Writes out part of an array of bytes. /// - public void WriteRawBytes(byte[] value, int offset, int length) + internal void WriteRawBytes(byte[] value, int offset, int length) { if (limit - position >= length) { @@ -548,7 +617,7 @@ namespace Google.Protobuf /// sign-extended to 64 bits to be varint encoded, thus always taking /// 10 bytes on the wire.) /// - public static uint EncodeZigZag32(int n) + internal static uint EncodeZigZag32(int n) { // Note: the right-shift must be arithmetic return (uint) ((n << 1) ^ (n >> 31)); @@ -563,7 +632,7 @@ namespace Google.Protobuf /// sign-extended to 64 bits to be varint encoded, thus always taking /// 10 bytes on the wire.) /// - public static ulong EncodeZigZag64(long n) + internal static ulong EncodeZigZag64(long n) { return (ulong) ((n << 1) ^ (n >> 63)); } diff --git a/csharp/src/ProtocolBuffers/Collections/ReadOnlyDictionary.cs b/csharp/src/ProtocolBuffers/Collections/ReadOnlyDictionary.cs index 031ebd0268..cf3ff83c26 100644 --- a/csharp/src/ProtocolBuffers/Collections/ReadOnlyDictionary.cs +++ b/csharp/src/ProtocolBuffers/Collections/ReadOnlyDictionary.cs @@ -38,7 +38,7 @@ namespace Google.Protobuf.Collections /// /// Read-only wrapper around another dictionary. /// - public sealed class ReadOnlyDictionary : IDictionary + internal sealed class ReadOnlyDictionary : IDictionary { private readonly IDictionary wrapped; diff --git a/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs b/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs index 588f66a4f9..0d82e3bc69 100644 --- a/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs +++ b/csharp/src/ProtocolBuffers/Collections/RepeatedField.cs @@ -51,12 +51,14 @@ namespace Google.Protobuf.Collections public void AddEntriesFrom(CodedInputStream input, FieldCodec codec) { + // TODO: Inline some of the Add code, so we can avoid checking the size on every + // iteration and the mutability. uint tag = input.LastTag; var reader = codec.ValueReader; // Value types can be packed or not. if (typeof(T).IsValueType && WireFormat.GetTagWireType(tag) == WireFormat.WireType.LengthDelimited) { - int length = (int)(input.ReadRawVarint32() & int.MaxValue); + int length = input.ReadLength(); if (length > 0) { int oldLimit = input.PushLimit(length); @@ -125,7 +127,6 @@ namespace Google.Protobuf.Collections public void WriteTo(CodedOutputStream output, FieldCodec codec) { - // TODO: Assert that T is a value type, and that codec.Tag is packed? if (count == 0) { return; @@ -172,9 +173,9 @@ namespace Google.Protobuf.Collections private void EnsureSize(int size) { - size = Math.Max(size, MinArraySize); if (array.Length < size) { + size = Math.Max(size, MinArraySize); int newSize = Math.Max(array.Length * 2, size); var tmp = new T[newSize]; Array.Copy(array, 0, tmp, 0, array.Length); diff --git a/csharp/src/ProtocolBuffers/FieldCodec.cs b/csharp/src/ProtocolBuffers/FieldCodec.cs index d3fc2f71bd..f075dbbfad 100644 --- a/csharp/src/ProtocolBuffers/FieldCodec.cs +++ b/csharp/src/ProtocolBuffers/FieldCodec.cs @@ -8,6 +8,7 @@ namespace Google.Protobuf /// public static class FieldCodec { + // TODO: Avoid the "dual hit" of lambda expressions: create open delegates instead. (At least test...) public static FieldCodec ForString(uint tag) { return new FieldCodec(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag); @@ -84,7 +85,7 @@ namespace Google.Protobuf } // Enums are tricky. We can probably use expression trees to build these delegates automatically, - // but it's easy to generate the code fdor it. + // but it's easy to generate the code for it. public static FieldCodec ForEnum(uint tag, Func toInt32, Func fromInt32) { return new FieldCodec(input => fromInt32( diff --git a/csharp/src/ProtocolBuffers/MessageExtensions.cs b/csharp/src/ProtocolBuffers/MessageExtensions.cs index 57cecfd400..253c18ae9b 100644 --- a/csharp/src/ProtocolBuffers/MessageExtensions.cs +++ b/csharp/src/ProtocolBuffers/MessageExtensions.cs @@ -38,7 +38,7 @@ namespace Google.Protobuf { ThrowHelper.ThrowIfNull(message, "message"); ThrowHelper.ThrowIfNull(input, "input"); - int size = (int)CodedInputStream.ReadRawVarint32(input); + int size = (int) CodedInputStream.ReadRawVarint32(input); Stream limitedStream = new LimitedInputStream(input, size); message.MergeFrom(limitedStream); } diff --git a/csharp/src/ProtocolBuffers/Properties/AssemblyInfo.cs b/csharp/src/ProtocolBuffers/Properties/AssemblyInfo.cs index 806bd5d5df..27ccddbcc0 100644 --- a/csharp/src/ProtocolBuffers/Properties/AssemblyInfo.cs +++ b/csharp/src/ProtocolBuffers/Properties/AssemblyInfo.cs @@ -61,6 +61,13 @@ using System.Security; [assembly: AssemblyVersion("2.4.1.555")] +[assembly: InternalsVisibleTo("Google.Protobuf.Test, PublicKey=" + + "00240000048000009400000006020000002400005253413100040000110000003b4611704c5379" + + "39c3e0fbe9447dd6fa5462507f9dd4fd9fbf0712457e415b037da6d2c4eb5d2c7d29c86380af68" + + "7cf400401bb183f2a70bd3b631c1fcb7db8aa66c766694a9fb53fa765df6303104da8c978f3b6d" + + "53909cd30685b8bc9922c726cd82b5995e9e2cfca6df7a2d189d851492e49f4b76f269ce6dfd08" + + "c34a7d98")] + #if !NOFILEVERSION [assembly: AssemblyFileVersion("2.4.1.555")] #endif diff --git a/csharp/src/ProtocolBuffers/WireFormat.cs b/csharp/src/ProtocolBuffers/WireFormat.cs index 221ffef682..974665f170 100644 --- a/csharp/src/ProtocolBuffers/WireFormat.cs +++ b/csharp/src/ProtocolBuffers/WireFormat.cs @@ -53,13 +53,13 @@ namespace Google.Protobuf #region Fixed sizes. // TODO(jonskeet): Move these somewhere else. They're messy. Consider making FieldType a smarter kind of enum - public const int Fixed32Size = 4; - public const int Fixed64Size = 8; - public const int SFixed32Size = 4; - public const int SFixed64Size = 8; - public const int FloatSize = 4; - public const int DoubleSize = 8; - public const int BoolSize = 1; + internal const int Fixed32Size = 4; + internal const int Fixed64Size = 8; + internal const int SFixed32Size = 4; + internal const int SFixed64Size = 8; + internal const int FloatSize = 4; + internal const int DoubleSize = 8; + internal const int BoolSize = 1; #endregion @@ -72,22 +72,7 @@ namespace Google.Protobuf EndGroup = 4, Fixed32 = 5 } - - internal static class MessageSetField - { - internal const int Item = 1; - internal const int TypeID = 2; - internal const int Message = 3; - } - - internal static class MessageSetTag - { - internal static readonly uint ItemStart = MakeTag(MessageSetField.Item, WireType.StartGroup); - internal static readonly uint ItemEnd = MakeTag(MessageSetField.Item, WireType.EndGroup); - internal static readonly uint TypeID = MakeTag(MessageSetField.TypeID, WireType.Varint); - internal static readonly uint Message = MakeTag(MessageSetField.Message, WireType.LengthDelimited); - } - + private const int TagTypeBits = 3; private const uint TagTypeMask = (1 << TagTypeBits) - 1; @@ -120,7 +105,6 @@ namespace Google.Protobuf return (uint) (fieldNumber << TagTypeBits) | (uint) wireType; } -#if !LITE public static uint MakeTag(FieldDescriptor field) { return MakeTag(field.FieldNumber, GetWireType(field)); @@ -135,8 +119,6 @@ namespace Google.Protobuf return descriptor.IsPacked ? WireType.LengthDelimited : GetWireType(descriptor.FieldType); } -#endif - /// /// Converts a field type to its wire type. Done with a switch for the sake /// of speed - this is significantly faster than a dictionary lookup. @@ -177,7 +159,7 @@ namespace Google.Protobuf case FieldType.Enum: return WireType.Varint; default: - throw new ArgumentOutOfRangeException("No such field type"); + throw new ArgumentOutOfRangeException("fieldType", "No such field type"); } } }