1.0.11 • Published 9 months ago

binary-packet v1.0.11

Weekly downloads
-
License
Apache-2.0
Repository
github
Last release
9 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

9 months ago

1.0.10

9 months ago

1.0.9

9 months ago

1.0.8

9 months ago

1.0.7

9 months ago

1.0.6

9 months ago

1.0.5

9 months ago

1.0.4

9 months ago

1.0.3

9 months ago

1.0.2

9 months ago

1.0.1

9 months ago

1.0.0

9 months ago