-
-
Notifications
You must be signed in to change notification settings - Fork 78
/
unknown_message.go
191 lines (167 loc) · 6.31 KB
/
unknown_message.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package proto_decoder
import (
"fmt"
"regexp"
"github.com/bradleyjkemp/grpc-tools/internal"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/empty"
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/desc/builder"
"github.com/jhump/protoreflect/dynamic"
"github.com/pkg/errors"
)
// When we don't have an actual proto message descriptor, this takes a best effort
// approach to generating one. It's definitely not perfect but is more useful than nothing.
type unknownFieldResolver struct{}
// This takes a message descriptor and enriches it to add any unknown fields present.
// This means that all unknown fields will show up in the dump.
func (u *unknownFieldResolver) enrichDecodeDescriptor(resolved *desc.MessageDescriptor, message *internal.Message) (*desc.MessageDescriptor, error) {
decoded := dynamic.NewMessage(resolved)
err := proto.Unmarshal(message.RawMessage, decoded)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal message")
}
descriptor, err := builder.FromMessage(resolved)
if err != nil {
return nil, errors.Wrap(err, "failed to create builder for message")
}
err = u.enrichMessage(descriptor, decoded)
if err != nil {
return nil, errors.Wrap(err, "failed to enrich decode descriptor")
}
decodeDescriptor, err := descriptor.Build()
if err != nil {
return nil, errors.Wrap(err, "failed to build enriched decode descriptor")
}
return decodeDescriptor, nil
}
func (u *unknownFieldResolver) enrichMessage(descriptor *builder.MessageBuilder, message *dynamic.Message) error {
if message == nil {
// TODO: I can't seem to reproduce this except in real usage.
// Putting a mutex in the dump interceptor also prevents this from occuring
// So it seems like a gnarly race condition
return errors.New("internal: got nil message")
}
for _, fieldNum := range message.GetUnknownFields() {
generatedFieldName := fmt.Sprintf("%s_%d", descriptor.GetName(), fieldNum)
unknownFieldContents := message.GetUnknownField(fieldNum)
fieldType, err := u.detectUnknownFieldType(descriptor.GetFile(), generatedFieldName, unknownFieldContents)
if err != nil {
return errors.Wrap(err, "failed to detect field type")
}
field := builder.NewField(generatedFieldName, fieldType)
if err := field.TrySetNumber(fieldNum); err != nil {
return errors.Wrap(err, "failed to set field number")
}
field.SetJsonName(fmt.Sprintf("%d", fieldNum))
if len(unknownFieldContents) > 1 {
field.SetRepeated()
}
if err = descriptor.TryAddField(field); err != nil {
return errors.Wrap(err, "failed to add field")
}
}
// recurse into the known fields to check for nested unknown fields
for _, fieldDescriptor := range message.GetKnownFields() {
if fieldDescriptor.GetMessageType() == nil {
// either this is a basic type
// or a map which is not yet supported
// TODO: support maps
continue
}
var nestedMessage proto.Message
switch field := message.GetField(fieldDescriptor).(type) {
case proto.Message:
nestedMessage = field
// Repeated field: fieldDescriptor.IsRepeated() == true
case []interface{}:
if len(field) == 0 {
// TODO: is this case possible?
// Field has no items to analyse
continue
}
var ok bool
// TODO: should iterate over all the repeated messages and merge the information
nestedMessage, ok = field[0].(proto.Message)
if !ok {
return fmt.Errorf("unknown: repeated field is not of type proto.Message")
}
default:
return fmt.Errorf("unknown nested field type %T", field)
}
nestedMessageDescriptor, err := builder.FromMessage(fieldDescriptor.GetMessageType())
if err != nil {
return errors.Wrap(err, "failed to create builder")
}
dynamicNestedMessage, err := dynamic.AsDynamicMessage(nestedMessage)
if err != nil {
return errors.Wrap(err, "failed to convert nested message to dynamic")
}
if dynamicNestedMessage == nil {
return nil
}
err = u.enrichMessage(nestedMessageDescriptor, dynamicNestedMessage)
if err != nil {
return errors.Wrapf(err, "failed to search nested field %s", fieldDescriptor.GetName())
}
fieldDescriptorBuilder, err := builder.FromField(fieldDescriptor)
if err != nil {
return errors.Wrapf(err, "failed to create builder for field %s", fieldDescriptor.GetName())
}
fieldDescriptorBuilder.SetType(builder.FieldTypeMessage(nestedMessageDescriptor))
descriptor.RemoveField(fieldDescriptor.GetName()).AddField(fieldDescriptorBuilder)
}
return nil
}
var (
asciiPattern = regexp.MustCompile(`^[ -~]*$`)
)
func (u *unknownFieldResolver) detectUnknownFieldType(file *builder.FileBuilder, fieldName string, fields []dynamic.UnknownField) (*builder.FieldType, error) {
field := fields[0]
switch field.Encoding {
// Used for: int32, int64, uint32, uint64, sint32, sint64, bool, enum
case proto.WireVarint:
return builder.FieldTypeInt64(), nil
// Used for: fixed32, sfixed32, float
case proto.WireFixed32:
return builder.FieldTypeFloat(), nil
// Used for: fixed64, sfixed64, double
case proto.WireFixed64:
return builder.FieldTypeDouble(), nil
// Used for: string, bytes, embedded messages, packed repeated fields
case proto.WireBytes:
if asciiPattern.Match(field.Contents) {
// highly unlikely that an entirely ASCII string is actually an embedded proto message
// TODO: make this heuristic cleverer
return builder.FieldTypeString(), nil
}
// embedded messages are encoded on the wire as strings
// so try to decode this string as a message
dyn, err := dynamic.AsDynamicMessage(&empty.Empty{})
if err != nil {
panic(err)
}
err = proto.Unmarshal(field.Contents, dyn)
if err != nil {
// looks like it wasn't a valid proto message
return builder.FieldTypeString(), nil
}
// TODO: check that the unmarshalled message doesn't have any illegal field numbers
// probably is an embedded message
descriptor := builder.NewMessage(fieldName)
err = u.enrichMessage(descriptor, dyn)
if err != nil {
return nil, errors.Wrapf(err, "failed to detect unknown field %s", fieldName)
}
if file != nil {
// TODO: why would file ever be nil?
err = file.TryAddMessage(descriptor)
if err != nil {
return nil, errors.Wrapf(err, "failed to add nested message %s", fieldName)
}
}
return builder.FieldTypeMessage(descriptor), nil
default:
return nil, errors.Errorf("Unsupported wire type id %v", field.Encoding)
}
}