All Projects → renproject → surge

renproject / surge

Licence: MIT license
Simple, specialised, and efficient binary marshaling

Programming Languages

go
31211 projects - #10 most used programming language

Projects that are alternatives of or similar to surge

binary
package binary is a lightweight and high-performance serialization library to encode/decode between go data and []byte.
Stars: ✭ 20 (-44.44%)
Mutual labels:  serialization, binary
Iguana
universal serialization engine
Stars: ✭ 481 (+1236.11%)
Mutual labels:  serialization, binary
Fspickler
A fast multi-format message serializer for .NET
Stars: ✭ 299 (+730.56%)
Mutual labels:  serialization, binary
hs-packer
Fast serialization in haskell
Stars: ✭ 13 (-63.89%)
Mutual labels:  serialization, binary
Binary
Generic and fast binary serializer for Go
Stars: ✭ 86 (+138.89%)
Mutual labels:  serialization, binary
protodata
A textual language for binary data.
Stars: ✭ 35 (-2.78%)
Mutual labels:  serialization, binary
Fastbinaryencoding
Fast Binary Encoding is ultra fast and universal serialization solution for C++, C#, Go, Java, JavaScript, Kotlin, Python, Ruby, Swift
Stars: ✭ 421 (+1069.44%)
Mutual labels:  serialization, binary
GroBuf
Fast binary serializer
Stars: ✭ 56 (+55.56%)
Mutual labels:  serialization, binary
Bincode
A binary encoder / decoder implementation in Rust.
Stars: ✭ 1,100 (+2955.56%)
Mutual labels:  serialization, binary
Beeschema
Binary Schema Library for C#
Stars: ✭ 46 (+27.78%)
Mutual labels:  serialization, binary
persistity
A persistence framework for game developers
Stars: ✭ 34 (-5.56%)
Mutual labels:  serialization, binary
Binaryserializer
A declarative serialization framework for controlling formatting of data at the byte and bit level using field bindings, converters, and code.
Stars: ✭ 197 (+447.22%)
Mutual labels:  serialization, binary
sirdez
Glorious Binary Serialization and Deserialization for TypeScript.
Stars: ✭ 20 (-44.44%)
Mutual labels:  serialization, binary
sia
Sia - Binary serialisation and deserialisation
Stars: ✭ 52 (+44.44%)
Mutual labels:  serialization, binary
Bois
Salar.Bois is a compact, fast and powerful binary serializer for .NET Framework. With Bois you can serialize your existing objects with almost no change.
Stars: ✭ 53 (+47.22%)
Mutual labels:  serialization, binary
Ceras
Universal binary serializer for a wide variety of scenarios https://discord.gg/FGaCX4c
Stars: ✭ 374 (+938.89%)
Mutual labels:  serialization, binary
nason
🗜 Ultra tiny serializer / encoder with plugin-support. Useful to build binary files containing images, strings, numbers and more!
Stars: ✭ 30 (-16.67%)
Mutual labels:  serialization, binary
Apex.Serialization
High performance contract-less binary serializer for .NET
Stars: ✭ 82 (+127.78%)
Mutual labels:  serialization, binary
Pbf
A low-level, lightweight protocol buffers implementation in JavaScript.
Stars: ✭ 618 (+1616.67%)
Mutual labels:  serialization, binary
Borer
Efficient CBOR and JSON (de)serialization in Scala
Stars: ✭ 131 (+263.89%)
Mutual labels:  serialization, binary

🔌 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.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].