Survive changing networks and deepen the receive buffer
Build and test / test (macos-latest) (push) Waiting to run
Build and test / test (ubuntu-24.04) (push) Waiting to run
Build and test / test (windows-latest) (push) Waiting to run
Build and test / apple-client (push) Waiting to run

Media died silently whenever a client's source address changed. The relay bound a
peer's endpoint once and refused to move it, and the client stopped offering its
binding token after the first bind, so a Wi-Fi/cellular handover stranded the
session in both directions. Add an authenticated Rebind media frame: the binding
token travels in the clear for peer lookup only, and the AEAD tag over header and
token plus the peer's existing replay window are what authorize the move, so a
captured rebind cannot be replayed to redirect someone else's downlink. The client
rebuilds its UDP socket instead of retrying on one still pinned to a vanished
interface.

Nothing judged the control connection live: pings were sent and pongs ignored, so a
blackholed TCP path went unnoticed for minutes while the UI showed a live session.
Treat any server traffic as liveness and fail the connection when it stops, which
drives the existing reconnect.

The receive jitter buffer had lost its depth floor, so a channel without FEC or
DRED played out with no buffer at all and ordinary reordering became concealment.
Restore a one-frame floor, observe every arrival rather than only accepted ones —
a shallow buffer was rejecting the late arrivals that should have deepened it —
and allow playout to hold a frame so depth can follow a degrading link. A stalled
consumer now sheds the oldest queued packet instead of refusing the live talkspurt.

Add a deterministic network-impairment simulation covering bursty loss, jitter,
reordering, duplication, outages and a stalled consumer, a handover test against a
real relay, a replay test for the rebind path, and a blackholed control connection
driven through a freezable TCP proxy.
This commit is contained in:
2026-09-24 19:15:16 +02:00
parent fb740bcfb2
commit f3ac779bf4
12 changed files with 612 additions and 41 deletions
+11 -1
View File
@@ -29,6 +29,15 @@ The iOS user list now opens a remote-user detail view with independent tuning fo
audio stream. Private messages are grouped into per-user conversations with direct access to the
same user and audio controls.
The media path now survives changing networks. A client whose source address changes proves
possession of its media key from the new address with an authenticated `Rebind` frame and the
relay moves its endpoint, instead of the session dying silently in both directions; the client
rebuilds its UDP socket rather than retrying on one pinned to a vanished interface. The control
connection is judged live by server traffic rather than assumed live, so a blackholed TCP path
is detected in 30 s instead of waiting minutes for the OS. The receive jitter buffer keeps a
one-frame depth floor, measures late and reordered arrivals, and can deepen mid-call, and a
stalled consumer now costs bounded audio rather than the live talkspurt.
SQLite schema v4 persists DRED and the channel packet-loss mode. Manual loss remains the default;
automatic Fast/Balanced/Stable modes measure each sender's authenticated UDP uplink at the server,
cap the applied Opus hint at 30%, and feed it back over TLS.
@@ -47,7 +56,8 @@ cap the applied Opus hint at 30%, and feed it back over TLS.
counters (the render callback now paces the mix and the 20 ms capture handoff, and a watchdog
rebuilds a graph that stops calling back). Take a Siri or phone-call interruption while
backgrounded and confirm audio resumes without foregrounding. Complete a
30-minute iOS call and Wi-Fi/cellular switching with voice restoration, plus extended
30-minute iOS call and Wi-Fi/cellular switching with voice restoration (the switch is covered
by simulation in `NetworkImpairmentTests`; hardware confirms the real route change), plus extended
mono/stereo/voice-chat switching while joined. Verify Windows desktop/per-app stereo sharing.
- Complete Developer ID signing/notarization. The iOS host and ReplayKit extension have been
distribution-signed and packaged locally; upload the IPA for Apple's server-side validation.