T6 wiring: Trunking in relay hot path
- TrunkedForwarder wraps transport with TrunkBatcher
- run_participant uses 5ms flush timer when trunking enabled
- send_trunk/recv_trunk on QuinnTransport
- --trunking flag on relay config
- 2 new tests: forwarder batches, auto-flush on full
T7 wiring: Mini-frames in encoder/decoder
- MediaPacket::encode_compact/decode_compact with MiniFrameContext
- CallEncoder sends mini-headers for consecutive frames (full every 50th)
- CallDecoder auto-detects full vs mini on receive
- mini_frames_enabled in CallConfig (default true)
- 3 new tests: encode/decode sequence, periodic full, disabled mode
Noise suppression (nnnoiseless/RNNoise)
- NoiseSupressor in wzp-codec: pure Rust ML-based noise removal
- Processes 960-sample frames as two 480-sample halves
- Integrated in CallEncoder before silence detection
- noise_suppression in CallConfig (default true)
- 4 new tests: creation, processing, SNR improvement, passthrough
T1-S4: Adaptive playout delay
- AdaptivePlayoutDelay: EMA-based jitter tracking (NetEq-inspired)
- Computes target_delay from observed inter-arrival jitter
- JitterBuffer::new_adaptive() uses adaptive delay
- adaptive_jitter in CallConfig (default true)
- 5 new tests: stable, jitter increase, recovery, clamping, estimate
272 tests passing across all crates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WZP-S-4: Room access control
- hash_room_name() in wzp-crypto: SHA-256("featherchat-group:"+name)[:16]
- CLI --room flag hashes before SNI, web bridge does the same
- RoomManager gains ACL: with_acl(), allow(), is_authorized()
- join() returns Result, rejects unauthorized fingerprints
WZP-S-5: Crypto handshake wired into all live paths
- CLI: perform_handshake() after connect, before any mode
- Relay: accept_handshake() after auth, before room join
- Web bridge: perform_handshake() after auth, before audio
- Relay generates ephemeral identity at startup
WZP-S-6: Web bridge featherChat auth
- --auth-url flag: browsers send {"type":"auth","token":"..."} as first WS msg
- Validates against featherChat, passes token to relay
- --cert/--key flags for production TLS (replaces self-signed)
WZP-S-7: wzp-proto standalone
- Cargo.toml uses explicit versions (no workspace inheritance)
- FC can use as git dependency
WZP-S-9: All 6 hardcoded assumptions resolved
- Auth, hashed rooms, mandatory handshake, real TLS certs,
profile negotiation, token validation
CLI also gains --room and --token flags.
179 tests passing across all crates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WZP-S-2: Relay token authentication
- New --auth-url flag: relay calls POST {url} with bearer token
- Clients must send SignalMessage::AuthToken as first signal
- Relay validates against featherChat's /v1/auth/validate endpoint
- Rejects unauthenticated clients before they join rooms
- New auth.rs module with validate_token() + tests
WZP-S-3: featherChat signaling bridge
- New featherchat.rs module for CallSignal interop
- WzpCallPayload: wraps SignalMessage + relay_addr + room name
- encode_call_payload/decode_call_payload for JSON serialization
- CallSignalType enum mirrors featherChat's variant
- signal_to_call_type maps WZP signals to FC types
Protocol: Added SignalMessage::AuthToken { token } variant
129 tests passing across all crates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Room-based SFU relay:
- Clients join named rooms (room name from QUIC SNI)
- Each participant's packets forwarded to all others (no mixing)
- Multiple rooms run concurrently on one relay
- Web bridge passes room name from URL path to relay
Push-to-talk (radio mode):
- Toggle "Radio mode" checkbox after connecting
- Hold PTT button or spacebar to transmit
- Release to mute mic (receive-only)
- Works on desktop (spacebar) and mobile (touch)
URL routing:
- /myroom → joins room "myroom"
- Room name input field as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bridge mode rewrite:
- First client echoes while waiting, checks every 100ms if paired
- Second client triggers bridge immediately, first exits echo loop
- After bridge ends, slot is cleared for the next pair
- No more two tasks competing for the same transport recv
Web client auto-reconnect:
- On WebSocket close/error, automatically reconnects after 1s
- Keeps retrying as long as the user hasn't clicked Disconnect
Test fix:
- Install rustls crypto provider in transport config tests
(fixes race condition when running full workspace tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use power-of-2 buffer (1024) for ScriptProcessorNode
- Accumulate samples and send exact 960-sample frames
- Remove unused watch import from relay
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When no --remote is configured, the relay now operates in bridge mode:
- First client connects → echoes while waiting for a peer
- Second client connects → both clients are bridged bidirectionally
- A's packets go to B, B's packets go to A
- Stats logged every 5 seconds (a_to_b / b_to_a packet counts)
- Falls back to echo if only one client connects
This enables the core use case: two clients on different networks
calling each other through a single relay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- --record now handles Ctrl+C: saves PCM file before exiting
- Relay without --remote runs in echo mode (loops packets back to sender)
instead of sink mode, enabling single-relay audio testing
- recv task returns collected PCM via channel for clean file write
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>