codec: implement protobuf unknown fields checker (#6557)
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package unknownproto_test
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec/unknownproto"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||
)
|
||||
|
||||
var n1BBlob []byte
|
||||
|
||||
func init() {
|
||||
n1B := &testdata.Nested1B{
|
||||
Id: 1,
|
||||
Age: 99,
|
||||
Nested: &testdata.Nested2B{
|
||||
Id: 2,
|
||||
Route: "Wintery route",
|
||||
Fee: 99,
|
||||
Nested: &testdata.Nested3B{
|
||||
Id: 3,
|
||||
Name: "3A this one that one there those oens",
|
||||
Age: 4588,
|
||||
B4: []*testdata.Nested4B{
|
||||
{
|
||||
Id: 4,
|
||||
Age: 88,
|
||||
Name: "Nested4B",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
n1BBlob, err = proto.Marshal(n1B)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRejectUnknownFields_serial(b *testing.B) {
|
||||
benchmarkRejectUnknownFields(b, false)
|
||||
}
|
||||
func BenchmarkRejectUnknownFields_parallel(b *testing.B) {
|
||||
benchmarkRejectUnknownFields(b, true)
|
||||
}
|
||||
|
||||
func benchmarkRejectUnknownFields(b *testing.B, parallel bool) {
|
||||
b.ReportAllocs()
|
||||
|
||||
if !parallel {
|
||||
ckr := new(unknownproto.Checker)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
n1A := new(testdata.Nested1A)
|
||||
if err := ckr.RejectUnknownFields(n1BBlob, n1A); err == nil {
|
||||
b.Fatal("expected an error")
|
||||
}
|
||||
b.SetBytes(int64(len(n1BBlob)))
|
||||
}
|
||||
} else {
|
||||
var mu sync.Mutex
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
ckr := new(unknownproto.Checker)
|
||||
for pb.Next() {
|
||||
// To simulate the conditions of multiple transactions being processed in parallel.
|
||||
n1A := new(testdata.Nested1A)
|
||||
if err := ckr.RejectUnknownFields(n1BBlob, n1A); err == nil {
|
||||
b.Fatal("expected an error")
|
||||
}
|
||||
mu.Lock()
|
||||
b.SetBytes(int64(len(n1BBlob)))
|
||||
mu.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkProtoUnmarshal_serial(b *testing.B) {
|
||||
benchmarkProtoUnmarshal(b, false)
|
||||
}
|
||||
func BenchmarkProtoUnmarshal_parallel(b *testing.B) {
|
||||
benchmarkProtoUnmarshal(b, true)
|
||||
}
|
||||
func benchmarkProtoUnmarshal(b *testing.B, parallel bool) {
|
||||
b.ReportAllocs()
|
||||
|
||||
if !parallel {
|
||||
for i := 0; i < b.N; i++ {
|
||||
n1A := new(testdata.Nested1A)
|
||||
if err := proto.Unmarshal(n1BBlob, n1A); err == nil {
|
||||
b.Fatal("expected an error")
|
||||
}
|
||||
b.SetBytes(int64(len(n1BBlob)))
|
||||
}
|
||||
} else {
|
||||
var mu sync.Mutex
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
n1A := new(testdata.Nested1A)
|
||||
if err := proto.Unmarshal(n1BBlob, n1A); err == nil {
|
||||
b.Fatal("expected an error")
|
||||
}
|
||||
mu.Lock()
|
||||
b.SetBytes(int64(len(n1BBlob)))
|
||||
mu.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
unknownproto implements functionality to "type check" protobuf serialized byte sequences
|
||||
against an expected proto.Message to report:
|
||||
|
||||
a) Unknown fields in the stream -- this is indicative of mismatched services, perhaps a malicious actor
|
||||
|
||||
b) Mismatched wire types for a field -- this is indicative of mismatched services
|
||||
|
||||
Its API signature is similar to proto.Unmarshal([]byte, proto.Message) as
|
||||
|
||||
ckr := new(unknownproto.Checker)
|
||||
if err := ckr.RejectUnknownFields(protoBlob, protoMessage); err != nil {
|
||||
// Handle the error.
|
||||
}
|
||||
|
||||
and ideally should be added before invoking proto.Unmarshal, if you'd like to enforce the features mentioned above.
|
||||
|
||||
By default, for security we report every single field that's unknown, whether a non-critical field or not. To customize
|
||||
this behavior, please create a Checker and set the AllowUnknownNonCriticals to true, for example:
|
||||
|
||||
ckr := &unknownproto.Checker{
|
||||
AllowUnknownNonCriticals: true,
|
||||
}
|
||||
if err := ckr.RejectUnknownFields(protoBlob, protoMessage); err != nil {
|
||||
// Handle the error.
|
||||
}
|
||||
*/
|
||||
package unknownproto
|
||||
@@ -0,0 +1,32 @@
|
||||
package unknownproto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/protobuf/encoding/protowire"
|
||||
)
|
||||
|
||||
func TestWireTypeToString(t *testing.T) {
|
||||
tests := []struct {
|
||||
typ protowire.Type
|
||||
want string
|
||||
}{
|
||||
{typ: 0, want: "varint"},
|
||||
{typ: 1, want: "fixed64"},
|
||||
{typ: 2, want: "bytes"},
|
||||
{typ: 3, want: "start_group"},
|
||||
{typ: 4, want: "end_group"},
|
||||
{typ: 5, want: "fixed32"},
|
||||
{typ: 95, want: "unknown type: 95"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(fmt.Sprintf("wireType=%d", tt.typ), func(t *testing.T) {
|
||||
if g, w := wireTypeToString(tt.typ), tt.want; g != w {
|
||||
t.Fatalf("Mismatch:\nGot: %q\nWant: %q\n", g, w)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
package unknownproto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/gogo/protobuf/protoc-gen-gogo/descriptor"
|
||||
"google.golang.org/protobuf/encoding/protowire"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
)
|
||||
|
||||
const bit11NonCritical = 1 << 10
|
||||
|
||||
type descriptorIface interface {
|
||||
Descriptor() ([]byte, []int)
|
||||
}
|
||||
|
||||
type Checker struct {
|
||||
// AllowUnknownNonCriticals when set will skip over non-critical fields that are unknown.
|
||||
AllowUnknownNonCriticals bool
|
||||
}
|
||||
|
||||
func (ckr *Checker) RejectUnknownFields(b []byte, msg proto.Message) error {
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
desc, ok := msg.(descriptorIface)
|
||||
if !ok {
|
||||
return fmt.Errorf("%T does not have a Descriptor() method", msg)
|
||||
}
|
||||
|
||||
fieldDescProtoFromTagNum, _, err := getDescriptorInfo(desc, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for len(b) > 0 {
|
||||
tagNum, wireType, n := protowire.ConsumeField(b)
|
||||
if n < 0 {
|
||||
return errors.New("invalid length")
|
||||
}
|
||||
|
||||
fieldDescProto, ok := fieldDescProtoFromTagNum[int32(tagNum)]
|
||||
switch {
|
||||
case ok:
|
||||
// Assert that the wireTypes match.
|
||||
if !canEncodeType(wireType, fieldDescProto.GetType()) {
|
||||
return &errMismatchedWireType{
|
||||
Type: reflect.ValueOf(msg).Type().String(),
|
||||
TagNum: tagNum,
|
||||
GotWireType: wireType,
|
||||
WantWireType: protowire.Type(fieldDescProto.WireType()),
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
if !ckr.AllowUnknownNonCriticals || tagNum&bit11NonCritical == 0 {
|
||||
// The tag is critical, so report it.
|
||||
return &errUnknownField{
|
||||
Type: reflect.ValueOf(msg).Type().String(),
|
||||
TagNum: tagNum,
|
||||
WireType: wireType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip over the 2 bytes that store fieldNumber and wireType bytes.
|
||||
fieldBytes := b[2:n]
|
||||
b = b[n:]
|
||||
|
||||
// An unknown but non-critical field or just a scalar type (aka *INT and BYTES like).
|
||||
if fieldDescProto == nil || fieldDescProto.IsScalar() {
|
||||
continue
|
||||
}
|
||||
|
||||
protoMessageName := fieldDescProto.GetTypeName()
|
||||
if protoMessageName == "" {
|
||||
switch typ := fieldDescProto.GetType(); typ {
|
||||
case descriptor.FieldDescriptorProto_TYPE_STRING, descriptor.FieldDescriptorProto_TYPE_BYTES:
|
||||
// At this point only TYPE_STRING is expected to be unregistered, since FieldDescriptorProto.IsScalar() returns false for
|
||||
// TYPE_BYTES and TYPE_STRING as per
|
||||
// https://github.com/gogo/protobuf/blob/5628607bb4c51c3157aacc3a50f0ab707582b805/protoc-gen-gogo/descriptor/descriptor.go#L95-L118
|
||||
default:
|
||||
return fmt.Errorf("failed to get typename for message of type %v, can only be TYPE_STRING or TYPE_BYTES", typ)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Let's recursively traverse and typecheck the field.
|
||||
|
||||
if protoMessageName == ".google.protobuf.Any" {
|
||||
// Firstly typecheck types.Any to ensure nothing snuck in.
|
||||
if err := ckr.RejectUnknownFields(fieldBytes, (*types.Any)(nil)); err != nil {
|
||||
return err
|
||||
}
|
||||
// And finally we can extract the TypeURL containing the protoMessageName.
|
||||
any := new(types.Any)
|
||||
if err := proto.Unmarshal(fieldBytes, any); err != nil {
|
||||
return err
|
||||
}
|
||||
protoMessageName = any.TypeUrl
|
||||
fieldBytes = any.Value
|
||||
}
|
||||
|
||||
msg, err := protoMessageForTypeName(protoMessageName[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ckr.RejectUnknownFields(fieldBytes, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var protoMessageForTypeNameMu sync.RWMutex
|
||||
var protoMessageForTypeNameCache = make(map[string]proto.Message)
|
||||
|
||||
// protoMessageForTypeName takes in a fully qualified name e.g. testdata.TestVersionFD1
|
||||
// and returns a corresponding empty protobuf message that serves the prototype for typechecking.
|
||||
func protoMessageForTypeName(protoMessageName string) (proto.Message, error) {
|
||||
protoMessageForTypeNameMu.RLock()
|
||||
msg, ok := protoMessageForTypeNameCache[protoMessageName]
|
||||
protoMessageForTypeNameMu.RUnlock()
|
||||
if ok {
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
concreteGoType := proto.MessageType(protoMessageName)
|
||||
if concreteGoType == nil {
|
||||
return nil, fmt.Errorf("failed to retrieve the message of type %q", protoMessageName)
|
||||
}
|
||||
|
||||
value := reflect.New(concreteGoType).Elem()
|
||||
msg, ok = value.Interface().(proto.Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%q does not implement proto.Message", protoMessageName)
|
||||
}
|
||||
|
||||
// Now cache it.
|
||||
protoMessageForTypeNameMu.Lock()
|
||||
protoMessageForTypeNameCache[protoMessageName] = msg
|
||||
protoMessageForTypeNameMu.Unlock()
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// checks is a mapping of protowire.Type to supported descriptor.FieldDescriptorProto_Type.
|
||||
// it is implemented this way so as to have constant time lookups and avoid the overhead
|
||||
// from O(n) walking of switch. The change to using this mapping boosts throughput by about 200%.
|
||||
var checks = [...]map[descriptor.FieldDescriptorProto_Type]bool{
|
||||
// "0 Varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum"
|
||||
0: {
|
||||
descriptor.FieldDescriptorProto_TYPE_INT32: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_INT64: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_UINT32: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_UINT64: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_SINT32: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_SINT64: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_BOOL: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_ENUM: true,
|
||||
},
|
||||
|
||||
// "1 64-bit: fixed64, sfixed64, double"
|
||||
1: {
|
||||
descriptor.FieldDescriptorProto_TYPE_FIXED64: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_SFIXED64: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_DOUBLE: true,
|
||||
},
|
||||
|
||||
// "2 Length-delimited: string, bytes, embedded messages, packed repeated fields"
|
||||
2: {
|
||||
descriptor.FieldDescriptorProto_TYPE_STRING: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_BYTES: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_MESSAGE: true,
|
||||
},
|
||||
|
||||
// "3 Start group: groups (deprecated)"
|
||||
3: {
|
||||
descriptor.FieldDescriptorProto_TYPE_GROUP: true,
|
||||
},
|
||||
|
||||
// "4 End group: groups (deprecated)"
|
||||
4: {
|
||||
descriptor.FieldDescriptorProto_TYPE_GROUP: true,
|
||||
},
|
||||
|
||||
// "5 32-bit: fixed32, sfixed32, float"
|
||||
5: {
|
||||
descriptor.FieldDescriptorProto_TYPE_FIXED32: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_SFIXED32: true,
|
||||
descriptor.FieldDescriptorProto_TYPE_FLOAT: true,
|
||||
},
|
||||
}
|
||||
|
||||
// canEncodeType returns true if the wireType is suitable for encoding the descriptor type.
|
||||
// See https://developers.google.com/protocol-buffers/docs/encoding#structure.
|
||||
func canEncodeType(wireType protowire.Type, descType descriptor.FieldDescriptorProto_Type) bool {
|
||||
if iwt := int(wireType); iwt < 0 || iwt >= len(checks) {
|
||||
return false
|
||||
}
|
||||
return checks[wireType][descType]
|
||||
}
|
||||
|
||||
// errMismatchedWireType describes a mismatch between
|
||||
// expected and got wireTypes for a specific tag number.
|
||||
type errMismatchedWireType struct {
|
||||
Type string
|
||||
GotWireType protowire.Type
|
||||
WantWireType protowire.Type
|
||||
TagNum protowire.Number
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (mwt *errMismatchedWireType) String() string {
|
||||
return fmt.Sprintf("Mismatched %q: {TagNum: %d, GotWireType: %q != WantWireType: %q}",
|
||||
mwt.Type, mwt.TagNum, wireTypeToString(mwt.GotWireType), wireTypeToString(mwt.WantWireType))
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (mwt *errMismatchedWireType) Error() string {
|
||||
return mwt.String()
|
||||
}
|
||||
|
||||
var _ error = (*errMismatchedWireType)(nil)
|
||||
|
||||
func wireTypeToString(wt protowire.Type) string {
|
||||
switch wt {
|
||||
case 0:
|
||||
return "varint"
|
||||
case 1:
|
||||
return "fixed64"
|
||||
case 2:
|
||||
return "bytes"
|
||||
case 3:
|
||||
return "start_group"
|
||||
case 4:
|
||||
return "end_group"
|
||||
case 5:
|
||||
return "fixed32"
|
||||
default:
|
||||
return fmt.Sprintf("unknown type: %d", wt)
|
||||
}
|
||||
}
|
||||
|
||||
// errUnknownField represents an error indicating that we encountered
|
||||
// a field that isn't available in the target proto.Message.
|
||||
type errUnknownField struct {
|
||||
Type string
|
||||
TagNum protowire.Number
|
||||
WireType protowire.Type
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (twt *errUnknownField) String() string {
|
||||
return fmt.Sprintf("errUnknownField %q: {TagNum: %d, WireType:%q}",
|
||||
twt.Type, twt.TagNum, wireTypeToString(twt.WireType))
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (twt *errUnknownField) Error() string {
|
||||
return twt.String()
|
||||
}
|
||||
|
||||
var _ error = (*errUnknownField)(nil)
|
||||
|
||||
var (
|
||||
protoFileToDesc = make(map[string]*descriptor.FileDescriptorProto)
|
||||
protoFileToDescMu sync.RWMutex
|
||||
)
|
||||
|
||||
func unnestDesc(mdescs []*descriptor.DescriptorProto, indices []int) *descriptor.DescriptorProto {
|
||||
mdesc := mdescs[indices[0]]
|
||||
for _, index := range indices[1:] {
|
||||
mdesc = mdesc.NestedType[index]
|
||||
}
|
||||
return mdesc
|
||||
}
|
||||
|
||||
// Invoking descriptor.ForMessage(proto.Message.(Descriptor).Descriptor()) is incredibly slow
|
||||
// for every single message, thus the need for a hand-rolled custom version that's performant and cacheable.
|
||||
func extractFileDescMessageDesc(desc descriptorIface) (*descriptor.FileDescriptorProto, *descriptor.DescriptorProto, error) {
|
||||
gzippedPb, indices := desc.Descriptor()
|
||||
|
||||
protoFileToDescMu.RLock()
|
||||
cached, ok := protoFileToDesc[string(gzippedPb)]
|
||||
protoFileToDescMu.RUnlock()
|
||||
|
||||
if ok {
|
||||
return cached, unnestDesc(cached.MessageType, indices), nil
|
||||
}
|
||||
|
||||
// Time to gunzip the content of the FileDescriptor and then proto unmarshal them.
|
||||
gzr, err := gzip.NewReader(bytes.NewReader(gzippedPb))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
protoBlob, err := ioutil.ReadAll(gzr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fdesc := new(descriptor.FileDescriptorProto)
|
||||
if err := proto.Unmarshal(protoBlob, fdesc); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Now cache the FileDescriptor.
|
||||
protoFileToDescMu.Lock()
|
||||
protoFileToDesc[string(gzippedPb)] = fdesc
|
||||
protoFileToDescMu.Unlock()
|
||||
|
||||
// Unnest the type if necessary.
|
||||
return fdesc, unnestDesc(fdesc.MessageType, indices), nil
|
||||
}
|
||||
|
||||
type descriptorMatch struct {
|
||||
cache map[int32]*descriptor.FieldDescriptorProto
|
||||
desc *descriptor.DescriptorProto
|
||||
}
|
||||
|
||||
var descprotoCacheMu sync.RWMutex
|
||||
var descprotoCache = make(map[reflect.Type]*descriptorMatch)
|
||||
|
||||
// getDescriptorInfo retrieves the mapping of field numbers to their respective field descriptors.
|
||||
func getDescriptorInfo(desc descriptorIface, msg proto.Message) (map[int32]*descriptor.FieldDescriptorProto, *descriptor.DescriptorProto, error) {
|
||||
key := reflect.ValueOf(msg).Type()
|
||||
|
||||
descprotoCacheMu.RLock()
|
||||
got, ok := descprotoCache[key]
|
||||
descprotoCacheMu.RUnlock()
|
||||
|
||||
if ok {
|
||||
return got.cache, got.desc, nil
|
||||
}
|
||||
|
||||
// Now compute and cache the index.
|
||||
_, md, err := extractFileDescMessageDesc(desc)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
tagNumToTypeIndex := make(map[int32]*descriptor.FieldDescriptorProto)
|
||||
for _, field := range md.Field {
|
||||
tagNumToTypeIndex[field.GetNumber()] = field
|
||||
}
|
||||
|
||||
descprotoCacheMu.Lock()
|
||||
descprotoCache[key] = &descriptorMatch{
|
||||
cache: tagNumToTypeIndex,
|
||||
desc: md,
|
||||
}
|
||||
descprotoCacheMu.Unlock()
|
||||
|
||||
return tagNumToTypeIndex, md, nil
|
||||
}
|
||||
@@ -0,0 +1,756 @@
|
||||
package unknownproto
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||
)
|
||||
|
||||
func TestRejectUnknownFieldsRepeated(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in proto.Message
|
||||
recv proto.Message
|
||||
wantErr error
|
||||
allowUnknownNonCriticals bool
|
||||
}{
|
||||
{
|
||||
name: "Unknown field in midst of repeated values",
|
||||
in: &testdata.TestVersion2{
|
||||
C: []*testdata.TestVersion2{
|
||||
{
|
||||
C: []*testdata.TestVersion2{
|
||||
{
|
||||
Sum: &testdata.TestVersion2_F{
|
||||
F: &testdata.TestVersion2{
|
||||
A: &testdata.TestVersion2{
|
||||
B: &testdata.TestVersion2{
|
||||
H: []*testdata.TestVersion2{
|
||||
{
|
||||
X: 0x01,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testdata.TestVersion2_F{
|
||||
F: &testdata.TestVersion2{
|
||||
A: &testdata.TestVersion2{
|
||||
B: &testdata.TestVersion2{
|
||||
H: []*testdata.TestVersion2{
|
||||
{
|
||||
X: 0x02,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testdata.TestVersion2_F{
|
||||
F: &testdata.TestVersion2{
|
||||
NewField: 411,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion1),
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.TestVersion1",
|
||||
TagNum: 25,
|
||||
WireType: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Unknown field in midst of repeated values, allowUnknownNonCriticals set",
|
||||
allowUnknownNonCriticals: true,
|
||||
in: &testdata.TestVersion2{
|
||||
C: []*testdata.TestVersion2{
|
||||
{
|
||||
C: []*testdata.TestVersion2{
|
||||
{
|
||||
Sum: &testdata.TestVersion2_F{
|
||||
F: &testdata.TestVersion2{
|
||||
A: &testdata.TestVersion2{
|
||||
B: &testdata.TestVersion2{
|
||||
H: []*testdata.TestVersion2{
|
||||
{
|
||||
X: 0x01,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testdata.TestVersion2_F{
|
||||
F: &testdata.TestVersion2{
|
||||
A: &testdata.TestVersion2{
|
||||
B: &testdata.TestVersion2{
|
||||
H: []*testdata.TestVersion2{
|
||||
{
|
||||
X: 0x02,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testdata.TestVersion2_F{
|
||||
F: &testdata.TestVersion2{
|
||||
NewField: 411,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion1),
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.TestVersion1",
|
||||
TagNum: 25,
|
||||
WireType: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Unknown field in midst of repeated values, non-critical field to be rejected",
|
||||
in: &testdata.TestVersion3{
|
||||
C: []*testdata.TestVersion3{
|
||||
{
|
||||
C: []*testdata.TestVersion3{
|
||||
{
|
||||
Sum: &testdata.TestVersion3_F{
|
||||
F: &testdata.TestVersion3{
|
||||
A: &testdata.TestVersion3{
|
||||
B: &testdata.TestVersion3{
|
||||
X: 0x01,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testdata.TestVersion3_F{
|
||||
F: &testdata.TestVersion3{
|
||||
A: &testdata.TestVersion3{
|
||||
B: &testdata.TestVersion3{
|
||||
X: 0x02,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testdata.TestVersion3_F{
|
||||
F: &testdata.TestVersion3{
|
||||
NonCriticalField: "non-critical",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion1),
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.TestVersion1",
|
||||
TagNum: 1031,
|
||||
WireType: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Unknown field in midst of repeated values, non-critical field ignored",
|
||||
allowUnknownNonCriticals: true,
|
||||
in: &testdata.TestVersion3{
|
||||
C: []*testdata.TestVersion3{
|
||||
{
|
||||
C: []*testdata.TestVersion3{
|
||||
{
|
||||
Sum: &testdata.TestVersion3_F{
|
||||
F: &testdata.TestVersion3{
|
||||
A: &testdata.TestVersion3{
|
||||
B: &testdata.TestVersion3{
|
||||
X: 0x01,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testdata.TestVersion3_F{
|
||||
F: &testdata.TestVersion3{
|
||||
A: &testdata.TestVersion3{
|
||||
B: &testdata.TestVersion3{
|
||||
X: 0x02,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sum: &testdata.TestVersion3_F{
|
||||
F: &testdata.TestVersion3{
|
||||
NonCriticalField: "non-critical",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion1),
|
||||
wantErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
protoBlob, err := proto.Marshal(tt.in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ckr := &Checker{AllowUnknownNonCriticals: tt.allowUnknownNonCriticals}
|
||||
gotErr := ckr.RejectUnknownFields(protoBlob, tt.recv)
|
||||
if !reflect.DeepEqual(gotErr, tt.wantErr) {
|
||||
t.Fatalf("Error mismatch\nGot:\n%v\n\nWant:\n%v", gotErr, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectUnknownFields_allowUnknownNonCriticals(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in proto.Message
|
||||
allowUnknownNonCriticals bool
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "Field that's in the reserved range, should fail by default",
|
||||
in: &testdata.Customer2{
|
||||
Id: 289,
|
||||
Reserved: 99,
|
||||
},
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.Customer1",
|
||||
TagNum: 1047,
|
||||
WireType: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Field that's in the reserved range, toggle allowUnknownNonCriticals",
|
||||
allowUnknownNonCriticals: true,
|
||||
in: &testdata.Customer2{
|
||||
Id: 289,
|
||||
Reserved: 99,
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Unkown fields that are critical, but with allowUnknownNonCriticals set",
|
||||
allowUnknownNonCriticals: true,
|
||||
in: &testdata.Customer2{
|
||||
Id: 289,
|
||||
City: testdata.Customer2_PaloAlto,
|
||||
},
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.Customer1",
|
||||
TagNum: 6,
|
||||
WireType: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
blob, err := proto.Marshal(tt.in)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal input: %v", err)
|
||||
}
|
||||
|
||||
ckr := &Checker{AllowUnknownNonCriticals: tt.allowUnknownNonCriticals}
|
||||
c1 := new(testdata.Customer1)
|
||||
gotErr := ckr.RejectUnknownFields(blob, c1)
|
||||
if !reflect.DeepEqual(gotErr, tt.wantErr) {
|
||||
t.Fatalf("Error mismatch\nGot:\n%s\n\nWant:\n%s", gotErr, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectUnknownFieldsNested(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in proto.Message
|
||||
recv proto.Message
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "TestVersion3 from TestVersionFD1",
|
||||
in: &testdata.TestVersion2{
|
||||
X: 5,
|
||||
Sum: &testdata.TestVersion2_E{
|
||||
E: 100,
|
||||
},
|
||||
H: []*testdata.TestVersion2{
|
||||
{X: 999},
|
||||
{X: -55},
|
||||
{
|
||||
X: 102,
|
||||
Sum: &testdata.TestVersion2_F{
|
||||
F: &testdata.TestVersion2{
|
||||
X: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Customer1: &testdata.Customer1{
|
||||
Id: 45,
|
||||
Name: "customer1",
|
||||
SubscriptionFee: 99,
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersionFD1),
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.TestVersionFD1",
|
||||
TagNum: 12,
|
||||
WireType: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Alternating oneofs",
|
||||
in: &testdata.TestVersion3{
|
||||
Sum: &testdata.TestVersion3_E{
|
||||
E: 99,
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion3LoneOneOfValue),
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Alternating oneofs mismatched field",
|
||||
in: &testdata.TestVersion3{
|
||||
Sum: &testdata.TestVersion3_F{
|
||||
F: &testdata.TestVersion3{
|
||||
X: 99,
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion3LoneOneOfValue),
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.TestVersion3LoneOneOfValue",
|
||||
TagNum: 7,
|
||||
WireType: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Discrepancy in a deeply nested one of field",
|
||||
in: &testdata.TestVersion3{
|
||||
Sum: &testdata.TestVersion3_F{
|
||||
F: &testdata.TestVersion3{
|
||||
Sum: &testdata.TestVersion3_F{
|
||||
F: &testdata.TestVersion3{
|
||||
X: 19,
|
||||
Sum: &testdata.TestVersion3_E{
|
||||
E: 99,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion3LoneNesting),
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.TestVersion3LoneNesting",
|
||||
TagNum: 6,
|
||||
WireType: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown field types.Any in G",
|
||||
in: &testdata.TestVersion3{
|
||||
G: &types.Any{
|
||||
TypeUrl: "/testdata.TestVersion1",
|
||||
Value: mustMarshal(&testdata.TestVersion2{
|
||||
Sum: &testdata.TestVersion2_F{
|
||||
F: &testdata.TestVersion2{
|
||||
NewField: 999,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion3),
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.TestVersion1",
|
||||
TagNum: 25,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "types.Any with extra fields",
|
||||
in: &testdata.TestVersionFD1WithExtraAny{
|
||||
G: &testdata.AnyWithExtra{
|
||||
Any: &types.Any{
|
||||
TypeUrl: "/testdata.TestVersion1",
|
||||
Value: mustMarshal(&testdata.TestVersion2{
|
||||
Sum: &testdata.TestVersion2_F{
|
||||
F: &testdata.TestVersion2{
|
||||
NewField: 999,
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
B: 3,
|
||||
C: 2,
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion3),
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*types.Any",
|
||||
TagNum: 3,
|
||||
WireType: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mismatched types.Any in G",
|
||||
in: &testdata.TestVersion1{
|
||||
G: &types.Any{
|
||||
TypeUrl: "/testdata.TestVersion4LoneNesting",
|
||||
Value: mustMarshal(&testdata.TestVersion3LoneNesting_Inner1{
|
||||
Inner: &testdata.TestVersion3LoneNesting_Inner1_InnerInner{
|
||||
Id: "ID",
|
||||
City: "Gotham",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion1),
|
||||
wantErr: &errMismatchedWireType{
|
||||
Type: "*testdata.TestVersion3",
|
||||
TagNum: 1,
|
||||
GotWireType: 2,
|
||||
WantWireType: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "From nested proto message, message index 0",
|
||||
in: &testdata.TestVersion3LoneNesting{
|
||||
Inner1: &testdata.TestVersion3LoneNesting_Inner1{
|
||||
Id: 10,
|
||||
Name: "foo",
|
||||
Inner: &testdata.TestVersion3LoneNesting_Inner1_InnerInner{
|
||||
Id: "ID",
|
||||
City: "Palo Alto",
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion4LoneNesting),
|
||||
wantErr: &errMismatchedWireType{
|
||||
Type: "*testdata.TestVersion4LoneNesting_Inner1_InnerInner",
|
||||
TagNum: 1,
|
||||
GotWireType: 2,
|
||||
WantWireType: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "From nested proto message, message index 1",
|
||||
in: &testdata.TestVersion3LoneNesting{
|
||||
Inner2: &testdata.TestVersion3LoneNesting_Inner2{
|
||||
Id: "ID",
|
||||
Country: "Maldives",
|
||||
Inner: &testdata.TestVersion3LoneNesting_Inner2_InnerInner{
|
||||
Id: "ID",
|
||||
City: "Unknown",
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion4LoneNesting),
|
||||
wantErr: &errMismatchedWireType{
|
||||
Type: "*testdata.TestVersion4LoneNesting_Inner2_InnerInner",
|
||||
TagNum: 2,
|
||||
GotWireType: 2,
|
||||
WantWireType: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
protoBlob, err := proto.Marshal(tt.in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ckr := new(Checker)
|
||||
gotErr := ckr.RejectUnknownFields(protoBlob, tt.recv)
|
||||
if !reflect.DeepEqual(gotErr, tt.wantErr) {
|
||||
t.Fatalf("Error mismatch\nGot:\n%s\n\nWant:\n%s", gotErr, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectUnknownFieldsFlat(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in proto.Message
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "Oneof with same field number, shouldn't complain",
|
||||
in: &testdata.Customer3{
|
||||
Id: 68,
|
||||
Name: "ACME3",
|
||||
Payment: &testdata.Customer3_CreditCardNo{
|
||||
CreditCardNo: "123-XXXX-XXX881",
|
||||
},
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "Oneof with different field number, should fail",
|
||||
in: &testdata.Customer3{
|
||||
Id: 68,
|
||||
Name: "ACME3",
|
||||
Payment: &testdata.Customer3_ChequeNo{
|
||||
ChequeNo: "123XXXXXXX881",
|
||||
},
|
||||
},
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.Customer1",
|
||||
TagNum: 8, WireType: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Any in a field, the extra field will be serialized so should fail",
|
||||
in: &testdata.Customer2{
|
||||
Miscellaneous: &types.Any{},
|
||||
},
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.Customer1",
|
||||
TagNum: 10,
|
||||
WireType: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With a nested struct as a field",
|
||||
in: &testdata.Customer3{
|
||||
Id: 289,
|
||||
Original: &testdata.Customer1{
|
||||
Id: 991,
|
||||
},
|
||||
},
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.Customer1",
|
||||
TagNum: 9,
|
||||
WireType: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "An extra field that's non-existent in Customer1",
|
||||
in: &testdata.Customer2{
|
||||
Id: 289,
|
||||
Name: "Customer1",
|
||||
Industry: 5299,
|
||||
Fewer: 199.9,
|
||||
},
|
||||
wantErr: &errMismatchedWireType{
|
||||
Type: "*testdata.Customer1",
|
||||
TagNum: 2, GotWireType: 0, WantWireType: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Using a field that's in the reserved range, should fail by default",
|
||||
in: &testdata.Customer2{
|
||||
Id: 289,
|
||||
Reserved: 99,
|
||||
},
|
||||
wantErr: &errUnknownField{
|
||||
Type: "*testdata.Customer1",
|
||||
TagNum: 1047,
|
||||
WireType: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Only fields matching",
|
||||
in: &testdata.Customer2{
|
||||
Id: 289,
|
||||
Name: "Customer1",
|
||||
},
|
||||
wantErr: &errMismatchedWireType{
|
||||
Type: "*testdata.Customer1",
|
||||
TagNum: 3, GotWireType: 2, WantWireType: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Extra field that's non-existent in Customer1, along with Reserved set",
|
||||
in: &testdata.Customer2{
|
||||
Id: 289,
|
||||
Name: "Customer1",
|
||||
Industry: 5299,
|
||||
Fewer: 199.9,
|
||||
Reserved: 819,
|
||||
},
|
||||
wantErr: &errMismatchedWireType{
|
||||
Type: "*testdata.Customer1",
|
||||
TagNum: 2, GotWireType: 0, WantWireType: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Using enumerated field",
|
||||
in: &testdata.Customer2{
|
||||
Id: 289,
|
||||
Name: "Customer1",
|
||||
Industry: 5299,
|
||||
City: testdata.Customer2_PaloAlto,
|
||||
},
|
||||
wantErr: &errMismatchedWireType{
|
||||
Type: "*testdata.Customer1",
|
||||
TagNum: 2,
|
||||
GotWireType: 0, WantWireType: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple extraneous fields",
|
||||
in: &testdata.Customer2{
|
||||
Id: 289,
|
||||
Name: "Customer1",
|
||||
Industry: 5299,
|
||||
City: testdata.Customer2_PaloAlto,
|
||||
Fewer: 45,
|
||||
},
|
||||
wantErr: &errMismatchedWireType{
|
||||
TagNum: 2, GotWireType: 0, WantWireType: 2,
|
||||
Type: "*testdata.Customer1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
blob, err := proto.Marshal(tt.in)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal input: %v", err)
|
||||
}
|
||||
|
||||
c1 := new(testdata.Customer1)
|
||||
ckr := new(Checker)
|
||||
gotErr := ckr.RejectUnknownFields(blob, c1)
|
||||
if !reflect.DeepEqual(gotErr, tt.wantErr) {
|
||||
t.Fatalf("Error mismatch\nGot:\n%s\n\nWant:\n%s", gotErr, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMismatchedTypes_Nested(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in proto.Message
|
||||
recv proto.Message
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "mismatched types.Any in G",
|
||||
in: &testdata.TestVersion1{
|
||||
G: &types.Any{
|
||||
TypeUrl: "/testdata.TestVersion4LoneNesting",
|
||||
Value: mustMarshal(&testdata.TestVersion3LoneNesting_Inner1{
|
||||
Inner: &testdata.TestVersion3LoneNesting_Inner1_InnerInner{
|
||||
Id: "ID",
|
||||
City: "Gotham",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion1),
|
||||
wantErr: &errMismatchedWireType{
|
||||
Type: "*testdata.TestVersion3",
|
||||
TagNum: 1,
|
||||
GotWireType: 2,
|
||||
WantWireType: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "From nested proto message, message index 0",
|
||||
in: &testdata.TestVersion3LoneNesting{
|
||||
Inner1: &testdata.TestVersion3LoneNesting_Inner1{
|
||||
Id: 10,
|
||||
Name: "foo",
|
||||
Inner: &testdata.TestVersion3LoneNesting_Inner1_InnerInner{
|
||||
Id: "ID",
|
||||
City: "Palo Alto",
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion4LoneNesting),
|
||||
wantErr: &errMismatchedWireType{
|
||||
Type: "*testdata.TestVersion4LoneNesting_Inner1_InnerInner",
|
||||
TagNum: 1,
|
||||
GotWireType: 2,
|
||||
WantWireType: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "From nested proto message, message index 1",
|
||||
in: &testdata.TestVersion3LoneNesting{
|
||||
Inner2: &testdata.TestVersion3LoneNesting_Inner2{
|
||||
Id: "ID",
|
||||
Country: "Maldives",
|
||||
Inner: &testdata.TestVersion3LoneNesting_Inner2_InnerInner{
|
||||
Id: "ID",
|
||||
City: "Unknown",
|
||||
},
|
||||
},
|
||||
},
|
||||
recv: new(testdata.TestVersion4LoneNesting),
|
||||
wantErr: &errMismatchedWireType{
|
||||
Type: "*testdata.TestVersion4LoneNesting_Inner2_InnerInner",
|
||||
TagNum: 2,
|
||||
GotWireType: 2,
|
||||
WantWireType: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
protoBlob, err := proto.Marshal(tt.in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ckr := new(Checker)
|
||||
gotErr := ckr.RejectUnknownFields(protoBlob, tt.recv)
|
||||
if !reflect.DeepEqual(gotErr, tt.wantErr) {
|
||||
t.Fatalf("Error mismatch\nGot:\n%s\n\nWant:\n%s", gotErr, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustMarshal(msg proto.Message) []byte {
|
||||
blob, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return blob
|
||||
}
|
||||
Reference in New Issue
Block a user