packing.go 4.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
/*
Package restruct implements packing and unpacking of raw binary formats.

Structures can be created with struct tags annotating the on-disk or in-memory
layout of the structure, using the "struct" struct tag, like so:

	struct {
		Length int `struct:"int32,sizeof=Packets"`
		Packets []struct{
			Source    string    `struct:"[16]byte"`
			Timestamp int       `struct:"int32,big"`
			Data      [256]byte `struct:"skip=8"`
		}
	}

To unpack data in memory to this structure, simply use Unpack with a byte slice:

	msg := Message{}
	restruct.Unpack(data, binary.LittleEndian, &msg)
*/
package restruct

import (
	"encoding/binary"
	"reflect"
)

func fieldFromIntf(v interface{}) (field, reflect.Value) {
	val := reflect.ValueOf(v)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	f := fieldFromType(val.Type())
	return f, val
}

/*
Unpack reads data from a byteslice into a value.

Two types of values are directly supported here: Unpackers and structs. You can
pass them by value or by pointer, although it is an error if Restruct is
unable to set a value because it is unaddressable.

For structs, each field will be read sequentially based on a straightforward
interpretation of the type. For example, an int32 will be read as a 32-bit
signed integer, taking 4 bytes of memory. Structures and arrays are laid out
flat with no padding or metadata.

Unexported fields are ignored, except for fields named _ - those fields will
be treated purely as padding. Padding will not be preserved through packing
and unpacking.

The behavior of deserialization can be customized using struct tags. The
following struct tag syntax is supported:

	`struct:"[flags...]"`

Flags are comma-separated keys. The following are available:

	type              A bare type name, e.g. int32 or []string. For integer
	                  types, it is possible to specify the number of bits,
	                  allowing the definition of bitfields, by appending a
	                  colon followed by the number of bits. For example,
	                  uint32:20 would specify a field that is 20 bits long.

	sizeof=[Field]    Specifies that the field should be treated as a count of
	                  the number of elements in Field.

	sizefrom=[Field]  Specifies that the field should determine the number of
	                  elements in itself by reading the counter in Field.

	skip=[Count]      Skips Count bytes before the field. You can use this to
	                  e.g. emulate C structure alignment.

	big,msb           Specifies big endian byte order. When applied to
	                  structs, this will apply to all fields under the struct.

	little,lsb        Specifies little endian byte order. When applied to
	                  structs, this will apply to all fields under the struct.

	variantbool       Specifies that the boolean `true` value should be
	                  encoded as -1 instead of 1.

	invertedbool      Specifies that the `true` and `false` encodings for
	                  boolean should be swapped.
*/
func Unpack(data []byte, order binary.ByteOrder, v interface{}) (err error) {
	defer func() {
		if r := recover(); r != nil {
			var ok bool
			if err, ok = r.(error); !ok {
				panic(err)
			}
		}
	}()

	f, val := fieldFromIntf(v)
	ss := structstack{allowexpr: expressionsEnabled, buf: data}
	d := decoder{structstack: ss, order: order}
	d.read(f, val)

	return
}

/*
SizeOf returns the binary encoded size of the given value, in bytes.
*/
func SizeOf(v interface{}) (size int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = r.(error)
		}
	}()

	ss := structstack{allowexpr: expressionsEnabled}
	f, val := fieldFromIntf(v)
	return ss.fieldbytes(f, val), nil
}

/*
BitSize returns the binary encoded size of the given value, in bits.
*/
func BitSize(v interface{}) (size int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = r.(error)
		}
	}()

	ss := structstack{allowexpr: expressionsEnabled}
	f, val := fieldFromIntf(v)
	return ss.fieldbits(f, val), nil
}

/*
Pack writes data from a datastructure into a byteslice.

Two types of values are directly supported here: Packers and structs. You can
pass them by value or by pointer.

Each structure is serialized in the same way it would be deserialized with
Unpack. See Unpack documentation for the struct tag format.
*/
func Pack(order binary.ByteOrder, v interface{}) (data []byte, err error) {
	defer func() {
		if r := recover(); r != nil {
			data = nil
			err = r.(error)
		}
	}()

	ss := structstack{allowexpr: expressionsEnabled, buf: []byte{}}

	f, val := fieldFromIntf(v)
	data = make([]byte, ss.fieldbytes(f, val))

	ss.buf = data
	e := encoder{structstack: ss, order: order}
	e.write(f, val)

	return
}