| // 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 ( |
| "io" |
| "math" |
| "sync" |
| |
| "v.io/v23/vdl" |
| "v.io/v23/verror" |
| ) |
| |
| var ( |
| errEncodeTypeIdOverflow = verror.Register(pkgPath+".errEncodeTypeIdOverflow", verror.NoRetry, "{1:}{2:} vom: encoder type id overflow{:_}") |
| errUnhandledType = verror.Register(pkgPath+".errUnhandledType", verror.NoRetry, "{1:}{2:} vom: encode unhandled type {3}{:_}") |
| ) |
| |
| // 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 *encoder // 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 { |
| return newTypeEncoderWithVersionByte(version, w) |
| } |
| |
| func newTypeEncoderWithVersionByte(version Version, w io.Writer) *TypeEncoder { |
| return &TypeEncoder{ |
| typeToId: make(map[*vdl.Type]typeId), |
| nextId: WireIdFirstUserType, |
| enc: newEncoderWithoutVersionByte(version, w, nil), |
| sentVersionByte: false, |
| } |
| } |
| |
| func newTypeEncoderWithoutVersionByte(version Version, w io.Writer) *TypeEncoder { |
| return &TypeEncoder{ |
| typeToId: make(map[*vdl.Type]typeId), |
| nextId: WireIdFirstUserType, |
| enc: newEncoderWithoutVersionByte(version, w, nil), |
| sentVersionByte: true, |
| } |
| } |
| |
| // 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) { |
| // 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, vdl.Complex64, vdl.Complex128: |
| 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(verror.New(errUnhandledType, nil, 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 { |
| if tid := bootstrapTypeToId[tt]; tid != 0 { |
| return tid |
| } |
| e.typeMu.RLock() |
| tid := e.typeToId[tt] |
| e.typeMu.RUnlock() |
| return tid |
| } |
| |
| func (e *TypeEncoder) lookupOrAssignTypeId(tt *vdl.Type) (typeId, bool, error) { |
| if tid := bootstrapTypeToId[tt]; tid != 0 { |
| return tid, false, nil |
| } |
| e.typeMu.Lock() |
| tid := e.typeToId[tt] |
| if tid > 0 { |
| e.typeMu.Unlock() |
| return tid, false, nil |
| } |
| |
| // Assign a new id. |
| newId := e.nextId |
| if newId > math.MaxInt64 { |
| e.typeMu.Unlock() |
| return 0, false, verror.New(errEncodeTypeIdOverflow, nil) |
| } |
| e.nextId++ |
| e.typeToId[tt] = newId |
| e.typeMu.Unlock() |
| return newId, true, nil |
| } |