1.0.11 • Published 10 months ago

binary-packet v1.0.11

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
10 months ago

binary-packet

Lightweight and hyper-fast, zero-dependencies, TypeScript-first, schema-based binary packets serialization and deserialization library. \ Originally made to be used for an ultrafast WebSockets communication with user-defined type-safe messages between client and server, with the smallest bytes usage possible.

Supports serializing into and deserializing from DataViews, ArrayBuffers and Buffers (NodeJS/Bun only). \ To achieve the maximum performance is it always advised to use node Buffer(s) when available.

Installation

Node: \ npm install binary-packet

Bun: \ bun add binary-packet

Features & Specification

Define the structure of the packets through unique Packet IDs and "schema" objects. \ This "schema" object is simply called Definition and defines the shape of a packet: specifically its fields and their types.

Fields / Data types

Currently, these kinds of fields are supported: | Type | Description | Values | Size (bytes) | |------|-------------|--------------|--------------| | Field.UNSIGNED_INT_8 | 8 bits unsigned integer | 0 - 255 | 1 | | Field.UNSIGNED_INT_16 | 16 bits unsigned integer | 0 - 65535 | 2 | | Field.UNSIGNED_INT_32 | 32 bits unsigned integer | 0 - 4294967295 | 4 | | Field.INT_8 | 8 bits signed integer | -128 - 127 | 1 | | Field.INT_16 | 16 bits signed integer | -32768 - 32767 | 2 | | Field.INT_32 | 32 bits signed integer | -2147483648 - 2147483647 | 4 | | Field.FLOAT_32 | 32 bits IEEE754 floating-point | | 4 | | Field.FLOAT_64 | 64 bits IEEE754 floating-point | | 8 | | BinaryPacket | BinaryPacket "subpacket" | BinaryPacket | size(BinaryPacket) | | FieldString | ASCII or single octet utf-8 chars string | Up to 65536 chars | 2 + length | | FieldArray | Dynamically-sized array of one of the types above | Up to 256 elements | 1 + length * size(Element) | | FieldFixedArray | Statically-sized array of one of the types above | Any pre-defined numbers of elements | length * size(Element) | | FieldBitFlags | Boolean flags packed into a single 8 bits integer | Up to 8 boolean flags | 1 | | FieldOptional | Optional BinaryPacket "subpacket" | BinaryPacket | undefined | 1 + size(BinaryPacket) |

As shown, both arrays and nested objects ("subpackets") are supported. \ Note: FieldFixedArray is much more memory efficient and performant than FieldArray, but require a pre-defined length.

Pattern matching

The library exposes an easy way to "pattern match" packets of a yet-unknown-type in a type-safe manner through a visitor pattern. \ For an example, search for "pattern matching" in the examples below.

Usage Examples

Example: (incomplete) definition of a simplistic board game

import { BinaryPacket, Field, FieldArray } from 'binary-packet'

// Suppose we have a game board where each cell is a square and is one unit big.
// A cell can be then defined by its X and Y coordinates.
// For simplicity, let's say there cannot be more than 256 cells, so we can use 8 bits for each coordinate.
const Cell = {
  x: Field.UNSIGNED_INT_8,
  y: Field.UNSIGNED_INT_8
}

// When done with the cell definition we can create its BinaryPacket writer/reader.
// NOTE: each BinaryPacket needs an unique ID, for identification purposes and error checking.
const CellPacket = BinaryPacket.define(0, Cell)

// Let's now make the definition of the whole game board.
// You can also specify arrays of both "primitive" fields and other BinaryPackets.
const Board = {
  numPlayers: Field.UNSIGNED_INT_8,
  otherStuff: Field.INT_32,
  cells: FieldArray(CellPacket)
}

// When done with the board definition we can create its BinaryPacket writer/reader.
// NOTE: each BinaryPacket needs an unique ID, for identification purposes and error checking.
const BoardPacket = BinaryPacket.define(1, Board)

//////////////////
// WRITING SIDE //
//////////////////
const buffer = BoardPacket.writeNodeBuffer({
  numPlayers: 1,
  otherStuff: 69420,
  cells: [
    { x: 0, y: 0 },
    { x: 1, y: 1 }
  ]
})

// ...
// sendTheBufferOverTheNetwork(buffer)
// ...

//////////////////
// READING SIDE //
//////////////////
import assert from 'assert'

// ...
// const buffer = receiveTheBufferFromTheNetwork()
// ...

const board = BoardPacket.readNodeBuffer(buffer)

assert(board.numPlayers === 1)
assert(board.otherStuff === 69420)
assert(board.cells.length === 2)
assert(board.cells[0].x === 0)
assert(board.cells[0].y === 0)
assert(board.cells[1].x === 1)
assert(board.cells[1].y === 1)

Example: pattern matching

import assert from 'assert/strict'
import { BinaryPacket, Field } from 'binary-packet'

// Packet A definition
const A = BinaryPacket.define(1)

// Packet B definition: This is the kind of packets that we care about in this example!
const B = BinaryPacket.define(2, { data: Field.UNSIGNED_INT_8 })

// Packet C definition
const C = BinaryPacket.define(3)

// Assume the following packet comes from the network or, for some other reason, is a buffer we do not know anything about.
const buffer = B.writeNodeBuffer({ data: 255 })

BinaryPacket.visitNodeBuffer(
  buffer,

  A.visitor(() => assert(false, 'Erroneously accepted visitor A')),

  B.visitor(packet => {
    // Do something with the packet
    assert.equal(packet.data, 255)
    console.log('Accepted visitor B:', packet)
  }),

  C.visitor(() => assert(false, 'Erroneously accepted visitor C'))
)

Benchmarks & Alternatives

Benchmarks are not always meant to be taken seriously. \ Most of the times the results of a benchmark do not actually show the full capabilities of each library. \ So, take these "performance" comparisons with a grain of salt; or, even better, do your own benchmarks with the actual data you need to serialize/deserialize.

This library has been benchmarked against the following alternatives:

  • msgpackr - A very popular, fast and battle-tested library. Currently offers more features than binary-packet, but it always appears to be slower and is also less type-safe.
  • restructure - An older, popular schema-based library, has some extra features like LazyArrays, but it is much slower than both binary-packet and msgpackr. And, sadly, easily crashes with complex structures.

The benchmarks are executed on three different kinds of packets:

  • EmptyPacket: basically an empty javascript object.
  • SimplePacket: objects with just primitive fields, statically-sized arrays and a string.
  • ComplexPacket: objects with primitives, statically-sized arrays, dynamically-sized arrays, bitflags, a string, an array of strings and other nested objects/arrays.

You can see and run the benchmarks yourself if you:

Disclaimer

This library is still very new, thus not "battle-tested" in production enough, or may still have missing important features. \ If you plan on serializing highly sensitive data or need to guarantee no crashes, use an alternative like msgpackr until this library becomes 100% production-ready.

Contribute

Would like to have more complex, but still hyper-fast and memory efficient, features? \ Contribute on GitHub yourself or, alternatively, buy me a coffee!

1.0.11

10 months ago

1.0.10

10 months ago

1.0.9

10 months ago

1.0.8

10 months ago

1.0.7

10 months ago

1.0.6

10 months ago

1.0.5

10 months ago

1.0.4

10 months ago

1.0.3

10 months ago

1.0.2

10 months ago

1.0.1

10 months ago

1.0.0

10 months ago