blob: 6f28da90f0b355afdf57fbf6df4dccc31d076670 [file] [log] [blame]
// 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"
"sync"
"v.io/v23/vdl"
"v.io/v23/verror"
)
var (
errTypeInvalid = verror.Register(pkgPath+".errTypeInvalid", verror.NoRetry, "{1:}{2:} vom: type {3} id {4} invalid, the min user type id is {5}{:_}")
errAlreadyDefined = verror.Register(pkgPath+".errAlreadyDefined", verror.NoRetry, "{1:}{2:} vom: type {3} id {4} already defined as {5}{:_}")
errUnknownType = verror.Register(pkgPath+".errUnknownType", verror.NoRetry, "{1:}{2:} vom: unknown type id {3}{:_}")
errEmptyName = verror.Register(pkgPath+".errEmptyName", verror.NoRetry, "{1:}{2:} vom: NamedType has empty name{:_}")
errUnknownWireTypeDef = verror.Register(pkgPath+".errUnknownWireTypeDef", verror.NoRetry, "{1:}{2:} vom: unknown wire type definition {3}{:_}")
)
// TypeDecoder manages the receipt and unmarshalling of types from the other
// side of a connection. Start must be called to start decoding types,
// and Stop must be called to reclaim resources.
type TypeDecoder struct {
// The type encoder uses a 2-lock strategy for decoding. We use typeMu to lock
// type definitions, and use buildMu to allow only one worker to build types at
// a time. This is for simplifying the workflow and avoid unnecessary blocking
// for type lookups.
typeMu sync.RWMutex
idToType map[typeId]*vdl.Type // GUARDED_BY(typeMu)
buildMu sync.Mutex
buildCond *sync.Cond
err error // GUARDED_BY(buildMu)
idToWire map[typeId]wireType // GUARDED_BY(buildMu)
dec *Decoder // GUARDED_BY(buildMu)
receivedVersionByte bool // GUARDED_BY(buildMu)
processingControlMu sync.Mutex
goroutineRunning bool // GUARDED_BY(processingControlMu)
goroutineShouldStop bool // GUARDED_BY(processingControlMu)
}
// 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 {
mr := newMessageReader(newDecbuf(r))
td := newTypeDecoderInternal(mr)
mr.SetCallbacks(td.lookupType, nil)
return td
}
func newTypeDecoderInternal(mr *messageReader) *TypeDecoder {
td := &TypeDecoder{
idToType: make(map[typeId]*vdl.Type),
idToWire: make(map[typeId]wireType),
dec: &Decoder{
mr: mr,
typeDec: nil,
},
}
td.buildCond = sync.NewCond(&td.buildMu)
return td
}
func newDerivedTypeDecoderInternal(mr *messageReader, orig *TypeDecoder) *TypeDecoder {
td := &TypeDecoder{
idToType: orig.idToType,
idToWire: orig.idToWire,
dec: &Decoder{
mr: mr,
typeDec: nil,
},
}
td.buildCond = sync.NewCond(&td.buildMu)
return td
}
func (d *TypeDecoder) processLoop() {
var err error
for {
d.processingControlMu.Lock()
if d.goroutineShouldStop || err != nil {
d.goroutineShouldStop = false
d.goroutineRunning = false
d.processingControlMu.Unlock()
return
}
d.processingControlMu.Unlock()
// Note that we will block indefinitely if the underlying
// read blocks on the io.Reader.
err = d.readSingleType()
d.buildMu.Lock()
d.err = err
d.buildCond.Broadcast()
d.buildMu.Unlock()
// TODO(toddw): Reconsider d.err and d.buildCond strategy.
}
}
// Start must be called to start decoding types.
func (d *TypeDecoder) Start() {
d.processingControlMu.Lock()
d.goroutineShouldStop = false
if !d.goroutineRunning {
d.goroutineRunning = true
go d.processLoop()
}
d.processingControlMu.Unlock()
}
// Stop must be called after Start, to stop decoding types
// and reclaim resources. Once Stop is called,
// subsequent Decode calls on Decoders initialized with d
// will return errors.
func (d *TypeDecoder) Stop() {
d.processingControlMu.Lock()
d.goroutineShouldStop = true
d.processingControlMu.Unlock()
}
// readSingleType reads a single wire type
func (d *TypeDecoder) readSingleType() error {
var wt wireType
curTypeID, err := d.dec.decodeWireType(&wt)
if err != nil {
return err
}
// Add the wire type.
if err := d.addWireType(curTypeID, wt); err != nil {
return err
}
if !d.dec.mr.typeIncomplete {
if err := d.buildType(curTypeID); d.dec.mr.version != Version80 && 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 (d *TypeDecoder) lookupType(tid typeId) (*vdl.Type, error) {
if tt := d.lookupKnownType(tid); tt != nil {
return tt, nil
}
d.buildMu.Lock()
defer d.buildMu.Unlock()
for {
if d.err != nil && d.err != io.EOF {
// Return any existing error immediately. Skip EOF because it
// may still be possible to lookup a type.
return nil, d.err
}
if tt := d.lookupKnownType(tid); tt != nil {
return tt, nil
}
if d.err != nil {
return nil, d.err
}
d.buildCond.Wait()
}
}
// addWireType adds the wire type wt with the type id tid.
func (d *TypeDecoder) addWireType(tid typeId, wt wireType) error {
d.buildMu.Lock()
err := d.addWireTypeBuildLocked(tid, wt)
d.buildMu.Unlock()
return err
}
func (d *TypeDecoder) addWireTypeBuildLocked(tid typeId, wt wireType) error {
if tid < WireIdFirstUserType {
return verror.New(errTypeInvalid, nil, 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 := d.lookupKnownType(tid); dup != nil {
return verror.New(errAlreadyDefined, nil, wt, tid, dup)
}
if dup := d.idToWire[tid]; dup != nil {
return verror.New(errAlreadyDefined, nil, wt, tid, dup)
}
d.idToWire[tid] = wt
return nil
}
func (d *TypeDecoder) lookupKnownType(tid typeId) *vdl.Type {
if tt := bootstrapIdToType[tid]; tt != nil {
return tt
}
d.typeMu.RLock()
tt := d.idToType[tid]
d.typeMu.RUnlock()
return tt
}
// buildType builds the type from the given wire type.
func (d *TypeDecoder) buildType(tid typeId) error {
builder := vdl.TypeBuilder{}
pending := make(map[typeId]vdl.PendingType)
_, err := d.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.
d.typeMu.Lock()
for tid, tt := range types {
delete(d.idToWire, tid)
d.idToType[tid] = tt
}
d.typeMu.Unlock()
return nil
}
// makeType makes the pending type from its wire type representation.
func (d *TypeDecoder) makeType(tid typeId, builder *vdl.TypeBuilder, pending map[typeId]vdl.PendingType) (vdl.PendingType, error) {
wt := d.idToWire[tid]
if wt == nil {
return nil, verror.New(errUnknownType, nil, tid)
}
// Make the type from its wireType representation, adding
// it to pending so that subsequent lookups get the pending
// type. Eventually the built type will be added to dt.idToType.
if name := wt.(wireTypeGeneric).TypeName(); name != "" {
// Named types may be recursive, so we must create the named type first and
// add it to pending, before we make the base type. The base type may refer
// back to this named type, and will find it in pending.
namedType := builder.Named(name)
pending[tid] = namedType
if wtNamed, ok := wt.(wireTypeNamedT); ok {
// This is a NamedType pointing at a base type.
baseType, err := d.lookupOrMakeType(wtNamed.Value.Base, builder, pending)
if err != nil {
return nil, err
}
namedType.AssignBase(baseType)
return namedType, nil
}
// This isn't NamedType, but has a non-empty name.
baseType, err := d.makeBaseType(wt, builder, pending)
if err != nil {
return nil, err
}
namedType.AssignBase(baseType)
return namedType, nil
}
// Unnamed types are made directly from their base type. It's fine to update
// pending after making the base type, since there's no way to create a
// recursive type based solely on unnamed vdl.
baseType, err := d.makeBaseType(wt, builder, pending)
if err != nil {
return nil, err
}
pending[tid] = baseType
return baseType, nil
}
func (d *TypeDecoder) makeBaseType(wt wireType, builder *vdl.TypeBuilder, pending map[typeId]vdl.PendingType) (vdl.PendingType, error) {
switch wt := wt.(type) {
case wireTypeNamedT:
return nil, verror.New(errEmptyName, nil, wt)
case wireTypeEnumT:
enumType := builder.Enum()
for _, label := range wt.Value.Labels {
enumType.AppendLabel(label)
}
return enumType, nil
case wireTypeArrayT:
elemType, err := d.lookupOrMakeType(wt.Value.Elem, builder, pending)
if err != nil {
return nil, err
}
return builder.Array().AssignElem(elemType).AssignLen(int(wt.Value.Len)), nil
case wireTypeListT:
elemType, err := d.lookupOrMakeType(wt.Value.Elem, builder, pending)
if err != nil {
return nil, err
}
return builder.List().AssignElem(elemType), nil
case wireTypeSetT:
keyType, err := d.lookupOrMakeType(wt.Value.Key, builder, pending)
if err != nil {
return nil, err
}
return builder.Set().AssignKey(keyType), nil
case wireTypeMapT:
keyType, err := d.lookupOrMakeType(wt.Value.Key, builder, pending)
if err != nil {
return nil, err
}
elemType, err := d.lookupOrMakeType(wt.Value.Elem, builder, pending)
if err != nil {
return nil, err
}
return builder.Map().AssignKey(keyType).AssignElem(elemType), nil
case wireTypeStructT:
structType := builder.Struct()
for _, field := range wt.Value.Fields {
fieldType, err := d.lookupOrMakeType(field.Type, builder, pending)
if err != nil {
return nil, err
}
structType.AppendField(field.Name, fieldType)
}
return structType, nil
case wireTypeUnionT:
unionType := builder.Union()
for _, field := range wt.Value.Fields {
fieldType, err := d.lookupOrMakeType(field.Type, builder, pending)
if err != nil {
return nil, err
}
unionType.AppendField(field.Name, fieldType)
}
return unionType, nil
case wireTypeOptionalT:
elemType, err := d.lookupOrMakeType(wt.Value.Elem, builder, pending)
if err != nil {
return nil, err
}
return builder.Optional().AssignElem(elemType), nil
default:
return nil, verror.New(errUnknownWireTypeDef, nil, wt)
}
}
func (d *TypeDecoder) lookupOrMakeType(tid typeId, builder *vdl.TypeBuilder, pending map[typeId]vdl.PendingType) (vdl.TypeOrPending, error) {
if tt := d.lookupKnownType(tid); tt != nil {
return tt, nil
}
if p, ok := pending[tid]; ok {
return p, nil
}
return d.makeType(tid, builder, pending)
}