encoding/prototext: add proto editions and fuzz tests

Change-Id: I2afc5ae83bf68600def3568e1d3ad51ef00e7671
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/566395
Auto-Submit: Lasse Folger <lassefolger@google.com>
Reviewed-by: Michael Stapelberg <stapelberg@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Lasse Folger 2024-02-23 13:03:15 +01:00
parent 2caa6b02a2
commit 055c812a4f
6 changed files with 1314 additions and 500 deletions

View File

@ -9,7 +9,7 @@ package goproto.protoc.import_public;
// Same Go package.
import public "cmd/protoc-gen-go/testdata/import_public/b.proto";
// Different Go package.
// Different Go package.
import public "cmd/protoc-gen-go/testdata/import_public/sub/a.proto";
option go_package = "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/import_public";

View File

@ -18,6 +18,7 @@ import (
weakpb "google.golang.org/protobuf/internal/testprotos/test/weak1"
pb2 "google.golang.org/protobuf/internal/testprotos/textpb2"
pb3 "google.golang.org/protobuf/internal/testprotos/textpb3"
pbeditions "google.golang.org/protobuf/internal/testprotos/textpbeditions"
"google.golang.org/protobuf/types/known/anypb"
)
@ -70,6 +71,62 @@ opt_string: ""
OptBytes: []byte{},
OptString: proto.String(""),
},
}, {
desc: "protoeditions explicit scalars set to zero values",
inputMessage: &pbeditions.Scalars{},
inputText: `opt_bool: false
opt_int32: 0
opt_int64: 0
opt_uint32: 0
opt_uint64: 0
opt_sint32: 0
opt_sint64: 0
opt_fixed32: 0
opt_fixed64: 0
opt_sfixed32: 0
opt_sfixed64: 0
opt_float: 0
opt_double: 0
opt_bytes: ""
opt_string: ""
`,
wantMessage: &pbeditions.Scalars{
OptBool: proto.Bool(false),
OptInt32: proto.Int32(0),
OptInt64: proto.Int64(0),
OptUint32: proto.Uint32(0),
OptUint64: proto.Uint64(0),
OptSint32: proto.Int32(0),
OptSint64: proto.Int64(0),
OptFixed32: proto.Uint32(0),
OptFixed64: proto.Uint64(0),
OptSfixed32: proto.Int32(0),
OptSfixed64: proto.Int64(0),
OptFloat: proto.Float32(0),
OptDouble: proto.Float64(0),
OptBytes: []byte{},
OptString: proto.String(""),
},
}, {
desc: "protoeditions implicit scalars set to zero values",
inputMessage: &pbeditions.ImplicitScalars{},
inputText: `s_bool: false
s_int32: 0
s_int64: 0
s_uint32: 0
s_uint64: 0
s_sint32: 0
s_sint64: 0
s_fixed32: 0
s_fixed64: 0
s_sfixed32: 0
s_sfixed64: 0
s_float: 0
s_double: 0
s_bytes: ""
s_string: ""
`,
wantMessage: &pbeditions.ImplicitScalars{},
}, {
desc: "proto3 scalars set to zero values",
inputMessage: &pb3.Scalars{},
@ -150,6 +207,38 @@ opt_string: "谷歌"
OptBytes: []byte("\xe8\xb0\xb7\xe6\xad\x8c"),
OptString: proto.String("谷歌"),
},
}, {
desc: "protoeditions explicit scalars",
inputMessage: &pbeditions.Scalars{},
inputText: `opt_bool: true
opt_int32: 255
opt_int64: 3735928559
opt_uint32: 0xff
opt_uint64: 0xdeadbeef
opt_sint32: -1001
opt_sint64: - 0xffff
opt_fixed64: 64
opt_sfixed32: - 32
opt_float: 1.234
opt_double: 1.23e+100
opt_bytes: "\xe8\xb0\xb7\xe6\xad\x8c"
opt_string: "谷歌"
`,
wantMessage: &pbeditions.Scalars{
OptBool: proto.Bool(true),
OptInt32: proto.Int32(0xff),
OptInt64: proto.Int64(0xdeadbeef),
OptUint32: proto.Uint32(0xff),
OptUint64: proto.Uint64(0xdeadbeef),
OptSint32: proto.Int32(-1001),
OptSint64: proto.Int64(-0xffff),
OptFixed64: proto.Uint64(64),
OptSfixed32: proto.Int32(-32),
OptFloat: proto.Float32(1.234),
OptDouble: proto.Float64(1.23e100),
OptBytes: []byte("\xe8\xb0\xb7\xe6\xad\x8c"),
OptString: proto.String("谷歌"),
},
}, {
desc: "case sensitive",
inputMessage: &pb3.Scalars{},
@ -195,6 +284,13 @@ s_string: "谷歌"
wantMessage: &pb2.Scalars{
OptString: proto.String("abc\xff"),
},
}, {
desc: "protoeditions unvalidated string with invalid UTF-8",
inputMessage: &pbeditions.Scalars{},
inputText: `opt_string: "abc\xff"`,
wantMessage: &pbeditions.Scalars{
OptString: proto.String("abc\xff"),
},
}, {
desc: "proto3 string with invalid UTF-8",
inputMessage: &pb3.Scalars{},
@ -215,6 +311,11 @@ s_string: "谷歌"
umo: prototext.UnmarshalOptions{DiscardUnknown: true},
inputMessage: &pb2.Scalars{},
inputText: `unknown_field:123 1000:"hello"`,
}, {
desc: "protoeditions message contains discarded unknown field",
umo: prototext.UnmarshalOptions{DiscardUnknown: true},
inputMessage: &pbeditions.Scalars{},
inputText: `unknown_field:123 1000:"hello"`,
}, {
desc: "proto3 message contains discarded unknown field",
umo: prototext.UnmarshalOptions{DiscardUnknown: true},
@ -408,6 +509,39 @@ opt_nested_enum: UNO
OptEnum: pb2.Enum_ONE.Enum(),
OptNestedEnum: pb2.Enums_UNO.Enum(),
},
}, {
desc: "protoeditions closed enum",
inputMessage: &pbeditions.Enums{},
inputText: `
opt_enum: ONE
opt_nested_enum: UNO
`,
wantMessage: &pbeditions.Enums{
OptEnum: pbeditions.Enum_ONE.Enum(),
OptNestedEnum: pbeditions.Enums_UNO.Enum(),
},
}, {
desc: "protoeditions open enum",
inputMessage: &pbeditions.Enums{},
inputText: `
implicit_enum: EINS
implicit_nested_enum: ZEHN
`,
wantMessage: &pbeditions.Enums{
ImplicitEnum: pbeditions.OpenEnum_EINS,
ImplicitNestedEnum: pbeditions.Enums_ZEHN,
},
}, {
desc: "protoeditions enum numeric value",
inputMessage: &pbeditions.Enums{},
inputText: `
implicit_enum: 1
implicit_nested_enum: 10
`,
wantMessage: &pbeditions.Enums{
ImplicitEnum: pbeditions.OpenEnum_EINS,
ImplicitNestedEnum: pbeditions.Enums_ZEHN,
},
}, {
desc: "proto2 enum set to numeric values",
inputMessage: &pb2.Enums{},
@ -482,6 +616,17 @@ OptGroup: {}
OptNested: &pb2.Nested{},
Optgroup: &pb2.Nests_OptGroup{},
},
}, {
desc: "protoeditions nested empty messages",
inputMessage: &pbeditions.Nests{},
inputText: `
opt_nested: {}
OptGroup: {}
`,
wantMessage: &pbeditions.Nests{
OptNested: &pbeditions.Nested{},
Optgroup: &pbeditions.Nests_OptGroup{},
},
}, {
desc: "message fields with no field separator",
inputMessage: &pb2.Nests{},
@ -493,11 +638,27 @@ OptGroup {}
OptNested: &pb2.Nested{},
Optgroup: &pb2.Nests_OptGroup{},
},
}, {
desc: "message fields with no field separator",
inputMessage: &pbeditions.Nests{},
inputText: `
opt_nested {}
OptGroup {}
`,
wantMessage: &pbeditions.Nests{
OptNested: &pbeditions.Nested{},
Optgroup: &pbeditions.Nests_OptGroup{},
},
}, {
desc: "group field name",
inputMessage: &pb2.Nests{},
inputText: `optgroup: {}`,
wantErr: "unknown field: optgroup",
}, {
desc: "delimited encoded message field name",
inputMessage: &pbeditions.Nests{},
inputText: `optgroup: {}`,
wantErr: "unknown field: optgroup",
}, {
desc: "proto2 nested messages",
inputMessage: &pb2.Nests{},
@ -517,6 +678,25 @@ opt_nested: {
},
},
},
}, {
desc: "protoeditions delimited encoded nested messages",
inputMessage: &pbeditions.Nests{},
inputText: `
opt_nested: {
opt_string: "nested message"
opt_nested: {
opt_string: "another nested message"
}
}
`,
wantMessage: &pbeditions.Nests{
OptNested: &pbeditions.Nested{
OptString: proto.String("nested message"),
OptNested: &pbeditions.Nested{
OptString: proto.String("another nested message"),
},
},
},
}, {
desc: "proto3 nested empty message",
inputMessage: &pb3.Nests{},
@ -711,6 +891,24 @@ RptGroup: {}
{},
},
},
}, {
desc: "repeated delimited encoded message fields",
inputMessage: &pbeditions.Nests{},
inputText: `
RptGroup: {
rpt_string: "hello"
rpt_string: "world"
}
RptGroup: {}
`,
wantMessage: &pbeditions.Nests{
Rptgroup: []*pbeditions.Nests_RptGroup{
{
RptString: []string{"hello", "world"},
},
{},
},
},
}, {
desc: "repeated message fields without field separator",
inputMessage: &pb2.Nests{},

View File

@ -20,6 +20,7 @@ import (
pb2 "google.golang.org/protobuf/internal/testprotos/textpb2"
pb3 "google.golang.org/protobuf/internal/testprotos/textpb3"
pbeditions "google.golang.org/protobuf/internal/testprotos/textpbeditions"
"google.golang.org/protobuf/types/known/anypb"
)
@ -40,6 +41,10 @@ func TestMarshal(t *testing.T) {
desc: "proto2 optional scalars not set",
input: &pb2.Scalars{},
want: "",
}, {
desc: "protoeditions optional scalars not set",
input: &pbeditions.Scalars{},
want: "",
}, {
desc: "proto3 scalars not set",
input: &pb3.Scalars{},
@ -48,6 +53,10 @@ func TestMarshal(t *testing.T) {
desc: "proto3 optional not set",
input: &pb3.Proto3Optional{},
want: "",
}, {
desc: "protoeditions implicit not set",
input: &pbeditions.ImplicitScalars{},
want: "",
}, {
desc: "proto2 optional scalars set to zero values",
input: &pb2.Scalars{
@ -82,6 +91,41 @@ opt_float: 0
opt_double: 0
opt_bytes: ""
opt_string: ""
`,
}, {
desc: "protoeditions optional scalars set to zero values",
input: &pbeditions.Scalars{
OptBool: proto.Bool(false),
OptInt32: proto.Int32(0),
OptInt64: proto.Int64(0),
OptUint32: proto.Uint32(0),
OptUint64: proto.Uint64(0),
OptSint32: proto.Int32(0),
OptSint64: proto.Int64(0),
OptFixed32: proto.Uint32(0),
OptFixed64: proto.Uint64(0),
OptSfixed32: proto.Int32(0),
OptSfixed64: proto.Int64(0),
OptFloat: proto.Float32(0),
OptDouble: proto.Float64(0),
OptBytes: []byte{},
OptString: proto.String(""),
},
want: `opt_bool: false
opt_int32: 0
opt_int64: 0
opt_uint32: 0
opt_uint64: 0
opt_sint32: 0
opt_sint64: 0
opt_fixed32: 0
opt_fixed64: 0
opt_sfixed32: 0
opt_sfixed64: 0
opt_float: 0
opt_double: 0
opt_bytes: ""
opt_string: ""
`,
}, {
desc: "proto3 optional set to zero values",
@ -130,6 +174,26 @@ opt_message: {}
SString: "",
},
want: "",
}, {
desc: "protoeditions implicit scalars set to zero values",
input: &pbeditions.ImplicitScalars{
SBool: false,
SInt32: 0,
SInt64: 0,
SUint32: 0,
SUint64: 0,
SSint32: 0,
SSint64: 0,
SFixed32: 0,
SFixed64: 0,
SSfixed32: 0,
SSfixed64: 0,
SFloat: 0,
SDouble: 0,
SBytes: []byte{},
SString: "",
},
want: "",
}, {
desc: "proto2 optional scalars set to some values",
input: &pb2.Scalars{
@ -160,6 +224,37 @@ opt_float: 1.02
opt_double: 1.0199999809265137
opt_bytes: "谷歌"
opt_string: "谷歌"
`,
}, {
desc: "proto editions optional scalars set to some values",
input: &pbeditions.Scalars{
OptBool: proto.Bool(true),
OptInt32: proto.Int32(0xff),
OptInt64: proto.Int64(0xdeadbeef),
OptUint32: proto.Uint32(47),
OptUint64: proto.Uint64(0xdeadbeef),
OptSint32: proto.Int32(-1001),
OptSint64: proto.Int64(-0xffff),
OptFixed64: proto.Uint64(64),
OptSfixed32: proto.Int32(-32),
OptFloat: proto.Float32(1.02),
OptDouble: proto.Float64(1.0199999809265137),
OptBytes: []byte("\xe8\xb0\xb7\xe6\xad\x8c"),
OptString: proto.String("谷歌"),
},
want: `opt_bool: true
opt_int32: 255
opt_int64: 3735928559
opt_uint32: 47
opt_uint64: 3735928559
opt_sint32: -1001
opt_sint64: -65535
opt_fixed64: 64
opt_sfixed32: -32
opt_float: 1.02
opt_double: 1.0199999809265137
opt_bytes: "谷歌"
opt_string: "谷歌"
`,
}, {
desc: "proto2 string with invalid UTF-8",
@ -167,6 +262,13 @@ opt_string: "谷歌"
OptString: proto.String("abc\xff"),
},
want: `opt_string: "abc\xff"
`,
}, {
desc: "protoeditions string with invalid UTF-8",
input: &pbeditions.Scalars{
OptString: proto.String("abc\xff"),
},
want: `opt_string: "abc\xff"
`,
}, {
desc: "proto3 string with invalid UTF-8",
@ -174,6 +276,12 @@ opt_string: "谷歌"
SString: "abc\xff",
},
wantErr: true,
}, {
desc: "protoeditions utf8 validated string with invalid UTF-8",
input: &pbeditions.UTF8Validated{
ValidatedString: "abc\xff",
},
wantErr: true,
}, {
desc: "float nan",
input: &pb3.Scalars{
@ -222,6 +330,15 @@ opt_string: "谷歌"
},
want: `opt_enum: 0
opt_nested_enum: 0
`,
}, {
desc: "protoeditions enum set to zero value",
input: &pbeditions.Enums{
OptEnum: pbeditions.Enum(0).Enum(),
OptNestedEnum: pbeditions.Enums_NestedEnum(0).Enum(),
},
want: `opt_enum: 0
opt_nested_enum: 0
`,
}, {
desc: "proto2 enum",
@ -231,6 +348,15 @@ opt_nested_enum: 0
},
want: `opt_enum: ONE
opt_nested_enum: UNO
`,
}, {
desc: "protoeditions enum",
input: &pbeditions.Enums{
OptEnum: pbeditions.Enum_ONE.Enum(),
OptNestedEnum: pbeditions.Enums_UNO.Enum(),
},
want: `opt_enum: ONE
opt_nested_enum: UNO
`,
}, {
desc: "proto2 enum set to numeric values",
@ -249,6 +375,15 @@ opt_nested_enum: DOS
},
want: `opt_enum: 101
opt_nested_enum: -101
`,
}, {
desc: "protoeditions enum set to unnamed numeric values",
input: &pbeditions.Enums{
OptEnum: pbeditions.Enum(101).Enum(),
OptNestedEnum: pbeditions.Enums_NestedEnum(-101).Enum(),
},
want: `opt_enum: 101
opt_nested_enum: -101
`,
}, {
desc: "proto3 enum not set",
@ -261,6 +396,21 @@ opt_nested_enum: -101
SNestedEnum: pb3.Enums_CERO,
},
want: "",
}, {
desc: "protoeditions implicit enum field set to default value",
input: &pbeditions.Enums{
ImplicitEnum: pbeditions.OpenEnum_UNKNOWN,
},
want: "",
}, {
desc: "protoeditions implicit enum field",
input: &pbeditions.Enums{
ImplicitEnum: pbeditions.OpenEnum_EINS,
ImplicitNestedEnum: pbeditions.Enums_ZEHN,
},
want: `implicit_enum: EINS
implicit_nested_enum: ZEHN
`,
}, {
desc: "proto3 enum",
input: &pb3.Enums{
@ -278,6 +428,15 @@ s_nested_enum: UNO
},
want: `s_enum: TWO
s_nested_enum: DOS
`,
}, {
desc: "protoeditions implicit enum set to unnamed numeric values",
input: &pbeditions.Enums{
ImplicitEnum: -47,
ImplicitNestedEnum: 47,
},
want: `implicit_enum: -47
implicit_nested_enum: 47
`,
}, {
desc: "proto3 enum set to unnamed numeric values",
@ -300,6 +459,15 @@ s_nested_enum: 47
},
want: `opt_nested: {}
OptGroup: {}
`,
}, {
desc: "protoeditions nested message set to empty",
input: &pbeditions.Nests{
OptNested: &pbeditions.Nested{},
Optgroup: &pbeditions.Nests_OptGroup{},
},
want: `opt_nested: {}
OptGroup: {}
`,
}, {
desc: "proto2 nested messages",
@ -317,6 +485,23 @@ OptGroup: {}
opt_string: "another nested message"
}
}
`,
}, {
desc: "protoeditions nested messages",
input: &pbeditions.Nests{
OptNested: &pbeditions.Nested{
OptString: proto.String("nested message"),
OptNested: &pbeditions.Nested{
OptString: proto.String("another nested message"),
},
},
},
want: `opt_nested: {
opt_string: "nested message"
opt_nested: {
opt_string: "another nested message"
}
}
`,
}, {
desc: "proto2 groups",
@ -340,6 +525,29 @@ OptGroup: {}
opt_fixed32: 47
}
}
`,
}, {
desc: "protoeditions delimited encoded meesage field",
input: &pbeditions.Nests{
Optgroup: &pbeditions.Nests_OptGroup{
OptString: proto.String("inside a group"),
OptNested: &pbeditions.Nested{
OptString: proto.String("nested message inside a group"),
},
Optnestedgroup: &pbeditions.Nests_OptGroup_OptNestedGroup{
OptFixed32: proto.Uint32(47),
},
},
},
want: `OptGroup: {
opt_string: "inside a group"
opt_nested: {
opt_string: "nested message inside a group"
}
OptNestedGroup: {
opt_fixed32: 47
}
}
`,
}, {
desc: "proto3 nested message not set",
@ -376,6 +584,14 @@ OptGroup: {}
},
},
wantErr: true,
}, {
desc: "protoeditions nested message contains invalid UTF-8",
input: &pbeditions.NestsUTF8Validated{
ValidatedMessage: &pbeditions.UTF8Validated{
ValidatedString: "abc\xff",
},
},
wantErr: true,
}, {
desc: "oneof not set",
input: &pb3.Oneofs{},
@ -1427,7 +1643,7 @@ value: "\x80"
t.Error("Marshal() got nil error, want error\n")
}
got := string(b)
if tt.want != "" && got != tt.want {
if got != tt.want {
t.Errorf("Marshal()\n<got>\n%v\n<want>\n%v\n", got, tt.want)
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("Marshal() diff -want +got\n%v\n", diff)

View File

@ -0,0 +1,98 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Go native fuzzing was added in go1.18. Remove this once we stop supporting
// go1.17.
//go:build go1.18
package prototext_test
import (
"math"
"testing"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/testing/protocmp"
testfuzzpb "google.golang.org/protobuf/internal/testprotos/editionsfuzztest"
)
// roundTripAndCompareProto tests if a prototext.Marshal/Unmarshal roundtrip
// preserves the contents of the message. Note: wireBytes are a protocol
// buffer wire format message, not the proto in text format. We do this because
// a random stream of bytes (e.g. generated by the fuzz engine) is more likely
// to be valid proto wire format than that it is valid text format.
func roundTripAndCompareProto(t *testing.T, wireBytes []byte, messages ...proto.Message) {
for _, msg := range messages {
src := msg.ProtoReflect().Type().New().Interface()
if err := proto.Unmarshal(wireBytes, src); err != nil {
// Ignoring invalid wire format since we want to test the prototext
// implementation, not the wireformat implementation.
return
}
// Unknown fields are not marshaled by prototext so we strip them.
src.ProtoReflect().SetUnknown(nil)
var ranger func(protoreflect.FieldDescriptor, protoreflect.Value) bool
stripUnknown := func(m protoreflect.Message) {
m.SetUnknown(nil)
m.Range(ranger)
}
ranger = func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
switch {
case fd.IsMap():
if fd.MapValue().Message() != nil {
v.Map().Range(func(_ protoreflect.MapKey, v protoreflect.Value) bool {
stripUnknown(v.Message())
return true
})
}
case fd.Message() != nil:
if fd.Cardinality() == protoreflect.Repeated {
l := v.List()
for i := 0; i < l.Len(); i++ {
stripUnknown(l.Get(i).Message())
}
} else {
stripUnknown(v.Message())
}
}
return true
}
stripUnknown(src.ProtoReflect())
textFormat, err := prototext.Marshal(src)
if err != nil {
t.Errorf("failed to marshal messsage to text format: %v\nmessage: %v", err, src)
}
dst := msg.ProtoReflect().Type().New().Interface()
if err := (prototext.Unmarshal(textFormat, dst)); err != nil {
t.Errorf("failed to unmarshal messsage from text format: %v\ntext format: %s", err, textFormat)
}
// The cmp package does not deal with NaN on its own and will report
// NaN != NaN.
optNaN64 := cmp.Comparer(func(x, y float32) bool {
return (math.IsNaN(float64(x)) && math.IsNaN(float64(y))) || x == y
})
optNaN32 := cmp.Comparer(func(x, y float64) bool {
return (math.IsNaN(x) && math.IsNaN(y)) || x == y
})
if diff := cmp.Diff(src, dst, protocmp.Transform(), optNaN64, optNaN32); diff != "" {
t.Error(diff)
}
}
}
func FuzzEncodeDecodeRoundTrip(f *testing.F) {
f.Add([]byte("Hello World!"))
f.Fuzz(func(t *testing.T, in []byte) {
roundTripAndCompareProto(t, in, (*testfuzzpb.TestAllTypesProto2)(nil), (*testfuzzpb.TestAllTypesProto2Editions)(nil), (*testfuzzpb.TestAllTypesProto3)(nil), (*testfuzzpb.TestAllTypesProto3Editions)(nil))
})
}

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@ import "google/protobuf/wrappers.proto";
option go_package = "google.golang.org/protobuf/internal/testprotos/textpbeditions";
option features.enum_type = CLOSED;
option features.utf8_validation = NONE;
// Scalars contains scalar fields.
message Scalars {
@ -74,18 +75,46 @@ enum Enum {
TEN = 10;
}
enum OpenEnum {
option features.enum_type = OPEN;
UNKNOWN = 0;
EINS = 1;
ZWEI = 2;
ZEHN = 10;
}
message UTF8Validated {
string validated_string = 1
[features.utf8_validation = VERIFY, features.field_presence = IMPLICIT];
}
message NestsUTF8Validated {
UTF8Validated validated_message = 1;
}
// Message contains enum fields.
message Enums {
Enum opt_enum = 1;
repeated Enum rpt_enum = 2;
OpenEnum implicit_enum = 5 [features.field_presence = IMPLICIT];
enum NestedEnum {
UNO = 1;
DOS = 2;
DIEZ = 10;
}
enum NestedOpenEnum {
option features.enum_type = OPEN;
UNKNOWN = 0;
EINS = 1;
ZWEI = 2;
ZEHN = 10;
}
NestedEnum opt_nested_enum = 3;
repeated NestedEnum rpt_nested_enum = 4;
NestedOpenEnum implicit_nested_enum = 6 [features.field_presence = IMPLICIT];
}
// Message contains repeated fields.