Skip to content

ELR2 protocol

ELR2 is Elura’s binary framing protocol for client and internal command traffic. All integer fields are encoded in network byte order (big-endian).

Frame layout

Every frame starts with a fixed 28-byte header:

OffsetSizeFieldDescription
04Magic0x454C5232, ASCII ELR2
42Version2
61KindRequest 1, response 2, push 3, error 4
71FlagsCurrently 0; other values are rejected
84RouteRuntime or application route ID
128Request IDNon-zero for request/response/error; zero for push
204SequenceSession ordering/reconnect sequence
244Payload lengthNumber of bytes after the header
28NPayloadOpaque to the frame codec; commonly protobuf or JSON

The default maximum payload is 1 MiB. FrameCodec permits configured limits up to 64 MiB, but application limits should stay as small as practical.

Frame kinds

  • Request starts an operation and must use a non-zero request ID.
  • Response correlates to the request ID, route, and sequence of a request.
  • Push is server-initiated and must use request ID 0.
  • Error correlates to a request and carries a serialized error envelope.

For WebSocket transport, one binary WebSocket message must contain exactly one ELR2 frame and the client must negotiate elura.v2 as the subprotocol. QUIC uses elura.v2 as ALPN. Text WebSocket messages are not part of the protocol.

Elura can generate matching C++17, C#, and TypeScript codecs. See Client protocol SDKs for the integration boundary and transport-specific checks.

The same protocol in every language

The Rust Frame and FrameCodec implementation is the ELR2 reference implementation, not a Rust-only transport. Generated C++17, C#, and TypeScript SDKs encode the same 28-byte header, integer byte order, frame kinds, validation rules, and payload bytes. Cross-language golden vectors verify byte-for-byte compatibility.

Do not replace ELR2 with a Rust-specific serialization format such as a native struct layout or bincode when non-Rust clients must connect. Those formats do not define a stable cross-language ABI. ELR2 provides language-neutral framing; protobuf provides language-neutral application request and response payloads.

Route ranges

RouteMeaning
1Authenticate
2Heartbeat
3Reconnect
4Session control
5..99Reserved for future runtime use
100+Application routes

Do not allocate application IDs below 100. On the Rust server, each typed application route implements Route, which binds its ID, diagnostic name, protobuf request, and protobuf response. WorldBuilder::register rejects duplicate IDs and names during startup. Clients must use the same route ID and protobuf schema.

Application payloads and errors

Typed application Request and successful Response payloads are protobuf messages declared by the application. A failed request uses kind 4 and the canonical UTF-8 JSON error envelope:

json
{
  "code": "NOT_ENOUGH_GOLD",
  "message": "not enough gold",
  "retryable": false
}

code uses uppercase ASCII letters, digits, and underscores and is at most 64 bytes. message is at most 1024 bytes. The error frame keeps the original request's route, request ID, and sequence. Because an error is a request result, its request ID cannot be zero; server-initiated notifications use Push.

Authentication sequence

text
Client                         Gateway                         World
  │                               │                              │
  ├── request route=1, ticket ───>│                              │
  │                               ├── verify signature/claims    │
  │                               ├── check replay/admission     │
  │<── response session+identity ─┤                              │
  │                               │                              │
  ├── request route>=100 ────────>│                              │
  │                               ├── authenticated command ────>│
  │                               │<── result/error ─────────────┤
  │<── response/error ────────────┤                              │

Unauthenticated sessions must authenticate within the Gateway’s authentication_timeout. Normal requests are subject to global and per-route token buckets, bounded queues, payload limits, handler timeouts, and idle timeouts.

Request IDs and retries

Request IDs are client-generated correlation identifiers and must be non-zero. The Gateway maintains a bounded response cache for recently completed requests, and the World maintains a bounded idempotency cache. Retries should reuse the same request ID and route within the configured TTL. Do not treat these in-memory caches as durable exactly-once delivery.

Compatibility

The codec rejects an unknown magic value, unsupported version, unknown kind, invalid request ID, oversized payload, or malformed message length. Version protocol changes explicitly; do not change field meanings while retaining version 2.

Released under the MIT License.