Skip to content

BSATN Wire Format

Technical reference for the Binary SpacetimeDB Algebraic Type Notation — the serialisation format used by SpacetimeDB for all WebSocket communication.


Overview

BSATN is a compact binary format that encodes SpacetimeDB's algebraic type system. QS-Bridge implements a full C++ codec in 18 header-only files.

Type System

BSATN serialises SpacetimeDB's algebraic type system:

Type BSATN Encoding Size
bool 1 byte (0 or 1) 1
u8 1 byte 1
u16 2 bytes, little-endian 2
u32 4 bytes, little-endian 4
u64 8 bytes, little-endian 8
u128 16 bytes, little-endian 16
i8i128 Same as unsigned, two's complement 1–16
f32 IEEE 754, little-endian 4
f64 IEEE 754, little-endian 8
String 4-byte length prefix + UTF-8 bytes 4 + N
Vec<T> 4-byte count + N × T 4 + N×sizeof(T)
Option<T> 1-byte tag (0=None, 1=Some) + T if Some 1 or 1+sizeof(T)
Product (struct) Fields concatenated in order Σ fields
Sum (enum) 1-byte tag + variant payload 1 + payload
Timestamp u64 microseconds since Unix epoch 8
Identity 32 bytes (256-bit) 32

Header Files

graph TD
    ROOT[include/qsbridge/bsatn/]
    ROOT --> A[bsatn.h — Master include]
    ROOT --> B[bsatn_fwd.h — Forward declarations]
    ROOT --> C[reader.h — BsatnReader, stream deserialization]
    ROOT --> D[writer.h — BsatnWriter, stream serialization]
    ROOT --> E[serialization.h — Serialize/Deserialize framework]
    ROOT --> F[traits.h — Type traits for BSATN types]
    ROOT --> G[primitive_traits.h — Primitives]
    ROOT --> H[monostate_traits.h — Unit type support]
    ROOT --> I[types.h — AlgebraicType enum]
    ROOT --> J[types_impl.h — AlgebraicType serialization]
    ROOT --> K[algebraic_type.h — Sum/Product type definitions]
    ROOT --> L[sum_type.h — Tagged union encoding]
    ROOT --> M[type_extensions.h — Extension traits]
    ROOT --> N[size_calculator.h — Compile-time BSATN size computation]
    ROOT --> O[timestamp.h — Timestamp type]
    ROOT --> P[time_duration.h — TimeDuration type]
    ROOT --> Q[schedule_at.h — ScheduleAt type]
    ROOT --> R[schedule_at_impl.h — ScheduleAt serialization]

Wire Protocol

Message Framing

SpacetimeDB v2 uses the protocol name v2.bsatn.spacetimedb during WebSocket handshake. All messages are binary frames with BSATN-encoded payloads.

Client → Server Messages

Message Tag Payload
Subscribe 0 Vec<String> — SQL subscription queries
CallReducer 1 String reducer_name + Vec<u8> BSATN-encoded args

Server → Client Messages

Message Tag Payload
InitialConnection 0 Identity (32 bytes) — client's assigned identity
SubscribeApplied 1 u32 request_id + Vec<TableUpdate> initial rows
TransactionUpdate 2 Vec<TableUpdate> incremental changes
ReducerResult 3 String reducer_name + outcome + Option<String> error
SubscriptionError 4 String error message

TableUpdate Format

TableUpdate:
  table_id:    u32
  table_name:  String
  inserts:     BsatnRowList
  deletes:     BsatnRowList

BsatnRowList:
  size_hint:   SizeHint (FixedSize(u16) or RowOffsets)
  row_data:    Vec<u8>  (concatenated BSATN-encoded rows)

SizeHint: For tables where all rows have the same BSATN size, FixedSize(n) allows the client to split the row data buffer into rows without parsing. For variable-size tables, RowOffsets provides a Vec<u32> of byte offsets.

BsatnReader

class BsatnReader {
    const uint8_t* data_;
    size_t offset_;
    size_t size_;

public:
    bool     read_bool();
    uint8_t  read_u8();
    uint16_t read_u16();
    uint32_t read_u32();
    uint64_t read_u64();
    int32_t  read_i32();
    float    read_f32();
    double   read_f64();
    std::string read_string();    // 4-byte length + UTF-8
    Identity read_identity();     // 32 bytes
    Timestamp read_timestamp();   // u64 microseconds

    template<typename T>
    T read();                     // Generic deserialization

    template<typename T>
    std::vector<T> read_vec();    // 4-byte count + elements

    template<typename T>
    std::optional<T> read_option(); // 1-byte tag + value
};

BsatnWriter

class BsatnWriter {
    std::vector<uint8_t> buffer_;

public:
    void write_bool(bool v);
    void write_u8(uint8_t v);
    void write_u32(uint32_t v);
    void write_u64(uint64_t v);
    void write_string(const std::string& v);
    void write_identity(const Identity& v);

    template<typename T>
    void write(const T& v);

    template<typename T>
    void write_vec(const std::vector<T>& v);

    const std::vector<uint8_t>& data() const;
};

Codegen

tools/codegen.py (809 lines) generates C++ BSATN types from UE4 bindings:

  • Input: UE4 property definitions extracted from the running game
  • Output: 218 header files (111 reducer + 58 type + 44 table deserialiser)
  • Each table header: struct definition + BsatnSerialize/BsatnDeserialize implementations

Testing

Suite Assertions Coverage
test_table_roundtrip.cpp 131 All 44 table types (default + value round-trips)
test_protocol_framing.cpp 26 Subscribe, CallReducer, InitialConnection, all message types
test_server_messages.cpp 108 SubscribeApplied, TransactionUpdate, ReducerResult (4 outcomes)