Add new vom conversion tests.
While the regular vom tests run against the golden bytes, the
conversion tests use vdltest and perform conversions. This
exposed a few unimplemented features and bugs; most notably, the
bytes special-case wasn't implemented in xDecoder.
Also updated comments for the new coders.
Change-Id: I2a6326ce8e41425bca2c6c1c44ccc6f801269e58
diff --git a/vdl/.api b/vdl/.api
index 56704dd..9ddce84 100644
--- a/vdl/.api
+++ b/vdl/.api
@@ -34,6 +34,7 @@
pkg vdl, func Compatible2(*Type, *Type) bool
pkg vdl, func Convert(interface{}, interface{}) error
pkg vdl, func CopyValue(*Value) *Value
+pkg vdl, func DecodeConvertedBytes(Decoder, int, *[]byte) error
pkg vdl, func DeepEqual(interface{}, interface{}) bool
pkg vdl, func DeepEqualReflect(reflect.Value, reflect.Value) bool
pkg vdl, func EnumType(...string) *Type
diff --git a/vdl/coder.go b/vdl/coder.go
index dbe7364..d150844 100644
--- a/vdl/coder.go
+++ b/vdl/coder.go
@@ -32,74 +32,156 @@
Writer
}
-// Decoder defines the interface for a decoder of vdl values.
+// Decoder defines the interface for a decoder of vdl values. The Decoder is
+// passed as the argument to VDLRead. An example of an implementation of this
+// interface is vom.Decoder.
//
-// TODO(toddw): This is a work in progress. Update the comments.
+// The Decoder provides an API to read vdl values of all types in depth-first
+// order. The ordering is based on the type of the value being read.
+// E.g. given the following value:
+// type MyStruct struct {
+// A []string
+// B map[int64]bool
+// C any
+// }
+// value := MyStruct{
+// A: {"abc", "def"},
+// B: {123: true, 456: false},
+// C: float32(1.5),
+// }
+// The values will be read in the following order:
+// "abc"
+// "def"
+// (123, true)
+// (456, false)
+// 1.5
type Decoder interface {
+ // StartValue must be called before decoding each value, for both scalar and
+ // composite values. Each call pushes the type of the next value on to the
+ // stack.
StartValue() error
+ // FinishValue must be called after decoding each value, for both scalar and
+ // composite values. Each call pops the type of the top value off of the
+ // stack.
FinishValue() error
- StackDepth() int
+ // SkipValue skips the next value; logically it behaves as if a full sequence
+ // of StartValue / ...Decode*... / FinishValue were called. It enables
+ // optimizations when the caller doesn't care about the next value.
SkipValue() error
+ // IgnoreNextStartValue instructs the Decoder to ignore the next call to
+ // StartValue. It is used to simplify implementations of VDLRead; e.g. a
+ // caller might call StartValue to check for nil values, and subsequently call
+ // StartValue again to read non-nil values. IgnoreNextStartValue is used to
+ // ignore the second StartValue call.
IgnoreNextStartValue()
- NextEntry() (bool, error)
- NextField() (string, error)
+ // StackDepth returns the stack depth of the current value. Returns 0 if
+ // StartValue hasn't been called for the next top-level value.
+ StackDepth() int
+ // NextEntry instructs the Decoder to move to the next element of an Array or
+ // List, the next key of a Set, or the next (key,elem) pair of a Map. Returns
+ // done=true when there are no remaining entries.
+ NextEntry() (done bool, _ error)
+ // NextField instructs the Decoder to move to the next field of a Struct or
+ // Union. Returns the name of the next field, or the empty string when there
+ // are no remaining fields.
+ NextField() (name string, _ error)
+
+ // Type returns the type of the top value on the stack, corresponding to the
+ // previous call of StartValue. Returns nil when the stack depth is 0. The
+ // returned type is only Any or Optional iff the value is nil; non-nil values
+ // are "auto-dereferenced" to their underlying element value.
Type() *Type
+ // IsAny returns true iff the type of the top value on the stack was Any,
+ // despite the "auto-dereference" behavior of non-nil values.
IsAny() bool
+ // IsOptional returns true iff the type of the top value on the stack was
+ // Optional, despite the "auto-dereference" behavior of non-nil values.
IsOptional() bool
+ // IsNil returns true iff the top value on the stack is nil. It is equivalent
+ // to Type() == AnyType || Type().Kind() == Optional.
IsNil() bool
+ // Index returns the index of the current entry or field of the top value on
+ // the stack. Returns -1 if the top value is a scalar, or if NextEntry /
+ // NextField has not been called.
Index() int
+ // LenHint returns the length of the top value on the stack, if it is
+ // available. Returns -1 if the top value is a scalar, or if the length is
+ // not available.
LenHint() int
- // DecodeBool decodes and returns a bool.
+ // DecodeBool returns the top value on the stack as a bool.
DecodeBool() (bool, error)
- // DecodeString decodes and returns a string.
+ // DecodeString returns the top value on the stack as a string.
DecodeString() (string, error)
- // DecodeTypeObject decodes and returns a type.
- DecodeTypeObject() (*Type, error)
- // DecodeUint decodes and returns a uint, where the result has bitlen bits.
- // Errors are returned on loss of precision.
+ // DecodeUint returns the top value on the stack as a uint, where the result
+ // has bitlen bits. Errors are returned on loss of precision.
DecodeUint(bitlen int) (uint64, error)
- // DecodeInt decodes and returns an int, where the result has bitlen bits.
- // Errors are returned on loss of precision.
+ // DecodeInt returns the top value on the stack as an int, where the result
+ // has bitlen bits. Errors are returned on loss of precision.
DecodeInt(bitlen int) (int64, error)
- // DecodeFloat decodes and returns a float, where the result has bitlen bits.
- // Errors are returned on loss of precision.
+ // DecodeFloat returns the top value on the stack as a float, where the result
+ // has bitlen bits. Errors are returned on loss of precision.
DecodeFloat(bitlen int) (float64, error)
- // DecodeBytes decodes bytes into x. If fixedlen >= 0 the decoded bytes must
- // be exactly that length, otherwise there is no restriction on the number of
- // decoded bytes. If cap(*x) is not large enough to fit the decoded bytes, a
- // new byte slice is assigned to *x.
+ // DecodeBytes decodes the top value on the stack as bytes, into x. If
+ // fixedlen >= 0 the decoded bytes must be exactly that length, otherwise
+ // there is no restriction on the number of decoded bytes. If cap(*x) is not
+ // large enough to fit the decoded bytes, a new byte slice is assigned to *x.
DecodeBytes(fixedlen int, x *[]byte) error
+ // DecodeTypeObject returns the top value on the stack as a type.
+ DecodeTypeObject() (*Type, error)
}
-// Encoder defines the interface for an encoder of vdl values.
+// Encoder defines the interface for an encoder of vdl values. The Encoder is
+// passed as the argument to VDLWrite. An example of an implementation of this
+// interface is vom.Encoder.
//
-// TODO(toddw): This is a work in progress. Update the comments.
+// The Encoder provides an API to write vdl values of all types in depth-first
+// order. The ordering is based on the type of the value being written; see
+// Decoder for examples.
type Encoder interface {
- SetNextStartValueIsOptional()
- // {Start,Finish}Value must be called before / after every concrete value
- // tt must be non-any and non-optional
+ // StartValue must be called before encoding each non-nil value, for both
+ // scalar and composite values. The tt type cannot be Any or Optional; use
+ // NilValue to encode nil values.
StartValue(tt *Type) error
+ // FinishValue must be called after encoding each non-nil value, for both
+ // scalar and composite values.
FinishValue() error
- // NilValue takes the place of StartValue and FinishValue for nil values.
+ // NilValue encodes a nil value. The tt type must be Any or Optional.
NilValue(tt *Type) error
+ // SetNextStartValueIsOptional instructs the encoder that the next call to
+ // StartValue represents a value with an Optional type.
+ SetNextStartValueIsOptional()
- // NextEntry must be called for every entry.
+ // NextEntry instructs the Encoder to move to the next element of an Array or
+ // List, the next key of a Set, or the next (key,elem) pair of a Map. Set
+ // done=true when there are no remaining entries.
NextEntry(done bool) error
- // NextField must be called for every field.
+ // NextField instructs the Encoder to move to the next field of a Struct or
+ // Union. Set name to the name of the next field, or set name="" when there
+ // are no remaining fields.
NextField(name string) error
+ // SetLenHint sets the length of the List, Set or Map value. It may only be
+ // called immediately after StartValue, before NextEntry has been called. Do
+ // not call this method if the length is not known.
SetLenHint(lenHint int) error
- EncodeBool(v bool) error // bool
- EncodeUint(v uint64) error // byte, uint16, uint32, uint64
- EncodeInt(v int64) error // int8, int16, int32, int64
- EncodeFloat(v float64) error // float32, float64
- EncodeBytes(v []byte) error // []byte
- EncodeString(v string) error // string, enum
- EncodeTypeObject(v *Type) error // *Type
+ // EncodeBool encodes a bool value.
+ EncodeBool(v bool) error
+ // EncodeString encodes a string value.
+ EncodeString(v string) error
+ // EncodeUint encodes a uint value.
+ EncodeUint(v uint64) error
+ // EncodeInt encodes an int value.
+ EncodeInt(v int64) error
+ // EncodeFloat encodes a float value.
+ EncodeFloat(v float64) error
+ // EncodeBytes encodes a bytes value; either an array or list of bytes.
+ EncodeBytes(v []byte) error
+ // EncodeTypeObject encodes a type.
+ EncodeTypeObject(v *Type) error
}
func decoderCompatible(dec Decoder, tt *Type) error {
@@ -108,3 +190,46 @@
}
return nil
}
+
+var ttByteList = ListType(ByteType)
+
+// DecodeConvertedBytes is a helper function for implementations of
+// Decoder.DecodeBytes, to deal with cases where the decoder value is
+// convertible to []byte. E.g. if the decoder value is []float64, we need to
+// decode each element as a uint8, performing conversion checks.
+//
+// Since this is meant to be used in the implementation of DecodeBytes, there is
+// no outer call to StartValue/FinishValue.
+func DecodeConvertedBytes(dec Decoder, fixedlen int, buf *[]byte) error {
+ if err := decoderCompatible(dec, ttByteList); err != nil {
+ return err
+ }
+ if len := dec.LenHint(); len >= 0 && cap(*buf) < len {
+ *buf = make([]byte, 0, len)
+ } else {
+ *buf = (*buf)[:0]
+ }
+ index := 0
+ for {
+ switch done, err := dec.NextEntry(); {
+ case err != nil:
+ return err
+ case fixedlen >= 0 && done != (index >= fixedlen):
+ return fmt.Errorf("array len mismatch, done:%v index:%d len:%d", done, index, fixedlen)
+ case done:
+ return nil
+ }
+ if err := dec.StartValue(); err != nil {
+ return err
+ }
+ elem, err := dec.DecodeUint(8)
+ if err != nil {
+ return err
+ }
+ if err := dec.FinishValue(); err != nil {
+ return err
+ }
+ *buf = append(*buf, byte(elem))
+ index++
+ }
+}
diff --git a/vdl/pipe.go b/vdl/pipe.go
index 0daea79..b7207b0 100644
--- a/vdl/pipe.go
+++ b/vdl/pipe.go
@@ -681,7 +681,7 @@
func (d *pipeDecoder) DecodeBytes(fixedLen int, b *[]byte) error {
if !d.Enc.IsBytes {
- if err := bytesVDLRead(fixedLen, b, d); err != nil {
+ if err := DecodeConvertedBytes(d, fixedLen, b); err != nil {
return d.Enc.closeLocked(err)
}
return nil
@@ -697,40 +697,3 @@
copy(*b, d.Enc.ArgBytes)
return d.Enc.Err
}
-
-func bytesVDLRead(fixedlen int, b *[]byte, d Decoder) error {
- var err error
- switch len := d.LenHint(); {
- case fixedlen >= 0:
- *b = (*b)[:0]
- case len > 0:
- *b = make([]byte, 0, len)
- default:
- *b = (*b)[:0]
- }
- index := 0
- for {
- switch done, err := d.NextEntry(); {
- case err != nil:
- return err
- case fixedlen >= 0 && done != (index >= fixedlen):
- return fmt.Errorf("array len mismatch, done:%v index:%d len:%d %T", done, index, len(*b), *b)
- case done:
- return nil
- }
- var elem byte
- if err = d.StartValue(); err != nil {
- return err
- }
- tmp, err := d.DecodeUint(8)
- if err != nil {
- return err
- }
- elem = byte(tmp)
- if err = d.FinishValue(); err != nil {
- return err
- }
- *b = append(*b, elem)
- index++
- }
-}
diff --git a/vdl/reflect_decoder.go b/vdl/reflect_decoder.go
index d49bcaa..285b3a9 100644
--- a/vdl/reflect_decoder.go
+++ b/vdl/reflect_decoder.go
@@ -478,7 +478,7 @@
return fmt.Errorf("vdl: %v got %v bytes, want fixed len %v", top.Type, top.ReflectValue.Len(), fixedlen)
}
if !top.Type.IsBytes() {
- return bytesVDLRead(fixedlen, v, d)
+ return DecodeConvertedBytes(d, fixedlen, v)
}
if cap(*v) < top.ReflectValue.Len() {
*v = make([]byte, top.ReflectValue.Len())
diff --git a/vdl/value_decoder.go b/vdl/value_decoder.go
index 1dd4a1f..be1bd30 100644
--- a/vdl/value_decoder.go
+++ b/vdl/value_decoder.go
@@ -320,7 +320,7 @@
return errEmptyDecoderStack
}
if !top.Value.Type().IsBytes() {
- return bytesVDLRead(fixedlen, v, d)
+ return DecodeConvertedBytes(d, fixedlen, v)
}
if fixedlen >= 0 && top.Value.Len() != fixedlen {
return fmt.Errorf("vdl: %v got %v bytes, want fixed len %v", top.Value.Type(), top.Value.Len(), fixedlen)
diff --git a/vom/convert_test.go b/vom/convert_test.go
new file mode 100644
index 0000000..961901b
--- /dev/null
+++ b/vom/convert_test.go
@@ -0,0 +1,183 @@
+// 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.
+
+// +build newvdltests
+
+package vom_test
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "reflect"
+ "strings"
+ "sync"
+ "testing"
+
+ "v.io/v23/vdl"
+ "v.io/v23/vdl/vdltest"
+ "v.io/v23/vom"
+)
+
+func TestConvert(t *testing.T) {
+ // We run the tests concurrently, sharing a single Type{En,De}coder pair.
+ // This tests concurrent usage of the Type{En,De}coder, while still ensuring
+ // that the {En,De}coders are used sequentially.
+ rType, wType := newPipe()
+ encT := vom.NewTypeEncoder(wType)
+ decT := vom.NewTypeDecoder(rType)
+ decT.Start()
+ defer decT.Stop()
+ var pending sync.WaitGroup
+ // Go race has a limit of 8192 goroutines, so instead of running each test in
+ // its own goroutine, we batch up multiple tests into the same goroutine.
+ const numGoroutines = 50
+ all := vdltest.AllPass()
+ numPerGoroutine := len(all) / numGoroutines
+ for len(all) > 0 {
+ pending.Add(1)
+ num := numPerGoroutine
+ if len(all) < num {
+ num = len(all)
+ }
+ batch := all[:num]
+ all = all[num:]
+ go func(batch []vdltest.Entry) {
+ defer pending.Done()
+ for _, test := range batch {
+ // Perform conversion tests with go values.
+ name := "[go value] " + test.Name()
+ want := rvPtrValue(test.Target).Interface()
+ target := reflect.New(test.Target.Type()).Interface()
+ source := test.Source.Interface()
+ if err := testConvert(target, source, encT, decT); err != nil {
+ t.Errorf("%s: %v", name, err)
+ continue
+ }
+ if got, want := target, want; !vdl.DeepEqual(got, want) {
+ t.Errorf("%s\nGOT %#v\nWANT %#v", name, got, want)
+ continue
+ }
+ // Skip conversions from VNamedError into vdl.Value, because verror.E has
+ // a weird property that it sets Msg="v.io/v23/verror.Unknown" on its own,
+ // which isn't captured in the vdl.Value.
+ //
+ // TODO(toddw): Fix this weirdness in verror.
+ if strings.Contains(test.Name(), "VNamedError") {
+ continue
+ }
+ // Perform conversion tests with vdl.Value.
+ name = "[vdl.Value] " + test.Name()
+ vvWant, err := vdl.ValueFromReflect(test.Target)
+ if err != nil {
+ t.Errorf("%s: ValueFromReflect(Target) failed: %v", name, err)
+ continue
+ }
+ vvTarget := vdl.ZeroValue(vvWant.Type())
+ vvSource, err := vdl.ValueFromReflect(test.Source)
+ if err != nil {
+ t.Errorf("%s: ValueFromReflect(Source) failed: %v", name, err)
+ continue
+ }
+ if err := testConvert(vvTarget, vvSource, encT, decT); err != nil {
+ t.Errorf("%s: %v", name, err)
+ continue
+ }
+ if got, want := vvTarget, vvWant; !vdl.DeepEqual(got, want) {
+ t.Errorf("%s\nGOT %#v\nWANT %#v", name, got, want)
+ }
+ }
+ }(batch)
+ }
+ pending.Wait()
+}
+
+func testConvert(target, source interface{}, encT *vom.TypeEncoder, decT *vom.TypeDecoder) error {
+ if err := testConvertCoder(target, source); err != nil {
+ return err
+ }
+ if err := testConvertSingleShot(target, source); err != nil {
+ return err
+ }
+ return testConvertWithTypeCoder(target, source, encT, decT)
+}
+
+func testConvertCoder(target, source interface{}) error {
+ var buf bytes.Buffer
+ enc := vom.NewXEncoder(&buf)
+ if err := enc.Encode(source); err != nil {
+ return fmt.Errorf("Encode failed: %v", err)
+ }
+ data := buf.Bytes()
+ dec := vom.NewXDecoder(&buf)
+ if err := dec.Decode(target); err != nil {
+ return fmt.Errorf("Decode failed: %v\nDATA %x", err, data)
+ }
+ return nil
+}
+
+func testConvertSingleShot(target, source interface{}) error {
+ data, err := vom.XEncode(source)
+ if err != nil {
+ return fmt.Errorf("(single-shot) Encode failed: %v", err)
+ }
+ if err := vom.XDecode(data, target); err != nil {
+ return fmt.Errorf("(single-shot) Decode failed: %v\nDATA %x", err, data)
+ }
+ return nil
+}
+
+func testConvertWithTypeCoder(target, source interface{}, encT *vom.TypeEncoder, decT *vom.TypeDecoder) error {
+ var buf bytes.Buffer
+ enc := vom.NewXEncoderWithTypeEncoder(&buf, encT)
+ if err := enc.Encode(source); err != nil {
+ return fmt.Errorf("(with TypeEncoder) Encode failed: %v", err)
+ }
+ data := buf.Bytes()
+ dec := vom.NewXDecoderWithTypeDecoder(&buf, decT)
+ if err := dec.Decode(target); err != nil {
+ return fmt.Errorf("(with TypeDecoder) Decode failed: %v\nDATA %x", err, data)
+ }
+ return nil
+}
+
+// In concurrent modes, one goroutine may try to read vom types before they are
+// actually sent by other goroutine. We use a simple buffered pipe to provide
+// blocking read since bytes.Buffer will return EOF in this case.
+type pipe struct {
+ b bytes.Buffer
+ m sync.Mutex
+ c sync.Cond
+ closed bool
+}
+
+func newPipe() (io.ReadCloser, io.WriteCloser) {
+ p := &pipe{}
+ p.c.L = &p.m
+ return p, p
+}
+
+func (r *pipe) Read(p []byte) (n int, err error) {
+ r.m.Lock()
+ defer r.m.Unlock()
+ for r.b.Len() == 0 || r.closed {
+ r.c.Wait()
+ }
+ return r.b.Read(p)
+}
+
+func (p *pipe) Close() error {
+ p.m.Lock()
+ p.closed = true
+ p.c.Broadcast()
+ p.m.Unlock()
+ return nil
+}
+
+func (w *pipe) Write(p []byte) (n int, err error) {
+ w.m.Lock()
+ defer w.m.Unlock()
+ defer w.c.Signal()
+ return w.b.Write(p)
+}
diff --git a/vom/xdecoder.go b/vom/xdecoder.go
index cabb3e0..e44851e 100644
--- a/vom/xdecoder.go
+++ b/vom/xdecoder.go
@@ -33,6 +33,8 @@
errReadRawBytesFromNonAny = errors.New("vom: read into vom.RawBytes only supported on any values")
)
+// Decoder manages the receipt and unmarshalling of typed values from the other
+// side of a connection.
type XDecoder struct {
dec xDecoder
}
@@ -52,26 +54,37 @@
IsOptional bool
}
+// NewDecoder returns a new Decoder that reads from the given reader. The
+// Decoder understands all formats generated by the Encoder.
func NewXDecoder(r io.Reader) *XDecoder {
return &XDecoder{xDecoder{
old: NewDecoder(r),
}}
}
+// NewDecoderWithTypeDecoder returns a new Decoder that reads from the given
+// reader. Types are decoded separately through the typeDec.
func NewXDecoderWithTypeDecoder(r io.Reader, typeDec *TypeDecoder) *XDecoder {
return &XDecoder{xDecoder{
old: NewDecoderWithTypeDecoder(r, typeDec),
}}
}
+// Decoder returns d as a vdl.Decoder.
func (d *XDecoder) Decoder() vdl.Decoder {
return &d.dec
}
+// Decode reads the next value and stores it in value v. The type of v need not
+// exactly match the type of the originally encoded value; decoding succeeds as
+// long as the values are convertible.
func (d *XDecoder) Decode(v interface{}) error {
return vdl.Read(&d.dec, v)
}
+// Ignore ignores the next value from d.
+//
+// TODO(toddw): Rename to SkipValue.
func (d *XDecoder) Ignore() error {
return d.dec.old.Ignore()
}
@@ -470,6 +483,21 @@
return false, fmt.Errorf("vom: type mismatch, got %v, want bool", tt)
}
+func (d *xDecoder) binaryDecodeByte() (byte, error) {
+ // Handle a special-case where normally single bytes are written out as
+ // variable sized numbers, which use 2 bytes to encode bytes > 127. But each
+ // byte contained in a list or array is written out as one byte. E.g.
+ // byte(0x81) -> 0xFF81 : single byte with variable-size
+ // []byte("\x81\x82") -> 0x028182 : each elem byte encoded as one byte
+ if stackTop2 := len(d.stack) - 2; stackTop2 >= 0 {
+ if top2 := d.stack[stackTop2]; top2.Type.IsBytes() {
+ return d.old.buf.ReadByte()
+ }
+ }
+ x, err := binaryDecodeUint(d.old.buf)
+ return byte(x), err
+}
+
func (d *xDecoder) DecodeUint(bitlen int) (uint64, error) {
const errFmt = "vom: %v conversion to uint%d loses precision: %v"
tt, ubitlen := d.Type(), uint(bitlen)
@@ -477,7 +505,13 @@
return 0, errEmptyDecoderStack
}
switch tt.Kind() {
- case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64:
+ case vdl.Byte:
+ x, err := d.binaryDecodeByte()
+ if err != nil {
+ return 0, err
+ }
+ return uint64(x), err
+ case vdl.Uint16, vdl.Uint32, vdl.Uint64:
x, err := binaryDecodeUint(d.old.buf)
if err != nil {
return 0, err
@@ -517,7 +551,18 @@
return 0, errEmptyDecoderStack
}
switch tt.Kind() {
- case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64:
+ case vdl.Byte:
+ x, err := d.binaryDecodeByte()
+ if err != nil {
+ return 0, err
+ }
+ // The only case that fails is if we're converting byte(x) to int8, and x
+ // uses more than 7 bits (i.e. is greater than 127).
+ if bitlen <= 8 && x > 0x7f {
+ return 0, fmt.Errorf(errFmt, tt, bitlen, x)
+ }
+ return int64(x), nil
+ case vdl.Uint16, vdl.Uint32, vdl.Uint64:
x, err := binaryDecodeUint(d.old.buf)
if err != nil {
return 0, err
@@ -559,7 +604,13 @@
return 0, errEmptyDecoderStack
}
switch tt.Kind() {
- case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64:
+ case vdl.Byte:
+ x, err := d.binaryDecodeByte()
+ if err != nil {
+ return 0, err
+ }
+ return float64(x), nil
+ case vdl.Uint16, vdl.Uint32, vdl.Uint64:
x, err := binaryDecodeUint(d.old.buf)
if err != nil {
return 0, err
@@ -607,26 +658,23 @@
if top == nil {
return errEmptyDecoderStack
}
- tt, len := top.Type, top.LenHint
- if tt.IsBytes() {
- switch {
- case len == -1:
- return fmt.Errorf("vom: %v LenHint is currently required", tt)
- case fixedlen >= 0 && fixedlen != len:
- return fmt.Errorf("vom: %v got %v bytes, want fixed len %v", tt, len, fixedlen)
- case len == 0:
- *v = nil
- return nil
- }
- if cap(*v) >= len {
- *v = (*v)[:len]
- } else {
- *v = make([]byte, len)
- }
- return d.old.buf.ReadIntoBuf(*v)
+ tt := top.Type
+ if !tt.IsBytes() {
+ return vdl.DecodeConvertedBytes(d, fixedlen, v)
}
- // TODO(toddw): Deal with conversions from []number.
- return fmt.Errorf("vom: type mismatch, got %v, want bytes", tt)
+ len := top.LenHint
+ switch {
+ case len == -1:
+ return fmt.Errorf("vom: %v LenHint is currently required", tt)
+ case fixedlen >= 0 && fixedlen != len:
+ return fmt.Errorf("vom: %v got %d bytes, want fixed len %d", tt, len, fixedlen)
+ }
+ if cap(*v) >= len {
+ *v = (*v)[:len]
+ } else {
+ *v = make([]byte, len)
+ }
+ return d.old.buf.ReadIntoBuf(*v)
}
func (d *xDecoder) DecodeString() (string, error) {
diff --git a/vom/xencoder.go b/vom/xencoder.go
index 093a3a3..1817e93 100644
--- a/vom/xencoder.go
+++ b/vom/xencoder.go
@@ -17,23 +17,34 @@
errEmptyEncoderStack = errors.New("vom: empty encoder stack")
)
+// Encoder manages the transmission and marshaling of typed values to the other
+// side of a connection.
type XEncoder struct {
enc xEncoder
}
+// NewEncoder returns a new Encoder that writes to the given writer in the VOM
+// binary format. The binary format is compact and fast.
func NewXEncoder(w io.Writer) *XEncoder {
return NewVersionedXEncoder(DefaultVersion, w)
}
-func NewXEncoderWithTypeEncoder(w io.Writer, typeEnc *TypeEncoder) *XEncoder {
- return NewVersionedXEncoderWithTypeEncoder(DefaultVersion, w, typeEnc)
-}
-
+// NewVersionedEncoder returns a new Encoder that writes to the given writer with
+// the specified version.
func NewVersionedXEncoder(version Version, w io.Writer) *XEncoder {
typeEnc := newTypeEncoderInternal(version, newXEncoderForTypes(version, w))
return NewVersionedXEncoderWithTypeEncoder(version, w, typeEnc)
}
+// NewEncoderWithTypeEncoder returns a new Encoder that writes to the given
+// writer, where types are encoded separately through the typeEnc.
+func NewXEncoderWithTypeEncoder(w io.Writer, typeEnc *TypeEncoder) *XEncoder {
+ return NewVersionedXEncoderWithTypeEncoder(DefaultVersion, w, typeEnc)
+}
+
+// NewVersionedEncoderWithTypeEncoder returns a new Encoder that writes to the
+// given writer with the specified version, where types are encoded separately
+// through the typeEnc.
func NewVersionedXEncoderWithTypeEncoder(version Version, w io.Writer, typeEnc *TypeEncoder) *XEncoder {
if !isAllowedVersion(version) {
panic(fmt.Sprintf("unsupported VOM version: %x", version))
@@ -94,10 +105,13 @@
}
}
+// Encoder returns e as a vdl.Encoder.
func (e *XEncoder) Encoder() vdl.Encoder {
return &e.enc
}
+// Encode transmits the value v. Values of type T are encodable as long as the
+// T is a valid vdl type.
func (e *XEncoder) Encode(v interface{}) error {
return vdl.Write(&e.enc, v)
}
@@ -462,18 +476,33 @@
binaryEncodeBool(e.buf, v)
return nil
}
+
func (e *xEncoder) EncodeUint(v uint64) error {
+ // Handle a special-case where normally single bytes are written out as
+ // variable sized numbers, which use 2 bytes to encode bytes > 127. But each
+ // byte contained in a list or array is written out as one byte. E.g.
+ // byte(0x81) -> 0xFF81 : single byte with variable-size
+ // []byte("\x81\x82") -> 0x028182 : each elem byte encoded as one byte
+ if stackTop2 := len(e.stack) - 2; stackTop2 >= 0 {
+ if top2 := e.stack[stackTop2]; top2.Type.IsBytes() {
+ e.buf.WriteOneByte(byte(v))
+ return nil
+ }
+ }
binaryEncodeUint(e.buf, v)
return nil
}
+
func (e *xEncoder) EncodeInt(v int64) error {
binaryEncodeInt(e.buf, v)
return nil
}
+
func (e *xEncoder) EncodeFloat(v float64) error {
binaryEncodeFloat(e.buf, v)
return nil
}
+
func (e *xEncoder) EncodeBytes(v []byte) error {
top := e.top()
if top == nil {
@@ -490,6 +519,7 @@
e.buf.Write(v)
return nil
}
+
func (e *xEncoder) EncodeString(v string) error {
top := e.top()
if top == nil {
@@ -509,6 +539,7 @@
}
return nil
}
+
func (e *xEncoder) EncodeTypeObject(v *vdl.Type) error {
tid, err := e.typeEnc.encode(v)
if err != nil {