diff --git a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs index fd2d52aa3f..e6a685d605 100644 --- a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs +++ b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs @@ -348,6 +348,56 @@ namespace Google.Protobuf Assert.AreEqual(message, parsed); } + [Test] + public void RoundTrip_Groups() + { + var message = new Proto2.TestAllTypes + { + OptionalGroup = new Proto2.TestAllTypes.Types.OptionalGroup + { + A = 10 + }, + RepeatedGroup = + { + new Proto2.TestAllTypes.Types.RepeatedGroup { A = 10 }, + new Proto2.TestAllTypes.Types.RepeatedGroup { A = 20 }, + new Proto2.TestAllTypes.Types.RepeatedGroup { A = 30 } + } + }; + + byte[] bytes = message.ToByteArray(); + Proto2.TestAllTypes parsed = Proto2.TestAllTypes.Parser.ParseFrom(bytes); + Assert.AreEqual(message, parsed); + } + + [Test] + public void RoundTrip_ExtensionGroups() + { + var message = new Proto2.TestAllExtensions(); + message.SetExtension(Proto2.UnittestExtensions.OptionalGroupExtension, new Proto2.OptionalGroup_extension { A = 10 }); + message.GetOrRegisterExtension(Proto2.UnittestExtensions.RepeatedGroupExtension).AddRange(new[] + { + new Proto2.RepeatedGroup_extension { A = 10 }, + new Proto2.RepeatedGroup_extension { A = 20 }, + new Proto2.RepeatedGroup_extension { A = 30 } + }); + + byte[] bytes = message.ToByteArray(); + Proto2.TestAllExtensions extendable_parsed = Proto2.TestAllExtensions.Parser.WithExtensionRegistry(new ExtensionRegistry() { Proto2.UnittestExtensions.OptionalGroupExtension, Proto2.UnittestExtensions.RepeatedGroupExtension }).ParseFrom(bytes); + Assert.AreEqual(message, extendable_parsed); + } + + [Test] + public void RoundTrip_NestedExtensionGroup() + { + var message = new Proto2.TestGroupExtension(); + message.SetExtension(Proto2.TestNestedExtension.Extensions.OptionalGroupExtension, new Proto2.TestNestedExtension.Types.OptionalGroup_extension { A = 10 }); + + byte[] bytes = message.ToByteArray(); + Proto2.TestGroupExtension extendable_parsed = Proto2.TestGroupExtension.Parser.WithExtensionRegistry(new ExtensionRegistry() { Proto2.TestNestedExtension.Extensions.OptionalGroupExtension }).ParseFrom(bytes); + Assert.AreEqual(message, extendable_parsed); + } + // Note that not every map within map_unittest_proto3 is used. They all go through very // similar code paths. The fact that all maps are present is validation that we have codecs // for every type. diff --git a/csharp/src/Google.Protobuf.Test/SampleMessages.cs b/csharp/src/Google.Protobuf.Test/SampleMessages.cs index 68f7e27db9..0c37814461 100644 --- a/csharp/src/Google.Protobuf.Test/SampleMessages.cs +++ b/csharp/src/Google.Protobuf.Test/SampleMessages.cs @@ -123,6 +123,7 @@ namespace Google.Protobuf OptionalString = "test", OptionalUint32 = UInt32.MaxValue, OptionalUint64 = UInt64.MaxValue, + OptionalGroup = new Proto2.TestAllTypes.Types.OptionalGroup { A = 10 }, RepeatedBool = { true, false }, RepeatedBytes = { ByteString.CopyFrom(1, 2, 3, 4), ByteString.CopyFrom(5, 6), ByteString.CopyFrom(new byte[1000]) }, RepeatedDouble = { -12.25, 23.5 }, @@ -144,6 +145,7 @@ namespace Google.Protobuf RepeatedString = { "foo", "bar" }, RepeatedUint32 = { UInt32.MaxValue, UInt32.MinValue }, RepeatedUint64 = { UInt64.MaxValue, UInt32.MinValue }, + RepeatedGroup = { new Proto2.TestAllTypes.Types.RepeatedGroup { A = 10 }, new Proto2.TestAllTypes.Types.RepeatedGroup { A = 20 } }, OneofString = "Oneof string" }; } diff --git a/csharp/src/Google.Protobuf.Test/TestProtos/Unittest.cs b/csharp/src/Google.Protobuf.Test/TestProtos/Unittest.cs index 13effe5e45..1043d738c8 100644 --- a/csharp/src/Google.Protobuf.Test/TestProtos/Unittest.cs +++ b/csharp/src/Google.Protobuf.Test/TestProtos/Unittest.cs @@ -5406,6 +5406,8 @@ namespace Google.Protobuf.TestProtos.Proto2 { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { + case 132: + return; default: _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); break; @@ -5550,6 +5552,8 @@ namespace Google.Protobuf.TestProtos.Proto2 { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { + case 372: + return; default: _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); break; @@ -6279,6 +6283,8 @@ namespace Google.Protobuf.TestProtos.Proto2 { uint tag; while ((tag = input.ReadTag()) != 0) { switch(tag) { + case 132: + return; default: _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); break; diff --git a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs index 827bc71183..d8095c810e 100644 --- a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs +++ b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs @@ -148,6 +148,10 @@ namespace Google.Protobuf.Collections { var sizeCalculator = codec.ValueSizeCalculator; int size = count * CodedOutputStream.ComputeRawVarint32Size(tag); + if (codec.EndTag != 0) + { + size += count * CodedOutputStream.ComputeRawVarint32Size(codec.EndTag); + } for (int i = 0; i < count; i++) { size += sizeCalculator(array[i]); diff --git a/csharp/src/Google.Protobuf/FieldCodec.cs b/csharp/src/Google.Protobuf/FieldCodec.cs index 90d31131fe..c2c4b57c39 100644 --- a/csharp/src/Google.Protobuf/FieldCodec.cs +++ b/csharp/src/Google.Protobuf/FieldCodec.cs @@ -731,6 +731,7 @@ namespace Google.Protobuf ValueSizeCalculator = sizeCalculator; FixedSize = 0; Tag = tag; + EndTag = endTag; DefaultValue = defaultValue; tagSize = CodedOutputStream.ComputeRawVarint32Size(tag); if (endTag != 0) diff --git a/src/google/protobuf/compiler/csharp/csharp_helpers.cc b/src/google/protobuf/compiler/csharp/csharp_helpers.cc index 4042512b4b..514eb9b0ba 100644 --- a/src/google/protobuf/compiler/csharp/csharp_helpers.cc +++ b/src/google/protobuf/compiler/csharp/csharp_helpers.cc @@ -284,16 +284,33 @@ std::string GetEnumValueName(const std::string& enum_name, const std::string& en uint GetGroupEndTag(const Descriptor* descriptor) { const Descriptor* containing_type = descriptor->containing_type(); - if (containing_type == NULL) { - return 0; - } - const FieldDescriptor* field; - for (int i = 0; i < containing_type->field_count(); i++) { - field = containing_type->field(i); - if (field->type() == FieldDescriptor::Type::TYPE_GROUP && field->message_type() == descriptor) { - return internal::WireFormatLite::MakeTag(field->number(), internal::WireFormatLite::WIRETYPE_END_GROUP); + if (containing_type != NULL) { + const FieldDescriptor* field; + for (int i = 0; i < containing_type->field_count(); i++) { + field = containing_type->field(i); + if (field->type() == FieldDescriptor::Type::TYPE_GROUP && field->message_type() == descriptor) { + return internal::WireFormatLite::MakeTag(field->number(), internal::WireFormatLite::WIRETYPE_END_GROUP); + } + } + for (int i = 0; i < containing_type->extension_count(); i++) { + field = containing_type->extension(i); + if (field->type() == FieldDescriptor::Type::TYPE_GROUP && field->message_type() == descriptor) { + return internal::WireFormatLite::MakeTag(field->number(), internal::WireFormatLite::WIRETYPE_END_GROUP); + } + } + } else { + const FileDescriptor* containing_file = descriptor->file(); + if (containing_file != NULL) { + const FieldDescriptor* field; + for (int i = 0; i < containing_file->extension_count(); i++) { + field = containing_file->extension(i); + if (field->type() == FieldDescriptor::Type::TYPE_GROUP && field->message_type() == descriptor) { + return internal::WireFormatLite::MakeTag(field->number(), internal::WireFormatLite::WIRETYPE_END_GROUP); + } + } } } + return 0; }