Simple binary encoding and decoding tool, a type-safe wrapper for encoding/binary
package.
Implements BinaryMarshaler
and BinaryUnmarshaler
from encoding
, supports different
encoding.BiteOrder
s. Errors accumulator to avoid each line error checks.
It could be useful to implement custom encoding
binary protocols.
Go has a package encoding/binary
for binary serialization, it provides good and low-level API for encoding.
The problem is that it's not really handy and requires a lot of boilerplate
code to read and write data to stream, such as: lack of type safety (accepts
interface{}
params) error-checks for each line,
custom array/slice encoding/decoding protocols, custom nested encoding/decoding.
This library aims to solve these issues by providing type-safe
and extended wrapper over encoding/binary
package which reduce the amount
of bolierplate codes and ensures type-safety.
Since it uses encoding/binary
under the hood, keep in mind that:
This package favors simplicity over efficiency. Clients that require high-performance serialization, especially for large data structures, should look at more advanced solutions such as the encoding/gob package or protocol buffers.
Add to imports: "github.com/g4s8/go-bundle"
The bundle package provides two main types:
Out
and Inp
. Out
(output) is used to put data into bundle
and produce byte array from it, Inp
(input) is used to read
data from bytes array.
There are different factory functions to create bundle outputs:
out := NewOut(order) // creates bundle and specify byte order
outBE := NewBEOut() // creates bundle with big-endian byte order
outLE := NewLEOut() // creates bundle with little-endian byte order
Each out bundle method has type-name suffix and accepts only fixed-length and strongly typed parameters:
out := NewBEOut()
out.PutInt32(int32(42))
out.PutUIint64(uint64(1111111111111))
out.PutFloat32(float32(1.4))
out.PutBytes([]byte{0x00, 0x01, 0x02})
out.PutString("hello bundle!")
Also, output accepts encoding.BinaryMarshaler
types to put:
out.PutBinary(binary)
.
Bundle can contain nested bundles, to put one bundle into another
use PutBundle
method:
parent := NewLEOut()
nested := NewLEOut()
parent.PutBundle(nested)
Output Put*
methods doesn't return errors, but in case of error it will be
accumulated. It can be checked with out.Error()
method. If error happens,
put method won't try to put next values to internal bytes buffer.
Output implements encoding.BinaryMarshaler
protocol. It can be used to
get byte array of the bundle:
out := NewBEOut()
out.PutInt32(int32(42))
bytes, err := out.MarshalBinary()
It returns buffer bytes or first error.
Flip
methods creates input from output: func (o *Out) Flip() *Inp
.
It can be useful for testing.
These factory functions can be used to wrap byte array with bundle input type:
i1 := NewInput(order) // creates input with specified order
i1 := NewBEInput() // creates big-endian input
i3 := NewLEInput() // creates little-endian input
i4 := InpWrapBytes(order, bts) // creates input from bytes with order
Bundle input implements encoding.BinaryUnmarshaler
protocol, it means
it can be read from byte array:
inp := NewInput(binary.BigEndian)
err := inp.UnmarshalBinary(data)
Alternatively, it can be constructed directly from bytes using wrap factory method:
inp := InpWrapBytes(order, bytes)
Input can't be read twise, the call to UnmarshalBinary
returns an error
if input was already read.
Input bundle methods has type suffixes and accepts output reference parameters to read the data:
// Reads int32
var i32 int32
inp.ReadInt32(&i32)
// Reads byte-array
var bts []byte
inp.ReadBytes(&bts)
// Read binary unmarshaler
// subInput - implements `encoding.BinaryUnmarshaler`
inp.ReadBinary(subInput)
Inp
can contain nested bundles, to read it create new empty Inp
with
new(Inp)
and read it from parent bundle:
// var parent Inp
nested := new(Inp)
parent.GetBundle(nested)
Errors are accumulated and can be accessed with Error()
method.
In case of error, next read methods won't be performed.
Assuming you need to implement encoding.BinaryMarshaler
and
encoding.BinaryUnmarshaler
for some struct. You can use bundle
to do that:
import (
"encoding/binary"
"github.com/g4s8/go-bundle"
ma "github.com/multiformats/go-multiaddr"
)
type Foo struct {
ID int32
Description string
internal []byte
Addr ma.Multiaddr
}
var byteOrder = binary.BigEndian
func (f *Foo) MarshalBinary() ([]byte, error) {
out := bundle.NewOut(byteOrder)
out.PutInt32(f.ID)
out.PutString(f.Description)
out.PutBytes(f.internal)
out.PutBinary(f.Addr)
return out.MarshalBinary()
}
func (f *Foo) UnmarshalBinary(data []byte) error {
inp := bundle.NewInput(byteOrder)
if err := inp.UnmarshalBinary(data); err != nil {
return err
}
inp.GetInt32(&f.ID)
inp.GetString(&f.Description)
inp.GetBytes(&f.internal)
var rawAddr []byte
inp.GetBytes(&rawAddr)
addr, err := ma.NewMultiaddrBytes(rawAddr)
if err != nil {
return err
}
f.Addr = addr
return nil
}