Skip to main content

Crate o_sfu

Crate o_sfu 

Source
Expand description

A Selective Forwarding Unit (SFU) for audio/video calls.

A SFU receives each participant’s media once and selectively forwards it to the others, so an N-party call costs one upload per sender rather than one per listener and no stream is transcoded or mixed. o-sfu runs this model as a dedicated server that handles room admission, routing topology, media policy, packet forwarding, signaling and telemetry. Applications provision rooms over HTTP, browsers connect over WebSocket and media travels over UDP with str0m terminating ICE, DTLS and SRTP.

§Core Concepts

o-sfu separates the control plane for admission and room policy, the routing plane for user-to-connection placement and the packet plane for RTP forwarding on worker loops.

  • Runtime: Owns the process lifecycle, the HTTP/WebSocket servers and graceful shutdown.
  • core::server::room::Room: The control plane boundary for a set of participants. It commits membership and media relationships.
  • o_sfu_router::Router: The routing plane, a pure, sans-I/O engine owning the placement graph that maps users to connections.
  • core::prelude::MediaSession: Orchestrates a user’s connection, bridging room intent to transport effects.
  • core::server::transport::MediaTransport: Owns the media workers and hides their threading model. Each worker holds a packet loop and applies projected routes to incoming datagrams.

§Architecture

Control and routing decisions happen above the packet loops, which apply the resulting transport state to UDP datagrams.

HTTP control API ----------------------> RoomManager
                                              |
WebSocket session -> SfuCore -> MediaSession  |
                             \                /
                              +----> Room <----+
                                     |
                                     +<----> Router
                                     |
                                     v
                               MediaTransport
                                |    |    |
                                v    v    v
                         RTC workers / packet loops
                        |            |              |
                     UDP:40001   UDP:40002      UDP:40003
                        |            |              |
                        v            v              v
                     fanout        fanout        fanout

§Admission Edge

Applications provision rooms through RoomManager::serve_room. WebSocket clients join through SfuCore::admit_user. Both paths require JWT authentication before admission.

                    +----------------------------------------+
                    |     Incoming HTTP / WebSocket I/O      |
                    +----------------------------------------+
                                        |
                                        v
                    +----------------------------------------+
                    |              Axum Router               |
                    +----------------------------------------+
                                        |
       +--------------------+-----------+-----------+--------------------+
       |                    |                       |                    |
       v                    v                       v                    v
 [ GET /v1/noop ]  [ GET /v1/channel ]     [ WebSocket / ]   [ Operator Routes ]   <-- Routes
 (Public Liveness) [ POST /v1/disconnect ] (Signaling Path)  - GET /v1/stats
       |                    |                     |          - GET /metrics
       |                    |                     |          - GET /internal/...
       |                    v                     v                  |
       |           +---------------+     +---------------+   +---------------+
       |           | VerifiedRoom  |     | Upgrade       |   | OperatorAccess|     <-- Extractors
       |           | VerifiedClaims|     | ConnectInfo   |   | (Route Layer) |
       |           | (JWT Header)  |     | 1st-Frame JWT |   | (Bearer/Local)|
       |           +---------------+     +---------------+   +---------------+
       |                    |                     |                  |
       v                    v                     v                  v
 +---------------+ +---------------+     +---------------+   +---------------+
 | noop          | | room          |     | upgrade       |   | stats         |     <-- Handlers
 |               | | disconnect    |     | -> admit_user |   | metrics       |
 | -> 200 JSON   | | -> Room State |     | -> WS Session |   | diagnostics_* |
 +---------------+ +---------------+     +---------------+   +---------------+

§Security Model

o-sfu secures two planes independently. Application-layer JWTs gate room admission on the control plane. str0m encrypts media on the packet plane with DTLS-SRTP. Signaling transport confidentiality is terminated at the deployment edge rather than in process.

control plane    JWT HS256           admission trust
packet plane     DTLS-SRTP (str0m)   media confidentiality
signaling wire   TLS at edge         transport confidentiality

§JWT Admission

Tokens are HS256 only. auth::verify rejects any other alg, checks the HMAC in constant time and enforces exp, nbf plus an iat future-skew bound. It caps token size at auth::MAX_JWT_TOKEN_BYTES. Two keys scope trust:

  • Server-to-server key: AUTH_KEY (base64, at least 32 bytes) verifies the HTTP http::CreateRoomQuery path through auth::HttpRoomClaims and auth::HttpDisconnectClaims. See config.
  • Per-room key: the request that creates the current room pins the signing key from the key or keySeed claim in auth::HttpRoomClaims. For more security, prefer the keySeed claim, which derives a per-room key with the AUTH_KEY and provided seed using the following KDF:
    room_key = Base64StdPad(HMAC-SHA256(
                   key = Base64Decode(AUTH_KEY),
                   message = Base64Decode(keySeed)
               ))
    WebSocket auth::WebSocketConnectClaims verify against that room key, never against AUTH_KEY.

Token carriage differs per surface: HTTP room creation uses the Authorization header, HTTP disconnect uses the request body and the WebSocket client sends a first-frame auth envelope decoded by websocket::decode_auth_payload_text. An unverified room id selects only a candidate key, then the same token is re-verified against it. Modern auth::WebSocketConnectClaims must name the selected room. Legacy Odoo tokens select it through the auth envelope’s channel and are normalized only after verification with that room’s key.

Admission establishes identity and room scope. It does not enforce the per-user permissions claim, which room state collapses to a marker.

§Signaling Ingress

Every authenticated client frame passes through the same decoder as the auth frame, websocket::decode_client_batch, which bounds parser work with static caps: websocket::MAX_CLIENT_FRAME_BYTES per frame and websocket::MAX_CLIENT_BATCH_ENVELOPES per batch. The Axum upgrade applies the frame cap at the socket and the decoder re-checks it. Oversized frames, oversized batches, malformed JSON, ambiguous routing metadata and unknown protocol tags reject as websocket::ClientBatchDecodeError and close the socket with a protocol-error code.

Two further bounds guard against resource exhaustion:

  • Pre-auth admission: global and per-origin permits cap concurrent unauthenticated sockets and return 503 once exhausted. See config.
  • Outbound backpressure: per-user fanout is a bounded queue by message count and by bytes. A consumer that falls behind is closed rather than buffered without limit.

A first-frame auth timeout rejects clients that never authenticate. A ping/pong health loop closes clients that stop responding. There is no per-session request-rate budget: the size, count and backpressure caps bound the work rather than metering a rate.

§Media Transport

str0m terminates ICE, DTLS and SRTP over UDP with the aws-lc-rs crypto backend. o-sfu builds and drives the str0m session but implements no DTLS or SRTP itself: it forwards already-decrypted RTP between sessions and hands outbound RTP back to str0m for SRTP protection.

  • Keying: the DTLS handshake derives SRTP keys per RFC 5764 DTLS-SRTP.
  • Certificate: str0m generates a self-signed certificate when each RTC session is built and advertises its SHA-256 fingerprint in the SDP offer. Accepting the answer stores the expected remote fingerprint. The DTLS handshake verifies it against the peer certificate.
  • ICE: o-sfu runs ICE-lite with a=setup:actpass and advertises ANNOUNCED_IP, so media UDP must reach the host directly.

§Signaling Transport

HTTP and WebSocket are served in plaintext in process. HTTPS and WSS are terminated by an external reverse proxy, so a forwarded scheme and client address are trusted only when the proxy is trusted through config (PROXY). Operator route access is documented by http.

§Room and Router Ownership

Room transitions produce typed commits while holding short exclusive state locks. RoomEffects consumes deferred transport, source-policy and WebSocket output work after the lock is released.

room state lock held                  lock released
+--------------------------------+    +------------------------------+
| validate user and connection   |    | MediaTransport commands      |
| commit room topology           |    | source-policy turn           |
| capture transition commit      |--->| websocket output             |
|                                |    | idempotent teardown          |
+--------------------------------+    +------------------------------+

o_sfu_router::Router owns exact user-to-connection placement. Receiver shadows are foreign local sessions derived from active consumer dependencies, disappearing with their final consumer.

§Signaling and Client Bundle

Browsers use SfuClient for connection, publication, subscription and room control. Signaling state stays in o_sfu_protocol::host::ProtocolCore and yields ordered o_sfu_protocol::host::Command values. The WASM bridge serializes those commands then maps protocol events to Odoo bundle updates that drive browser WebSocket, RTCPeerConnection and timer APIs.

SfuClient (public API)
       |
       v
BrowserRuntime
       |
       v
ProtocolCore (sans-I/O) -> Vec<Command>
       |
       v
WASM serialization -> TypeScript command union
       |
       v
BrowserRuntime -> WebSocket, RTCPeerConnection, timers
       ^                                        |
       +--------------- browser events ---------+

§Packet Path

core::server::transport::MediaTransport owns the media workers, which hold the packet loops. These loops receive UDP datagrams, drive WebRTC state (str0m), apply route tables and forward RTP.

UDP datagram
    |
    v
worker ingress and `str0m` drain
    |
    v
packet facts (source, RID and codec)
    |
    +-> origin packet sinks
    |
    +-> source and destination packet gates
             |
             +-> relay fanout
             +-> local RTC -> RTP identity and codec rewrite

str0m handles ICE, DTLS and SRTP. The private rtc::codec boundary keeps codec branching out of route planning. Origin packet sinks precede route gates so recording can observe a publisher without active receivers. Source, relay and receiver gates then narrow routed fanout. Same-process relays share payload data with another worker for local delivery.

Worker BWE and audio observations feed into room source policy, which updates route gates for later packets.

worker BWE and audio observations
               |
               v
       room source policy
               |
               v
  route gates for later packets

§Shutdown and Teardown

Teardown is explicit async work. Runtime::serve_listener stops listener acceptance, drains tracked web sockets and stops background tasks within config::HttpConfig::shutdown_timeout_ms.

Runtime::serve_listener
    |
    +-> stop listener
    +-> close tracker and cancel sessions
    +-> wait for tracker emptiness
    +-> stop source-policy sync and media workers

Missing worker-local sessions or media during teardown are successful no-ops. Unavailable workers or ownership mismatches are terminal.

§Observability

Monitored through the o_sfu_telemetry sub-crate. See http::telemetry for the HTTP contracts.

§Scaling

Rooms use one o_sfu_router::Router facade and can opt into additional same-process local routers through config::RoomWorkerPolicy. Joins stay on an assigned healthy packet loop. When no assigned worker has a known delay below the configured threshold, a join can attach a healthy worker not yet assigned to the room.

§Feature Flags

Core media behavior is configured at runtime through config, not Cargo features. The default feature otel-tracing enables OpenTelemetry tracing support through o_sfu_telemetry::TraceExportConfig. Other features are used strictly for tests and benchmarking.

§Sub-crates

CrateRole
o_sfu_rfcRFC-backed JWT, RTP, RTCP, SDP and WebRTC consts/types
o-sfu-modelShared call data (o_sfu_protocol::wire::UserId, etc.)
o_sfu_routerSans-I/O o_sfu_router::Router facade for room placement and routed media lifetimes
o_sfu_coreRoom engine, core::prelude::SourcePolicy, recording taps and core::server::transport::MediaTransport projection
o_sfu_protocolSans-I/O o_sfu_protocol::host::ProtocolCore and typed commands
o_sfu_telemetryTracing setup, metrics, diagnostics response types and graph payloads

§Reading Map

Modules§

application 🔒
auth
config
core
http
HTTP route and payload contracts.
runtime 🔒
Wires process services and drains them during shutdown.
websocket

Structs§

Runtime
Process services and lifecycle configuration.

Enums§

ServeError
Failure to serve or fully drain a Runtime.

Functions§

run
Errors