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 |
i8–i128 |
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/BsatnDeserializeimplementations
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) |
Related Pages¶
- Bridge Internals — C++ framework overview
- RPC Parameter Structs — game-specific RPC types