Skip to content

UTCP-SBI Wire Protocol ​

SPEC-710 — Production

UTCP-SBI defines how SBI (Sovereign Binary Interface) frames are carried over UTCP datagrams for mesh transfer, peer coordination, and inter-node communication. It replaces the JSON-based NIP protocol wire format with zero-encoding binary frames.

Normative reference: SPEC-039 Sovereign Binary Interface (Libertaria Core)

Design Principles ​

PrincipleImplementation
Wire = MemorySBI containers are raw extern struct bytes. No serialization step on same-architecture peers.
Transport owns framingSBI defines containers, not framing. The 5-byte UTCP envelope is a transport concern.
Closed compression enumThree blessed algorithms. Adding more requires a protocol version bump.
One exchange, done or deadHandshake is a single message pair. No negotiation rounds.
Reject before allocateFrame size validated before any buffer allocation. Two comparisons close the DoS vector.

Envelope Format ​

Every UTCP-SBI message is a 5-byte envelope followed by a complete SBI frame:

┌─────────────────┬──────────┬─────────────────────────────────────┐
│ frame_len: u32  │ op: u8   │ SBI Frame (preamble + container)   │
│ (big-endian)    │          │                                     │
└─────────────────┴──────────┴─────────────────────────────────────┘
  • frame_len is the byte length of the SBI frame only; it does NOT include the 5-byte envelope itself
  • op identifies the message type for routing before SBI decode
  • The SBI frame inside is a vanilla SPEC-039 frame (24-byte preamble + container payload)

Frame Size Constraints ​

UTCP_SBI_PREAMBLE_MIN = 24       // SBI preamble only, no payload
UTCP_SBI_MAX_FRAME    = 262144   // 256 KB hard cap

Validation is the first operation after reading the envelope – before any buffer allocation:

if frame_len < UTCP_SBI_PREAMBLE_MIN or frame_len > UTCP_SBI_MAX_FRAME:
    send NACK("invalid_frame_size")
    close connection

Two comparisons. Both cheap. Both non-negotiable.

Op Code Table ​

Op CodeNameSBI TypeSizeDirection
0x01HANDSHAKESbiHandshake52B fixedBidirectional
0x10BLOCK_WANTSbiBlockWant33B fixedRequest
0x11BLOCK_PUTSbiBlockPutVariable (spine + DATA)Response
0x20DAG_SYNCSbiDagSyncVariable (spine + DATA)Bidirectional
0xF0ACKSbiAckFixedControl
0xF1NACKSbiNackVariable (error string)Control

Op code ranges:

  • 0x01–0x0F — Control (handshake, keepalive, future)
  • 0x10–0x2F — Data transfer (blocks, chunks, DAG nodes)
  • 0xF0–0xFF — Responses (ack, nack, status)

Message Definitions ​

HANDSHAKE (0x01) ​

Exchanged once at connection establishment. One message each direction – no negotiation round-trip.

SbiHandshake (52 bytes, extern struct, zero-pad aligned):

Offset  Field               Type      Size   Description
──────────────────────────────────────────────────────────────
0x00    peer_id             [32]u8    32     BLAKE3 peer identity (CellID)
0x20    capabilities        u32       4      Capability bitfield
0x24    required_features   u32       4      Features this peer requires
0x28    optional_features   u32       4      Features this peer supports optionally
0x2C    block_size          u32       4      Volume block size (for chunk alignment)
0x30    version             u16       2      Protocol version (1)
0x32    replica_count       u8        1      Scattered SB replica count
0x33    _pad                u8        1      Alignment padding
──────────────────────────────────────────────────────────────
        Total: 52 bytes (1 byte padding)

Capability bitfield:

BitFlagMeaning
0HAS_DEFLATESupports deflate compression
1HAS_ZSTDSupports ZSTD compression
2HAS_DEDUPContent-addressed deduplication on store
3HAS_RECOMPRESSCan recompress across algorithms
4–31ReservedMust be 0

Negotiation logic:

intersection = local.capabilities & remote.capabilities
missing = remote.required_features & ~intersection
if missing != 0:
    send NACK("missing_required_features")
    close connection

Optional features that aren't mutually supported are silently skipped. No negotiation round-trip; one exchange, one comparison, done or dead.

BLOCK_WANT (0x10) ​

Request a CAS chunk by content ID.

SbiBlockWant (33 bytes, extern struct):

Offset  Field         Type      Size   Description
──────────────────────────────────────────────────────────────
0x00    block_hash    [32]u8    32     BLAKE3-256 CID (of uncompressed content)
0x20    priority      u8        1      0=normal, 1=high, 2=critical
──────────────────────────────────────────────────────────────
        Total: 33 bytes

BLOCK_PUT (0x11) ​

Deliver a CAS chunk. Variable-length – spine + DATA region.

SbiBlockPut spine (40 bytes, extern struct):

Offset  Field         Type      Size   Description
──────────────────────────────────────────────────────────────
0x00    block_hash    [32]u8    32     BLAKE3-256 CID (of uncompressed content)
0x20    chunk_index   u32       4      Chunk position within file
0x24    comp_algo     u8        1      Compression algorithm (closed enum)
0x25    comp_level    u8        1      Compression level (0=default)
0x26    _pad          [2]u8     2      Alignment padding
──────────────────────────────────────────────────────────────
        Spine: 40 bytes

DATA region: compressed (or raw) chunk bytes immediately following spine

Receiver verification: BLAKE3(decompress(data)) == block_hash. If mismatch, send NACK.

DAG_SYNC (0x20) ​

Merkle tree synchronization. Peers exchange root hashes and missing node lists.

SbiDagSync spine (36 bytes, extern struct):

Offset  Field         Type      Size   Description
──────────────────────────────────────────────────────────────
0x00    root_hash     [32]u8    32     Merkle DAG root CID
0x20    depth         u16       2      Tree depth (for bounded traversal)
0x22    node_count    u16       2      Number of node hashes in DATA region
──────────────────────────────────────────────────────────────
        Spine: 36 bytes

DATA region: array of [32]u8 node hashes (node_count * 32 bytes)

ACK (0xF0) ​

Acknowledge successful receipt of a message.

SbiAck (8 bytes, extern struct):

Offset  Field         Type      Size   Description
──────────────────────────────────────────────────────────────
0x00    ref_seq       u32       4      Sequence number being acknowledged
0x04    status        u8        1      0=ok
0x05    _pad          [3]u8     3      Alignment padding
──────────────────────────────────────────────────────────────
        Total: 8 bytes

NACK (0xF1) ​

Reject a message or signal an error. Variable-length – optional error string in DATA region.

SbiNack spine (8 bytes, extern struct):

Offset  Field         Type      Size   Description
──────────────────────────────────────────────────────────────
0x00    ref_seq       u32       4      Sequence number being rejected
0x04    error_code    u16       2      Machine-readable error code
0x06    error_len     u16       2      Length of error string in DATA region (0 = none)
──────────────────────────────────────────────────────────────
        Spine: 8 bytes

DATA region: UTF-8 error string (error_len bytes, optional)

Mesh Transfer Integration ​

UTCP-SBI is the wire protocol for the Mesh Transfer system. The SBI codec and transport layer are wired directly to the CAS (Content-Addressable Storage) backend:

Op CodeHandlerCAS Operation
BLOCK_WANTMeshTransfer.handleBlockWant()CasManager.get(block_hash) → BLOCK_PUT reply
BLOCK_PUTMeshTransfer.handleBlockPut()Decompress → verify BLAKE3 → CasManager.put(data)
DAG_SYNCMeshTransfer.handleDagSync()Walk local DAG → compute missing set → respond
HANDSHAKESbiTransport.handleHandshake()Capability negotiation, peer registration

The mesh daemon dispatches frames by op code to the appropriate handler. Each handler is a self-contained function that reads the SBI container, performs the CAS operation, and sends a response. No shared mutable state between handlers.

Compression ​

Negotiation ​

Compression support is declared in the HANDSHAKE capability bitfield. The sender picks the best mutually-supported algorithm:

Sender preference order:
1. ZSTD (if both HAS_ZSTD) — best ratio, acceptable speed
2. Deflate (if both HAS_DEFLATE) — Graf compatibility
3. None — raw bytes

The comp_algo field in BLOCK_PUT tells the receiver which algorithm was used. The receiver decompresses before BLAKE3 verification – the content hash is always over uncompressed data.

Algorithm Enum ​

Closed set. Adding algorithms requires a protocol version bump.

ValueAlgorithmNotes
0x00NoneRaw, uncompressed
0x01DeflateGraf compatibility
0x02ZSTDLevels 1–22
0x03–0x0EReservedFuture blessed algorithms
0x0FExperimentalReceiver MUST reject unless out-of-band agreement exists

Zstd/Deflate Codec ​

The compression codec handles both algorithms through a unified interface:

  • Zstd: Default level 3 for BLOCK_PUT (good ratio, low latency). Level configurable per-connection via comp_level field.
  • Deflate: Level 6 (zlib default). Used primarily for Graf interop where Zstd is not available.
  • Passthrough: comp_algo = 0x00 skips compression entirely. Used for already-compressed content or tiny blocks where compression overhead exceeds savings.

BLAKE3 Content Addressing ​

All content hashes in UTCP-SBI use real BLAKE3 – 32-byte (256-bit) digests computed via C FFI to the reference BLAKE3 library. This is not a placeholder or substitute:

PropertyValue
AlgorithmBLAKE3 (via C library FFI)
Digest size32 bytes (256 bits)
Used inblock_hash (BLOCK_WANT, BLOCK_PUT), root_hash (DAG_SYNC), schema fingerprints
VerificationEvery BLOCK_PUT is verified: BLAKE3(decompress(data)) == block_hash

CLD Schema Fingerprints ​

SBI preambles carry a schema fingerprint – a BLAKE3 hash of the canonical CLD (Container Layout Descriptor). This binds each wire message to its exact struct layout:

fingerprint = BLAKE3(canonical_cld_bytes)

If a peer sends a frame with an unknown fingerprint, the receiver knows the schema has changed and can NACK with "unknown_schema" instead of silently misinterpreting fields. Schema evolution without silent corruption.

Mechanism over Policy does not mean infinite extension points. It means the mechanism is honest about its boundaries.

Connection Lifecycle ​

Peer A                                  Peer B
──────                                  ──────
  │                                       │
  │──── HANDSHAKE(caps, features) ───────→│
  │←─── HANDSHAKE(caps, features) ────────│
  │                                       │
  │  [capability intersection check]      │
  │  [if missing_required → NACK, close]  │
  │                                       │
  │──── BLOCK_WANT(cid, priority) ───────→│
  │←─── BLOCK_PUT(cid, data) ────────────│
  │──── ACK(seq) ────────────────────────→│
  │                                       │
  │──── DAG_SYNC(root, hashes) ──────────→│
  │←─── DAG_SYNC(root, missing) ─────────│
  │──── BLOCK_PUT(cid1, data) ───────────→│
  │──── BLOCK_PUT(cid2, data) ───────────→│
  │←─── ACK(seq) ─────────────────────────│
  │                                       │

Version Negotiation ​

The HANDSHAKE carries version: u16. If a peer sends JSON (old protocol), the receiver reads 4 bytes of frame_len – JSON starts with { (0x7B), which would indicate a 2GB+ frame. The max-frame-size check rejects it instantly. Clean cut, no ambiguity, no graceful degradation.

Relationship to Existing Specs ​

SpecRelationship
SPEC-039 (Libertaria)SBI container format – normative. UTCP-SBI uses vanilla SPEC-039 frames without modification.
SPEC-093 (RUMPK)UTCP transport layer – the L2 frame header (EtherType 0x88B5, CellID addressing, SYN/ACK/FIN/DATA flags). UTCP-SBI rides on top.
SPEC-101 (NexFS)Feature flags – required_features / optional_features bitmasks in HANDSHAKE match NexFS superblock fields for interoperability.
ADR N2UTCP over QUIC decision – architectural context for why UTCP exists.
ADR ST3CBOR wire format – UTCP-SBI supersedes CBOR for inter-node wire encoding. CBOR remains the on-disk format for DAG nodes inside NexFS.

Implementation Notes ​

Nim (Mesh Daemon) ​

The mesh daemon uses the SBI binary codec for all wire communication. Nim does copy-decode, not zero-copy – the daemon is a coordination layer, not a hot path. Wire compatibility with Zig and JS SBI implementations is what matters.

Read path: read(4B) → frame_len → validate → read(1B) → op → read(frame_len) → decode_sbi_frame().

The daemon wires decoded frames to MeshTransfer handlers that operate against the CAS backend. See Mesh Transfer for the full architecture.

Zig (NexFS CAS FFI) ​

NexFS exposes CAS chunks for export/import via C-FFI. The mesh daemon (Nim) calls nexfs_cas_get() to read chunks for BLOCK_PUT responses and nexfs_cas_put() to store received chunks.

JavaScript (Graf Workers) ​

Graf already has an SBI implementation (graf-forge/workers/sbi.js). Browser-based mesh peers can participate using the same wire format.

Security Considerations ​

  • Frame size cap (256 KB) prevents memory exhaustion attacks
  • Minimum frame size (24 bytes) prevents underflow/parsing attacks
  • BLAKE3 content verification on every BLOCK_PUT prevents data corruption and substitution
  • Schema fingerprint in SBI preamble prevents type confusion between struct versions
  • Closed compression enum prevents algorithm negotiation attacks
  • CellID authentication is handled by the UTCP layer (SPEC-093), not by UTCP-SBI