-
Notifications
You must be signed in to change notification settings - Fork 28
/
type_encoder.go
229 lines (211 loc) · 6.71 KB
/
type_encoder.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
// 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"
"math"
"sync"
"v.io/v23/vdl"
)
// TypeEncoder manages the transmission and marshaling of types to the other
// side of a connection.
type TypeEncoder struct {
typeMu sync.RWMutex
typeToID map[*vdl.Type]TypeId // GUARDED_BY(typeMu)
nextID TypeId // GUARDED_BY(typeMu)
encMu sync.Mutex
enc *encoder81 // GUARDED_BY(encMu)
sentVersionByte bool // GUARDED_BY(encMu)
}
// NewTypeEncoder returns a new TypeEncoder that writes types to the given
// writer in the binary format.
func NewTypeEncoder(w io.Writer) *TypeEncoder {
return NewVersionedTypeEncoder(DefaultVersion, w)
}
// NewTypeEncoderVersion returns a new TypeEncoder that writes types to the given
// writer in the specified VOM version.
func NewVersionedTypeEncoder(version Version, w io.Writer) *TypeEncoder {
tc := &TypeEncoder{
typeToID: make(map[*vdl.Type]TypeId),
nextID: WireIdFirstUserType,
enc: newEncoderForTypes(version, w, newEncbuf()),
sentVersionByte: false,
}
return tc
}
func newTypeEncoderInternal(version Version, enc *encoder81) *TypeEncoder {
tc := &TypeEncoder{
typeToID: make(map[*vdl.Type]TypeId),
nextID: WireIdFirstUserType,
enc: enc,
sentVersionByte: true,
}
return tc
}
// encode encodes the wire type tt recursively in depth-first order, encoding
// any children of the type before the type itself. Type ids are allocated in
// the order that we recurse and consequentially may be sent out of sequential
// order if type information for children is sent (before the parent type).
func (e *TypeEncoder) encode(tt *vdl.Type) (TypeId, error) {
if tid := e.lookupTypeID(tt); tid != 0 {
return tid, nil
}
// We serialize type encoding to avoid a race that can break our assumption
// that all referenced types should be transmitted before the target type.
// This can happen when we allow multiple flows encode types concurrently.
// E.g.,
// * F1 is encoding T1
// * F2 is encoding T2 which has a T1 type field. F2 skipped T1 encoding,
// since F1 already assigned a type id to T1.
// * A type decoder can see T2 before T1 if F2 finishes encoding before F1.
//
// TODO(jhahn, toddw): We do not expect this would hurt the performance
// practically. Revisit this if it becomes a real issue.
//
// TODO(jhahn, toddw): There is still a known race condition where multiple
// flows send types with a cycle, but the types are referenced in different
// orders. Figure out the solution.
e.encMu.Lock()
defer e.encMu.Unlock()
if !e.sentVersionByte {
if _, err := e.enc.writer.Write([]byte{byte(e.enc.version)}); err != nil {
return 0, err
}
e.sentVersionByte = true
}
return e.encodeType(tt, map[*vdl.Type]bool{})
}
// encodeType encodes the type
func (e *TypeEncoder) encodeType(tt *vdl.Type, pending map[*vdl.Type]bool) (TypeId, error) { //nolint:gocyclo
// Lookup a type Id for tt or assign a new one.
tid, isNew, err := e.lookupOrAssignTypeID(tt)
if err != nil {
return 0, err
}
if !isNew {
return tid, nil
}
pending[tt] = true
// Construct the wireType.
var wt wireType
switch kind := tt.Kind(); kind {
case vdl.Bool, vdl.Byte, vdl.String, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64, vdl.Float32, vdl.Float64:
wt = wireTypeNamedT{wireNamed{tt.Name(), bootstrapKindToID[kind]}}
case vdl.Enum:
wireEnum := wireEnum{tt.Name(), make([]string, tt.NumEnumLabel())}
for ix := 0; ix < tt.NumEnumLabel(); ix++ {
wireEnum.Labels[ix] = tt.EnumLabel(ix)
}
wt = wireTypeEnumT{wireEnum}
case vdl.Array:
elm, err := e.encodeType(tt.Elem(), pending)
if err != nil {
return 0, err
}
wt = wireTypeArrayT{wireArray{tt.Name(), elm, uint64(tt.Len())}}
case vdl.List:
elm, err := e.encodeType(tt.Elem(), pending)
if err != nil {
return 0, err
}
wt = wireTypeListT{wireList{tt.Name(), elm}}
case vdl.Set:
key, err := e.encodeType(tt.Key(), pending)
if err != nil {
return 0, err
}
wt = wireTypeSetT{wireSet{tt.Name(), key}}
case vdl.Map:
key, err := e.encodeType(tt.Key(), pending)
if err != nil {
return 0, err
}
elm, err := e.encodeType(tt.Elem(), pending)
if err != nil {
return 0, err
}
wt = wireTypeMapT{wireMap{tt.Name(), key, elm}}
case vdl.Struct:
wireStruct := wireStruct{tt.Name(), make([]wireField, tt.NumField())}
for ix := 0; ix < tt.NumField(); ix++ {
field, err := e.encodeType(tt.Field(ix).Type, pending)
if err != nil {
return 0, err
}
wireStruct.Fields[ix] = wireField{tt.Field(ix).Name, field}
}
wt = wireTypeStructT{wireStruct}
case vdl.Union:
wireUnion := wireUnion{tt.Name(), make([]wireField, tt.NumField())}
for ix := 0; ix < tt.NumField(); ix++ {
field, err := e.encodeType(tt.Field(ix).Type, pending)
if err != nil {
return 0, err
}
wireUnion.Fields[ix] = wireField{tt.Field(ix).Name, field}
}
wt = wireTypeUnionT{wireUnion}
case vdl.Optional:
elm, err := e.encodeType(tt.Elem(), pending)
if err != nil {
return 0, err
}
wt = wireTypeOptionalT{wireOptional{tt.Name(), elm}}
default:
panic(fmt.Errorf("vom: encode unhandled type %v", tt))
}
// TODO(bprosnitz) Only perform the walk when there are cycles or otherwise optimize this
delete(pending, tt)
typeComplete := tt.Walk(vdl.WalkAll, func(t *vdl.Type) bool {
return !pending[t]
})
// Encode and write the wire type definition using the same
// binary encoder as values for wire types.
if err := e.enc.encodeWireType(tid, wt, !typeComplete); err != nil {
return 0, err
}
return tid, nil
}
// lookupTypeID returns the id for the type tt if it is already encoded;
// otherwise zero id is returned.
func (e *TypeEncoder) lookupTypeID(tt *vdl.Type) TypeId {
// Non-bootstrap types are the common case so look them up first.
e.typeMu.RLock()
tid := e.typeToID[tt]
e.typeMu.RUnlock()
if tid != 0 {
return tid
}
return bootstrapTypeToID[tt]
}
func (e *TypeEncoder) lookupOrAssignTypeID(tt *vdl.Type) (TypeId, bool, error) {
e.typeMu.Lock()
defer e.typeMu.Unlock()
tid := e.typeToID[tt]
if tid > 0 {
return tid, false, nil
}
if tid := bootstrapTypeToID[tt]; tid != 0 {
return tid, false, nil
}
// Assign a new id.
newID := e.nextID
if newID > math.MaxInt64 {
return 0, false, fmt.Errorf("vom: encoder type id overflow")
}
e.nextID++
e.typeToID[tt] = newID
return newID, true, nil
}
func (e *TypeEncoder) makeIDToTypeUnlocked() map[TypeId]*vdl.Type {
if len(e.typeToID) == 0 {
return nil
}
result := make(map[TypeId]*vdl.Type, len(e.typeToID))
for tt, id := range e.typeToID {
result[id] = tt
}
return result
}