tendermint/binary
The binary
submodule encodes primary types and structs into bytes.
uint*, int*, string, time, byteslice and byteslice-slice types can be encoded and decoded with the following methods:
The following writes o uint64
to w io.Writer
, and increments n
and/or sets err
WriteUint64(o uint64, w io.Writer, n *int64, err *error)
// Typical usage:
buf, n, err := new(bytes.Buffer), new(int64), new(error)
WriteUint64(uint64(x), buf, n, err)
if *err != nil {
panic(err)
}
The following reads a uint64
from r io.Reader
, and increments n
and/or sets err
var o = ReadUint64(r io.Reader, n *int64, err *error)
Similar methods for uint32
, uint16
, uint8
, int64
, int32
, int16
, int8
exist.
Protobuf variable length encoding is done with uint
and int
types:
WriteUvarint(o uint, w io.Writer, n *int64, err *error)
var o = ReadUvarint(r io.Reader, n *int64, err *error)
Byteslices can be written with:
WriteByteSlice(bz []byte, w io.Writer, n *int64, err *error)
Byteslices (and all slices such as byteslice-slices) are prepended with
uvarint
encoded length, so ReadByteSlice()
knows how many bytes to read.
Note that there is no type information encoded -- the caller is assumed to know what types to decode.
Struct types can be automatically encoded with reflection. Unlike json-encoding, no field name or type information is encoded. Field values are simply encoded in order.
type Foo struct {
MyString string
MyUint32 uint32
myPrivateBytes []byte
}
foo := Foo{"my string", math.MaxUint32, []byte("my private bytes")}
buf, n, err := new(bytes.Buffer), new(int64), new(error)
WriteBinary(foo, buf, n, err)
// fmt.Printf("%X", buf.Bytes()) gives:
// 096D7920737472696E67FFFFFFFF
// 09: uvarint encoded length of string "my string"
// 6D7920737472696E67: bytes of string "my string"
// FFFFFFFF: bytes for MaxUint32
// Note that the unexported "myPrivateBytes" isn't encoded.
foo2 := ReadBinary(Foo{}, buf, n, err).(Foo)
// Or, to decode onto a pointer:
foo2 := ReadBinary(&Foo{}, buf, n, err).(*Foo)
WriteBinary and ReadBinary can encode/decode structs recursively. However, interface field values are a bit more complicated.
type Greeter interface {
Greet() string
}
type Dog struct{}
func (d Dog) Greet() string { return "Woof!" }
type Cat struct{}
func (c Cat) Greet() string { return "Meow!" }
type Foo struct {
Greeter
}
foo := Foo{Dog{}}
buf, n, err := new(bytes.Buffer), new(int64), new(error)
WriteBinary(foo, buf, n, err)
// This errors because we don't know whether to read a Dog or Cat.
foo2 := ReadBinary(Foo{}, buf, n, err)
In the above example, ReadBinary()
fails because the Greeter
field for Foo{}
is ambiguous -- it could be either a Dog{}
or a Cat{}
, like a union structure.
The solution is to declare the concrete implementation types for interfaces:
type Dog struct{}
func (d Dog) TypeByte() byte { return GreeterTypeDog }
func (d Dog) Greet() string { return "Woof!" }
type Cat struct{}
func (c Cat) TypeByte() byte { return GreeterTypeCat }
func (c Cat) Greet() string { return "Meow!" }
var _ = RegisterInterface(
struct{Greeter}{},
ConcreteType{Dog{}},
ConcreteType{Cat{}},
})
NOTE: The TypeByte() is written and expected to be read even when the struct is encoded or decoded directly:
WriteBinary(Dog{}, buf, n, err) // Writes GreeterTypeDog byte
dog_ := ReadBinary(Dog{}, buf, n, err) // Expects to read GreeterTypeDog byte
dog := dog_.(Dog) // ok if *err != nil, otherwise dog_ == nil.