Introduction

Restruct is a library for the Go programming language that is designed to provide raw binary serialization and deserialization.

Restruct provides functionality similar to what you might expect from using raw structures in C, only richer. While using C structures is significantly faster, restruct provides greater functionality, such as allowing custom serialization/deserialization routines for user-defined types, explicit control of padding, and fine-grained control over byte order.

Features

Getting Started

Restruct's API is versioned using gopkg.in. To install the stable version of restruct, issue the following command:

go get gopkg.in/restruct.v1

Basic Usage

When not using any of the advanced features, restruct behaves similar to encoding/binary. Here's an example of reading and writing data with restruct, using only basic features:

package main

import (
	"encoding/binary"
	"io/ioutil"

	"gopkg.in/restruct.v1"
)

func main() {
	test := struct {
		Counter int32
	}{}

	if data, err := ioutil.ReadFile("counter"); err == nil {
		restruct.Unpack(data, binary.LittleEndian, &test)
	}

	test.Counter++

	data, _ := restruct.Pack(binary.LittleEndian, &test)
	ioutil.WriteFile("counter", data, 0777)
}

In this code sample, the file 'counter' will contain a mere 4 bytes, which will increment up from 1 each time the program is ran. Though contrived, this small program shows most of the public API of restruct.

The additional features provided by restruct are mostly provided through struct tagging.

Advanced Features

Struct tag formatting in restruct is very simple. Restruct uses the 'struct' name for its tags. The value of this tag will always be a comma separated list of flags, which are processed left-to-right. The following flags are currently supported:

Flag Description
[Type]
A bare Go type expression, e.g. int32 or []string. This allows you to override the binary interpretation of a field in many cases, although not every combination will work properly. Notably, however, it is possible to use a fixed-length byte array as a representation of a string, among many other transformations.
This field is parsed using the Go parser, so any valid Go expression will parse properly. However, channels and maps will cause an error.
sizeof=[Field]
Specifies that this field should be treated as a count of the number of elements in Field. Inspired by lunixbochs/struc, this flag makes it easy to unpack a variable number of elements. Unlike struc, however, the target field can be any slice type - it can be a slice of structures, a slice of arrays, and the binary size of elements can vary per element safely.
sizefrom=[Field]
Specifies that the size of this field should be taken from the count in Field. Using this flag, you can get the same functionality as sizeof=[Field], but with the relationship reversed, allowing, for example, multiple fields that use the same size counter.
skip=[Count]
Specifies an offset to skip before this field. This can be used to, for example, emulate C-style structure alignment. It is also possible to use this with unnamed (_) fields to skip an arbitrary number of bytes from the source without taking up any bytes of memory.
big
Specifies that this field should be treated as big endian. This will override the byte order passed into Pack or Unpack. This tag will also apply recursively if you put it on a composite field.
little
Like the above, but specifies little endian instead.
variantbool
When encoding boolean values, this flag will cause the true state to be encoded as ^0 (all bits set) instead of 1 (first bit set.) This emulates the VARIANT_BOOL type.
invertedbool
When encoding boolean values, this flag will cause the true and false encodings to be swapped.

Example

It would be difficult to show off every feature of restruct in a single example, but this slightly more advanced example shows off how some of the flags can be used in a real program.

package main

import (
    "encoding/binary"
    "io/ioutil"

    "gopkg.in/restruct.v1"
)

type Record struct {
    Message string `struct:"[128]byte"`
}

type Container struct {
    Version   int `struct:"int32"`
    NumRecord int `struct:"int32,sizeof=Records"`
    Records   []Record
}

func main() {
    var c Container

    data, _ := ioutil.ReadFile("records")

    restruct.Unpack(data, binary.LittleEndian, &c)
}