Skip to main content

Overview

The CloudGaming signaling protocol uses JSON messages over WebSocket to coordinate WebRTC connections. All messages are validated using Zod schemas before being forwarded to peers.

Message Validation

The server validates all incoming messages against strict schemas. Invalid messages are:
  1. Dropped silently
  2. Logged with warning level
  3. Trigger a schema-error control message to the sender
  4. Increment the signaling_schema_rejects_total metric
Non-JSON messages are rejected immediately and not processed.

Message Types

The protocol supports four primary message types:
  • offer - WebRTC session description (SDP offer)
  • answer - WebRTC session description (SDP answer)
  • candidate - ICE candidate for connection establishment
  • control - Server control messages

Offer Message

Sent by the host to initiate a WebRTC connection with a peer.

Schema

Validation Rules

  • type must be exactly "offer"
  • sdp must be a non-empty string
  • sdp should be valid SDP format (starts with v=)

Example

Usage

Client-side (JavaScript):
Server behavior:
  • Validates message schema
  • Publishes to Redis: room:ROOM_ID
  • Forwards to all peers in room (except sender)
  • Logs: "Received from server: { type: offer, sdp: '...', candidate: undefined }"

Answer Message

Sent by the client in response to an offer.

Schema

Validation Rules

  • type must be exactly "answer"
  • sdp must be a non-empty string
  • sdp should be valid SDP format (starts with v=)

Example

Usage

Client-side (JavaScript):
Server behavior:
  • Validates message schema
  • Publishes to Redis: room:ROOM_ID
  • Forwards to all peers in room (except sender)
  • Typically forwarded to the original offer sender

Candidate Message

Sends ICE candidates for NAT traversal and connection establishment.

Schema

Validation Rules

  • type must be exactly "candidate"
  • candidate must be a non-empty string
  • sdpMid is optional string
  • sdpMLineIndex is optional non-negative integer

Example

Host candidate:
Server reflexive candidate:
Relay candidate (TURN):
End of candidates signal:

ICE Candidate Types

Usage

Client-side (JavaScript):
Server behavior:
  • Validates message schema
  • Publishes to Redis: room:ROOM_ID
  • Forwards to all peers in room (except sender)
  • Empty candidate string is valid (end-of-candidates)

Legacy ice-candidate Format

The client also supports an older format for backwards compatibility:
The ice-candidate format is not validated by the server schema. Use the candidate format for proper validation.

Control Messages

Server-to-client control and informational messages.

Schema

Validation Rules

  • type must be exactly "control"
  • action must be a non-empty string
  • payload is optional and can be any JSON-serializable value

Control Actions

schema-error

Sent by the server when a client message fails validation. Example:
Trigger conditions:
  • Invalid JSON
  • Missing required fields
  • Wrong field types
  • Empty strings where non-empty required
Client handling:

peer-disconnected

Sent to all room members when a peer leaves. Example:
peer-disconnected uses a simplified schema without the control action field for backwards compatibility.
Server behavior:
  • Broadcast when client disconnects (WebSocket close)
  • Broadcast when client removed from Redis room
  • Published to Redis channel: room:ROOM_ID
  • Forwarded to all remaining peers
Client handling:
Example implementation:

Message Flow Examples

Complete Connection Establishment

Disconnection Flow

Error Scenarios

Invalid Message Type

Sent:
Result:
  • Message validation fails
  • Server logs: "Dropping invalid signaling message"
  • Server sends: { "type": "control", "action": "schema-error" }
  • Message not forwarded
  • signaling_schema_rejects_total metric incremented

Missing Required Field

Sent:
Result:
  • Schema validation fails on missing sdp
  • Server logs: "Dropping invalid signaling message"
  • Server sends: { "type": "control", "action": "schema-error" }
  • Message not forwarded

Empty SDP String

Sent:
Result:
  • Schema validation fails (minimum length: 1)
  • Server logs: "Dropping invalid signaling message"
  • Server sends: { "type": "control", "action": "schema-error" }
  • Message not forwarded

Malformed JSON

Sent:
Result:
  • JSON parse fails
  • Server logs: "Dropping non-JSON client message"
  • No control message sent (can’t send to unparseable client)
  • Message not forwarded

Message Size Limits

Maximum message size: messageMaxBytes (default: 64KB) Oversized message behavior:
Typical message sizes:
  • ICE candidate: 100-300 bytes
  • SDP offer: 2-10 KB
  • SDP answer: 2-10 KB
  • Control message: 50-100 bytes
For very large SDP descriptions (multiple codecs, many media lines), consider stripping unnecessary attributes or using SDP compression.

Rate Limiting Impact

Messages are subject to multiple rate limits:
  1. Per-client token bucket: 100 msg/10s (default)
  2. Per-IP rate limit: 500 msg/10s (default)
  3. Per-room rate limit: 1000 msg/10s (default)
When rate limited:
  • Message dropped silently
  • Server logs: "Rate limit exceeded, dropping message"
  • signaling_rate_limit_drops_total metric incremented
  • No error message sent to client
Best practice: Implement client-side rate limiting and exponential backoff.

Schema Validation Implementation

The server uses Zod for runtime type validation:

TypeScript Definitions

For TypeScript clients, use these type definitions:

Testing Messages

Test message validation using the WebSocket connection: