Skip to content

Detailed Protocol Specification

ZeroZ Stack relies on a pure binary WebSocket protocol to enable high-performance, bidirectional communication between the WebAssembly client and the Jakarta EE backend. The protocol eliminates JSON overhead entirely, providing dense serialization and native object mapping.

General Frame Structure

Every WebSocket frame in ZeroZ Stack is binary and begins with a 4-byte ID:

[ 4 bytes: Correlation ID or Handle ID (Int) ]
[ 1 byte : Frame Type / Opcode (Optional for RMI Requests) ]
[ Variable : Payload ]
  • Correlation ID: Used in Request/Response pairs to match server replies with suspended client coroutines.
  • Handle ID: Used in LiveSync frames to identify the synced object graph.

RPC Protocol (RMI)

Remote Method Invocations allow the client to call server-side CDI beans directly.

RMI Request (Client -> Server)

Interestingly, standard RMI requests do not include a dedicated opcode byte. Instead, they rely on the length prefix of the interface name. Since the interface name string length is less than 16MB, the 5th byte (MSB of the length integer) is always 0x00. The server interprets 0x00 as an RMI call because it falls outside the 0x10-0x1F LiveSync opcode range.

Structure: * [4 bytes] Message ID * [String] Interface FQCN (Length + UTF-8 bytes) * [String] Method Name * [4 bytes] Argument Count * [N Elements] Arguments (Type Tag + Value)

Server Responses (Server -> Client)

Server responses include an explicit opcode byte at index 4.

  • 0x01 — RMI_RESPONSE (Success)
  • Payload: [Type Tag + Value] (The return value of the method)
  • 0x0F — RMI_ERROR (Error)
  • Payload: [String] (Error message)
  • 0x02 — PUSH (Server-initiated)
  • Payload: [String] (Topic Name) + [Type Tag + Value] (Payload)
  • 0x03 — AUTH (Authentication Handshake)
  • Sent automatically on connection open.
  • Payload: [String] (Username) + [4 bytes] (Role Count) + [N Strings] (Roles)

LiveSync Protocol (0x10 – 0x1F)

LiveSync handles real-time object graph synchronization and reactive signals.

  • 0x10 — SUBSCRIBE (Client -> Server)
  • Payload: [String] Class Name
  • 0x11 — SNAPSHOT (Server -> Client)
  • Payload: [8 bytes] Version + [Type Tag + Value] Serialized Object
  • 0x12 — UNSUBSCRIBE (Client -> Server)
  • Payload: None (Handle ID is sufficient)
  • 0x13 — MUTATE (Client -> Server)
  • Propose a new state.
  • Payload: [8 bytes] Base Version + [Type Tag + Value] Serialized Object
  • 0x14 — ACK (Server -> Client)
  • Mutation accepted.
  • Payload: [8 bytes] New Version
  • 0x15 — REJECT (Server -> Client)
  • Mutation rejected (e.g., version conflict).
  • Payload: [8 bytes] Current Version + [Type Tag + Value] Current Object + [String] Reason
  • 0x16 — SIGNAL_SUB (Client -> Server)
  • Reserved. The current subscribe mechanism rides an RMI-shaped frame to the internal service zeroz4j.signals (method subscribe, one String argument: the signal name); the server intercepts it before service dispatch and answers with the retained value.
  • 0x17 — SIGNAL_UPD (Server -> Client)
  • Shared signal value. Broadcast to all sessions on every server-side change, and sent directly to a session in response to a subscribe.
  • Payload: [String] Signal Name + [Type Tag + Value] Serialized Value

Binary Type Tags (Serialization)

ZeroZ Stack serializes data dynamically using BinarySerializer. Each serialized argument or payload value is prefixed with a 1-byte type tag:

Tag Type Encoding
0x00 Null No payload
0x01 Integer 4 bytes
0x02 Long 8 bytes
0x03 Double 8 bytes
0x04 Float 4 bytes
0x05 Boolean 1 byte (0 or 1)
0x06 String 4-byte length + UTF-8 bytes
0x07 Object [String] ClassName + Field bytes from the generated <Model>_Serializer
0x08 Short 2 bytes
0x09 Byte 1 byte
0x0A Character 2 bytes
0x0B List 4-byte Size + N Elements (Tag + Value)
0x0C Map 4-byte Size + N Key/Value pairs (Tag + Value)
0x0D Byte Array 4-byte Length + N bytes
0x0E Reference [String] object id of an instance already written in this graph (cycles, shared references)
0x0F UUID Canonical 36-char string form (not two longs — TeaVM does not emulate UUID.getMostSignificantBits())
0x10 Instant 8-byte epoch second + 4-byte nano
0x11 Enum [String] declaring-class FQCN + [String] name()
0x12 Set 4-byte Size + N Elements (Tag + Value). Rebuilt as a LinkedHashSet, so encounter order survives
0x13 BigDecimal [String] exact toString() form — scale and precision preserved
0x14 BigInteger [String] exact toString() form
0x15 LocalDate 8-byte epoch-day long
0x16 LocalTime 8-byte nano-of-day long
0x17 LocalDateTime 8-byte epoch-day long + 8-byte nano-of-day long
0x18 Duration 8-byte seconds long + 4-byte nano int
0x19 Optional The contained value, or 0x00 NULL when empty
0x1A int[] 4-byte length + N × 4 bytes
0x1B long[] 4-byte length + N × 8 bytes
0x1C double[] 4-byte length + N × 8 bytes
0x1D float[] 4-byte length + N × 4 bytes
0x1E short[] 4-byte length + N × 2 bytes
0x1F char[] 4-byte length + N × 2 bytes
0x20 boolean[] 4-byte length + N × 1 byte
0x21 Lazy [String] session-scoped handle. Never the contents — the client resolves it with an RMI round trip on first get()

Note the tag space (0x000x21) is independent of the frame opcode space; a tag byte never appears where an opcode is expected.

Resolving a lazy handle rides an RMI-shaped frame to the internal service zeroz4j.lazy (method resolve, one String argument: the handle), alongside zeroz4j.signals and zeroz4j.livesync. The server intercepts it before service dispatch, checks that the handle was disclosed to that session, and answers with an ordinary 0x01 response carrying the loaded value.

Collections are rebuilt on the receiving side as ArrayList, LinkedHashSet and LinkedHashMap. Declare @DataModel fields as List, Set and Map rather than concrete implementation types.