Simple, specialised, and efficient binary marshaling

๐Ÿ”Œ surge

GitHub Coverage Report

Documentation

A library for fast binary (un)marshaling. Designed to be used in Byzantine networks, ๐Ÿ”Œ surge never explicitly panics, protects against malicious inputs, allocates minimally, and has very few dependencies (its only dependency is the ginkgo testing framework). It supports the (un)marshaling of:

  • scalars,
  • arrays,
  • slices,
  • maps,
  • structs, and
  • custom implementations (using the Marshaler and Unmarshaler interfaces).

Built-in Types

All built-in types that can be marshaled are supported by surge. And, for the vast majority of use cases, ToBinary and FromBinary are the only functions that you will need to use:

Scalars

// Marshal
x := uint64(42)
data, err := surge.ToBinary(x)
if err != nil {
    panic(err)
}

// Unmarshal
y := uint64(0)
if err := surge.FromBinary(&y, data); err != nil {
    panic(err)
}

Arrays

Arrays are collections of a known, fixed, length. Arrays are not length prefixed, because their length is part of their type. All arrays marshal their elements one-by-one, with the exception of byte arrays (which are marshaled in bulk using copy):

// Marshal
x := [4]uint64{42, 43, 44, 45}
data, err := surge.ToBinary(x)
if err != nil {
    panic(err)
}

// Unmarshal
y := [4]uint64{}
if err := surge.FromBinary(&y, data); err != nil {
    panic(err)
}

Slices

Slices are collections of variable length. Slices are length prefixed, because their length is not known at compile-time. All slices marshal their elements one-by-one, with the exception of byte slices (which are marshaled in bulk using copy):

// Marshal
x := []uint64{42, 43, 44, 45}
data, err := surge.ToBinary(x)
if err != nil {
    panic(err)
}

// Unmarshal
y := []uint64{}
if err := surge.FromBinary(&y, data); err != nil {
    panic(err)
}

Maps

Maps are effectively slices of key/value pairs. Maps are length prefixed, because their length is not known at compile-time. Maps are marshaled as a sorted slice of (key, value) tuples, sorted lexographically by keys (after the key has been marshaled, because not all key types are directly comparable). Sorting is done because it guarantees that the binary output is always the same when the key/value pairs are the same (this is particularly useful when hashing/signing maps for authenticity):

// Marshal
x := map[string]uint64{"foo": 42, "bar": 43, "baz": 44}
data, err := surge.ToBinary(x)
if err != nil {
    panic(err)
}

// Unmarshal
y := map[string]uint64{}
if err := surge.FromBinary(&y, data); err != nil {
    panic(err)
}

User-defined types

The same pattern that we have seen above works for custom structs too. You will not need to make any changes to your struct, as long as all of its fields are marshalable by surge:

type MyStruct struct {
    Foo int64
    Bar float64
    Baz MyInnerStruct
}

type MyInnerStruct struct {
    Inner1 []bool
    Inner2 []string
}

// Marshal
x := MyStruct{
    Foo: int64(43),
    Bar: float64(3.14),
    Baz: MyInnerStruct{
        Inner1: []bool{true, false},
        Inner2: []string{"hello", "world"},
    },
}
data, err := surge.ToBinary(x)
if err != nil {
    panic(err)
}

// Unmarshal
y := MyStruct{}
if err := surge.FromBinary(&y, data); err != nil {
    panic(err)
}

Specialisation

Using the default marshaler built into surge is great for prototyping, and will good enough for many applications. But, sometimes we need to specialise our marshaling. Providing our own implementation will not only be faster, but it will also give us the ability to customise the marshaler (which can be necessary when thinking about backward compatibility, etc.):

type MyStruct struct {
  Foo int64
  Bar float64
  Baz string
}

// SizeHint tells surge how many bytes our
// custom type needs when being represented
// in its binary form.
func (myStruct MyStruct) SizeHint() int {
    return surge.SizeHintI64 +
           surge.SizeHintF64 +
           surge.SizeHintString(myStruct.Baz)
}

// Marshal tells surge exactly how to marshal
// our custom type. As you can see, most implementations
// will be very straight forward, and mostly exist
// for performance reasons. In the future, surge might
// adopt some kind of generator to automatically
// generate these implementations.
func (myStruct MyStruct) Marshal(buf []byte, rem int) ([]byte, int, error) {
    var err error
    if buf, rem, err = surge.MarshalI64(myStruct.Foo, buf, rem); err != nil {
        return buf, rem, err
    }
    if buf, rem, err = surge.MarshalF64(myStruct.Bar, buf, rem); err != nil {
        return buf, rem, err
    }
    if buf, rem, err = surge.MarshalString(myStruct.Baz, buf, rem); err != nil {
        return buf, rem, err
    }
    return buf, rem, err
}

// Unmarshal is the opposite of Marshal, and requires
// a pointer receiver.
func (myStruct *MyStruct) Unmarshal(buf []byte, rem int) ([]byte, int, error) {
    var err error
    if buf, rem, err = surge.UnmarshalI64(&myStruct.Foo, buf, rem); err != nil {
        return buf, rem, err
    }
    if buf, rem, err = surge.UnmarshalF64(&myStruct.Bar, buf, rem); err != nil {
        return buf, rem, err
    }
    if buf, rem, err = surge.UnmarshalString(&myStruct.Baz, buf, rem); err != nil {
        return buf, rem, err
    }
    return buf, rem, err
}

Testing

Testing custom marshaling implementations is incredibly important, but it can also be very tedious, and so it is rarely done as extensively as it should be. Luckily, surge helps us get this done quickly. By using the surgeutil package, we can write comprehensive tests very quickly:

func TestMyStruct(t *testing.T) {
    // Reflect on our custom type
    t := reflect.TypeOf(MyStruct{})
    
    // Fuzz and expect that it does not panic.
    surgeutil.Fuzz(t)
    
    // Marshal, then unmarshal, then check for
    // equality, and expect there to be no
    // errors.
    if err := surgeutil.MarshalUnmarshalCheck(t); err != nil {
        t.Fatalf("bad marshal/unmarshal/check: %v", err)
    }
    
    // Marshal when the buffer is too small
    // and check that it does not work.
    if err := surgeutil.MarshalBufTooSmall(t); err != nil {
        t.Fatalf("bad marshal with insufficient buffer: %v", err)
    }
    
    // Marshal when the remaining memory quota
    // is too small and check that it does not
    // work.
    if err := surgeutil.MarshalRemTooSmall(t); err != nil {
        t.Fatalf("bad marshal with insufficient rem quota: %v", err)
    }
    
    // Unmarshal when the buffer is too small
    // and check that it does not work.
    if err := surgeutil.UnmarshalBufTooSmall(t); err != nil {
        t.Fatalf("bad marshal with insufficient buffer: %v", err)
    }
    
    // Unmarshal when the remaining memory quota
    // is too small and check that it does not
    // work.
    if err := surgeutil.UnmarshalRemTooSmall(t); err != nil {
        t.Fatalf("bad marshal with insufficient rem quota: %v", err)
    }
}

Internally, surgeutil makes use of the quick standard library. So, for surgeutil to work, your type needs to be compatible with quick. This is usually automatic, and most of the time you will not need to think about quick at all. For the more exotic types, that do need custom support, all you need to do is implement the quick.Generator interface. For more examples of surgeutil in use, checkout any of the *_test.go files. All of the testing in surge is done using the surgeutil package.

Benchmarks

When using specialised implementations, surge is about as fast as you can get; it does not really do much under-the-hood. When using the default implementations, the need to use reflect introduces some slow-down, but performance is still faster than most alternatives:

goos: darwin
goarch: amd64
pkg: github.com/renproject/surge
BenchmarkPointMarshalJSON-8              2064483               563 ns/op              80 B/op          1 allocs/op
BenchmarkTriangleMarshalJSON-8            583173              1752 ns/op             239 B/op          1 allocs/op
BenchmarkModelMarshalJSON-8                 7018            163255 ns/op           24588 B/op          1 allocs/op
BenchmarkPointMarshal-8                 11212546               109 ns/op               0 B/op          0 allocs/op
BenchmarkTriangleMarshal-8               3700579               294 ns/op               0 B/op          0 allocs/op
BenchmarkModelMarshal-8                    38652             28270 ns/op              32 B/op          1 allocs/op
BenchmarkFoo-8                          33130609                33 ns/op               0 B/op          0 allocs/op

Contributions

Built with โค by Ren.

Similar Resources

Go library for decoding generic map values into native Go structures and vice versa.

mapstructure mapstructure is a Go library for decoding generic map values to structures and vice versa, while providing helpful error handling. This l

Jan 1, 2023

Easily and dynamically generate maps from Go static structures

structomap This package helps you to transform your struct into map easily. It provides a structomap.Serializer interface implemented by the structoma

Dec 9, 2022

Go package for dealing with maps, slices, JSON and other data.

Objx Objx - Go package for dealing with maps, slices, JSON and other data. Get started: Install Objx with one line of code, or update it with another

Dec 27, 2022

Encode and decode Go (golang) struct types via protocol buffers.

protostructure protostructure is a Go library for encoding and decoding a struct type over the wire. This library is useful when you want to send arbi

Nov 15, 2022

An optimal, byte-aligned, LZ+RLE hybrid encoder, designed to maximize decoding speed on NMOS 6502 and derived CPUs

An optimal, byte-aligned, LZ+RLE hybrid encoder, designed to maximize decoding speed on NMOS 6502 and derived CPUs

TSCrunch TSCrunch is an optimal, byte-aligned, LZ+RLE hybrid encoder, designed to maximize decoding speed on NMOS 6502 and derived CPUs, while keeping

Dec 21, 2022

Package ethernet implements marshaling and unmarshaling of IEEE 802.3 Ethernet II frames and IEEE 802.1Q VLAN tags. MIT Licensed.

ethernet Package ethernet implements marshaling and unmarshaling of IEEE 802.3 Ethernet II frames and IEEE 802.1Q VLAN tags. MIT Licensed. For more in

Dec 29, 2022

Struct for marshaling and unmarshaling glTF

glTF Struct for marshaling and unmarshaling glTF go get github.com/sturfeeinc/glTF/model It's autogenerated code from official work group's specs. Don

Apr 5, 2020

Un-marshaling environment variables to Go structs

envcfg Un-marshaling environment variables to Go structs Getting Started Let's set a bunch of environment variables and then run your go app #!/usr/bi

Sep 26, 2022

goql is a GraphQL client package written in Go. with built-in two-way marshaling support via struct tags.

goql is a GraphQL client package written in Go. with built-in two-way marshaling support via struct tags.

Dec 1, 2022

A Protocol Buffers compiler that generates optimized marshaling & unmarshaling Go code for ProtoBuf APIv2

vtprotobuf, the Vitess Protocol Buffers compiler This repository provides the protoc-gen-go-vtproto plug-in for protoc, which is used by Vitess to gen

Jan 1, 2023

A YANG-centric Go toolkit - Go/Protobuf Code Generation; Validation; Marshaling/Unmarshaling

A YANG-centric Go toolkit - Go/Protobuf Code Generation; Validation; Marshaling/Unmarshaling

Introduction ygot (YANG Go Tools) is a collection of Go utilities that can be used to: Generate a set of Go structures and enumerated values for a set

Jan 8, 2023

searchHIBP is a golang tool that implements binary search over a hash ordered binary file.

searchHIBP is a golang tool that implements binary search over a hash ordered binary file.

Nov 9, 2021

Smocker is a simple and efficient HTTP mock server and proxy.

Smocker is a simple and efficient HTTP mock server and proxy.

Smocker (server mock) is a simple and efficient HTTP mock server. The documentation is available on smocker.dev. Table of contents Installation With D

Jan 7, 2023

Rpcx-framework - An RPC microservices framework based on rpcx, simple and easy to use, ultra fast and efficient, powerful, service discovery, service governance, service layering, version control, routing label registration.

RPCX Framework An RPC microservices framework based on rpcx. Features: simple and easy to use, ultra fast and efficient, powerful, service discovery,

Jan 5, 2022

Asynq: simple, reliable, and efficient distributed task queue in Go

Asynq: simple, reliable, and efficient distributed task queue in Go

Asynq Overview Asynq is a Go library for queueing tasks and processing them asynchronously with workers. It's backed by Redis and is designed to be sc

Dec 30, 2022

Go Open Source, Distributed, Simple and efficient Search Engine

Go Open Source, Distributed, Simple and efficient full text search engine.

Dec 31, 2022

Asynq: simple, reliable, and efficient distributed task queue in Go

Asynq: simple, reliable, and efficient distributed task queue in Go

Asynq: simple, reliable, and efficient distributed task queue in Go

Dec 31, 2022

A simple and efficient thread-safe sharded hashmap for Go

shardmap A simple and efficient thread-safe sharded hashmap for Go. This is an alternative to the standard Go map and sync.Map, and is optimized for w

Dec 17, 2022

Simple, customizable, leveled and efficient logging in Go

log Simple, customizable, leveled and efficient logging in Go Installation go get -u github.com/ermanimer/log Features log is a simple logging package

Dec 20, 2021
Comments
  • Per type marshaling

    Per type marshaling

    This PR introduces a few major changes:

    1. The Marshal and Unmarshal interfaces have changed to accept and return []byte instead of accepting io.Writer and io.Reader. This reduces the number of allocations, and increases performance significantly.
    2. An explicit SizeHintX, MarshalX, and UnmarshalX has been introduced for each type X allowing the programmer to avoid reflection overhead when the type X is known.
    3. Fixed handling of the rem (remaining number of bytes, previously called m) to correctly handle pre-allocations for slices and maps.
    4. Improved test coverage, including malicious scenarios, and added benchmarks for different kinds of data types.
    5. Added implicit marshaling/unmarshaling implementations for all struct types, using reflection to automatically marshal/unmarshal fields in-order.
  • Sparse testutil function variants and string unmarshaling tweak

    Sparse testutil function variants and string unmarshaling tweak

    This PR has the goal of improving performance, particularly targeting testing. The two changes are:

    • When unmarshaling strings, instead of using the strings.Builder we simply use the in built type cast. On my machine, the benchmark when using strings.Builder gives 15.45 ns/op, whereas using the in built type cast gives 14.86 ns/op.
    • For each of the testutil functions that checks for errors when marshaling/unmarshaling when the buffer/rem is too small, sparse variants are added. Whereas the original functions will test for every buffer/rem up to the valid amount, the sparse variants allow the caller to specify how many different sizes will be tested. This helps reduce the running time of tests when these functions are used to test large structures, and testing every single size is considered to be unnecessary.
  • Helper functions for marshalling and unmarshalling lengths.

    Helper functions for marshalling and unmarshalling lengths.

    It is likely that users of surge will want to marshal and unmarshal lengths (for slices, strings, etc.), so it makes sense to define functions for these in surge. These functions can also be used by surge itself.

  • Tests for unmarshalling with a too small buffer

    Tests for unmarshalling with a too small buffer

    The current implementation of UnmarshalBufTooSmall can not test the desired behaviour properly when the marshalled size of a type is smaller than the in memory size. This is because the rem is set to the marshalled size of the type.

Golang binary decoder for mapping data into the structure

binstruct Golang binary decoder to structure Install go get -u github.com/ghostiam/binstruct Examples ZIP decoder PNG decoder Use For struct From file

Dec 17, 2022
binary serialization format

Colfer Colfer is a binary serialization format optimized for speed and size. The project's compiler colf(1) generates source code from schema definiti

Dec 25, 2022
Musgo is a Go code generator for binary MUS format with validation support.

Musgo is a Go code generator for binary MUS format with validation support. Generated code converts data to and from MUS format.

Dec 29, 2022
Some Golang types based on builtin. Implements interfaces Value / Scan and MarshalJSON / UnmarshalJSON for simple working with database NULL-values and Base64 encoding / decoding.

gotypes Some simple types based on builtin Golang types that implement interfaces for working with DB (Scan / Value) and JSON (Marshal / Unmarshal). N

Feb 12, 2022
A simple Slack message tool for the CLI written in Go

heka A simple Slack message tool for the CLI written in Go Report Bug ยท Request

Jan 16, 2022
Sqlyog-password-decoder - Simple decode passwords from .sycs file (SQLyog export connections file)

Decode password: ./sqlyog-password-decoder -str password -action decode Encode p

Nov 21, 2021
Cap'n Proto library and parser for go. This is go-capnproto-1.0, and does not have rpc. See https://github.com/zombiezen/go-capnproto2 for 2.0 which has rpc and capabilities.

Version 1.0 vs 2.0 Update 2015 Sept 20: Big news! Version 2.0 of the go-bindings, authored by Ross Light, is now released and newly available! It feat

Nov 29, 2022
csvutil provides fast and idiomatic mapping between CSV and Go (golang) values.
csvutil provides fast and idiomatic mapping between CSV and Go (golang) values.

csvutil Package csvutil provides fast and idiomatic mapping between CSV and Go (golang) values. This package does not provide a CSV parser itself, it

Jan 6, 2023
Asn.1 BER and DER encoding library for golang.

WARNING This repo has been archived! NO further developement will be made in the foreseen future. asn1 -- import "github.com/PromonLogicalis/asn1" Pac

Nov 14, 2022
idiomatic codec and rpc lib for msgpack, cbor, json, etc. msgpack.org[Go]

go-codec This repository contains the go-codec library, the codecgen tool and benchmarks for comparing against other libraries. This is a High Perform

Dec 19, 2022