-
Notifications
You must be signed in to change notification settings - Fork 28
/
type_decoder.go
329 lines (310 loc) · 9.17 KB
/
type_decoder.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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vom
import (
"fmt"
"io"
"sync"
"v.io/v23/vdl"
)
// TypeDecoder manages the receipt and unmarshalling of types from the other
// side of a connection.
type TypeDecoder struct {
// The type encoder uses multiple locks for decoding.
// - typeMu to lock type definitions.
// - buildSyncMu is used to allonw only goroutine to build types
// at a time.
// - buildMu to protect shared datastructures.
//
// This is for simplifying the workflow and avoid unnecessary blocking
// for type lookups.
typeMu sync.RWMutex
idToType map[TypeId]*vdl.Type // GUARDED_BY(typeMu)
buildSyncMu sync.Mutex
buildMu sync.Mutex
err error // GUARDED_BY(buildSyncMu)
dec *decoder81 // GUARDED_BY(buildSyncMu)
idToWire map[TypeId]wireType // GUARDED_BY(builtMu)
}
// NewTypeDecoder returns a new TypeDecoder that reads from the given reader.
// The TypeDecoder understands all wire type formats generated by the TypeEncoder.
func NewTypeDecoder(r io.Reader) *TypeDecoder {
return newTypeDecoderInternal(&decoder81{buf: newDecbuf(r)})
}
func newTypeDecoderInternal(dec *decoder81) *TypeDecoder {
td := &TypeDecoder{
idToType: make(map[TypeId]*vdl.Type),
idToWire: make(map[TypeId]wireType),
dec: dec,
}
return td
}
func (td *TypeDecoder) reset(dec *decoder81, idToType map[TypeId]*vdl.Type,
idToWire map[TypeId]wireType) {
td.err = nil
td.dec = dec
td.idToType = idToType
td.idToWire = idToWire
}
func (td *TypeDecoder) readSingleTID(tid TypeId) (*vdl.Type, error) {
// Test to see if another goroutine has already found the
// requested type or encountered an error.
td.typeMu.RLock()
tt, ok := td.idToType[tid]
td.typeMu.RUnlock()
if ok {
return tt, nil
}
if td.err != nil {
return nil, td.err
}
td.err = td.readSingleType()
if td.err != nil && td.err != io.EOF {
return nil, td.err
}
// Was the requested type found.
td.typeMu.RLock()
tt, ok = td.idToType[tid]
td.typeMu.RUnlock()
if ok {
return tt, nil
}
if td.err == io.EOF {
td.err = fmt.Errorf("vom: failed to find type %v in type stream", tid)
}
// Type not found, only possibly error is eof.
return nil, td.err
}
// readSingleType reads a single wire type
func (td *TypeDecoder) readSingleType() error {
var wt wireType
curTypeID, err := td.dec.decodeWireType(&wt)
if err != nil {
return err
}
if err := td.addWireType(curTypeID, wt); err != nil {
return err
}
if !td.dec.flag.TypeIncomplete() {
if err := td.buildType(curTypeID); td.dec.buf.version >= Version81 && err != nil {
return err
}
}
return nil
}
// LookupType returns the type for tid. If the type is not yet available,
// this will wait until it arrives and is built.
func (td *TypeDecoder) lookupType(tid TypeId) (*vdl.Type, error) {
if tt := td.lookupKnownType(tid); tt != nil {
return tt, nil
}
// read from the type decoder's input stream to see if the
// requested type id is there.
for {
td.buildSyncMu.Lock()
tt, err := td.readSingleTID(tid)
td.buildSyncMu.Unlock()
if tt != nil {
return tt, nil
}
if err != nil {
return nil, err
}
}
}
// addWireType adds the wire type wt with the type id tid.
func (td *TypeDecoder) addWireType(tid TypeId, wt wireType) error {
td.buildMu.Lock()
defer td.buildMu.Unlock()
if tid < WireIdFirstUserType {
return fmt.Errorf("vom: type %v id %v invalid, the min user type id is %v", wt, tid, WireIdFirstUserType)
}
// TODO(toddw): Allow duplicates according to some heuristic (e.g. only
// identical, or only if the later one is a "superset", etc).
if dup := td.lookupKnownType(tid); dup != nil {
return fmt.Errorf("vom: type %v id %v already defined as %v", wt, tid, dup)
}
if dup := td.idToWire[tid]; dup != nil {
return fmt.Errorf("vom: type %v id %v already defined as %v", wt, tid, dup)
}
td.idToWire[tid] = wt
return nil
}
func (td *TypeDecoder) deleteWireType(tid TypeId) {
td.buildMu.Lock()
defer td.buildMu.Unlock()
delete(td.idToWire, tid)
}
func (td *TypeDecoder) lookupKnownType(tid TypeId) *vdl.Type {
// Non-bootstrap types are the common case so look them up first.
td.typeMu.RLock()
tt := td.idToType[tid]
td.typeMu.RUnlock()
if tt != nil {
return tt
}
return bootstrapIDToType[tid]
}
// buildType builds the type from the given wire type.
func (td *TypeDecoder) buildType(tid TypeId) error {
builder := vdl.TypeBuilder{}
pending := make(map[TypeId]vdl.PendingType)
_, err := td.makeType(tid, &builder, pending)
if err != nil {
return err
}
builder.Build()
types := make(map[TypeId]*vdl.Type)
for tid, pt := range pending {
tt, err := pt.Built()
if err != nil {
return err
}
types[tid] = tt
}
// Add the types to idToType map.
td.typeMu.Lock()
for tid, tt := range types {
td.deleteWireType(tid)
td.idToType[tid] = tt
}
td.typeMu.Unlock()
return nil
}
// makeType makes the pending type from its wire type representation.
func (td *TypeDecoder) makeType(tid TypeId, builder *vdl.TypeBuilder, pending map[TypeId]vdl.PendingType) (vdl.PendingType, error) {
td.buildMu.Lock()
wt := td.idToWire[tid]
td.buildMu.Unlock()
if wt == nil {
return nil, fmt.Errorf("vom: unknown type id %v", tid)
}
// Make the type from its wireType representation. Both named and unnamed
// types may be recursive, so we must populate pending before subsequent
// recursive lookups. Eventually the built type will be added to dt.idToType.
if name := wt.(wireTypeGeneric).TypeName(); name != "" {
namedType := builder.Named(name)
pending[tid] = namedType
if wtNamed, ok := wt.(wireTypeNamedT); ok {
// This is a wireNamed pointing at a base type.
baseType, err := td.lookupOrMakeType(wtNamed.Value.Base, builder, pending)
if err != nil {
return nil, err
}
namedType.AssignBase(baseType)
return namedType, nil
}
// This isn't wireNamed, but has a non-empty name.
baseType, err := td.startBaseType(wt, builder)
if err != nil {
return nil, err
}
if err := td.finishBaseType(wt, baseType, builder, pending); err != nil {
return nil, err
}
namedType.AssignBase(baseType)
return namedType, nil
}
// We make unnamed types in two stages, to ensure that we populate pending
// before any recursive lookups.
unnamedType, err := td.startBaseType(wt, builder)
if err != nil {
return nil, err
}
pending[tid] = unnamedType
if err := td.finishBaseType(wt, unnamedType, builder, pending); err != nil {
return nil, err
}
return unnamedType, nil
}
func (td *TypeDecoder) startBaseType(wt wireType, builder *vdl.TypeBuilder) (vdl.PendingType, error) {
switch wt := wt.(type) {
case wireTypeEnumT:
return builder.Enum(), nil
case wireTypeArrayT:
return builder.Array(), nil
case wireTypeListT:
return builder.List(), nil
case wireTypeSetT:
return builder.Set(), nil
case wireTypeMapT:
return builder.Map(), nil
case wireTypeStructT:
return builder.Struct(), nil
case wireTypeUnionT:
return builder.Union(), nil
case wireTypeOptionalT:
return builder.Optional(), nil
default:
return nil, fmt.Errorf("vom: unknown wire type definition %v", wt)
}
}
func (td *TypeDecoder) finishBaseType(wt wireType, p vdl.PendingType, builder *vdl.TypeBuilder, pending map[TypeId]vdl.PendingType) error { //nolint:gocyclo
switch wt := wt.(type) {
case wireTypeEnumT:
for _, label := range wt.Value.Labels {
p.(vdl.PendingEnum).AppendLabel(label)
}
case wireTypeArrayT:
elemType, err := td.lookupOrMakeType(wt.Value.Elem, builder, pending)
if err != nil {
return err
}
p.(vdl.PendingArray).AssignElem(elemType).AssignLen(int(wt.Value.Len))
case wireTypeListT:
elemType, err := td.lookupOrMakeType(wt.Value.Elem, builder, pending)
if err != nil {
return err
}
p.(vdl.PendingList).AssignElem(elemType)
case wireTypeSetT:
keyType, err := td.lookupOrMakeType(wt.Value.Key, builder, pending)
if err != nil {
return err
}
p.(vdl.PendingSet).AssignKey(keyType)
case wireTypeMapT:
keyType, err := td.lookupOrMakeType(wt.Value.Key, builder, pending)
if err != nil {
return err
}
elemType, err := td.lookupOrMakeType(wt.Value.Elem, builder, pending)
if err != nil {
return err
}
p.(vdl.PendingMap).AssignKey(keyType).AssignElem(elemType)
case wireTypeStructT:
for _, field := range wt.Value.Fields {
fieldType, err := td.lookupOrMakeType(field.Type, builder, pending)
if err != nil {
return err
}
p.(vdl.PendingStruct).AppendField(field.Name, fieldType)
}
case wireTypeUnionT:
for _, field := range wt.Value.Fields {
fieldType, err := td.lookupOrMakeType(field.Type, builder, pending)
if err != nil {
return err
}
p.(vdl.PendingUnion).AppendField(field.Name, fieldType)
}
case wireTypeOptionalT:
elemType, err := td.lookupOrMakeType(wt.Value.Elem, builder, pending)
if err != nil {
return err
}
p.(vdl.PendingOptional).AssignElem(elemType)
}
return nil
}
func (td *TypeDecoder) lookupOrMakeType(tid TypeId, builder *vdl.TypeBuilder, pending map[TypeId]vdl.PendingType) (vdl.TypeOrPending, error) {
if tt := td.lookupKnownType(tid); tt != nil {
return tt, nil
}
if p, ok := pending[tid]; ok {
return p, nil
}
return td.makeType(tid, builder, pending)
}