sctp: discard pre-established DATA so webdartc↔webdartc data channels open#46
Merged
Conversation
… open A data channel opened webdartc↔webdartc never reached `open` — the opener's onOpen never fired (readyState stuck at connecting). Root cause was a DCEP ACK lost to an SCTP establishment race: - The server peer establishes on COOKIE-ECHO and immediately sends the DCEP DATA_CHANNEL_OPEN; it overtakes the COOKIE-ACK and reaches the client still in COOKIE-ECHOED. - `_handleData` processed that DATA regardless of state and fired onDataChannelOpen, but the DCEP ACK reply failed `sendData`'s `established` guard and was silently dropped — yet the OPEN was still SACKed, so the sender never retransmitted. Permanent stall. Fix, matching RFC 4960 §5.1 and pion/sctp: `_handleData` now discards DATA received before the association is established (and does NOT SACK it), so the peer's T3-rtx retransmits it once we reach `established`. To avoid the ~3s retransmit wait in the common case, the DCEP OPEN send is deferred to a microtask so the COOKIE-ACK is flushed to the wire first. Tested: a new webdartc↔webdartc loopback test opens a channel (both peers fire onOpen / ondatachannel) and exchanges a message — completes in <1s. Full suite + e2e (Chrome/Firefox) pass; closes the BACKLOG item. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes a real functional gap found during the bufferedAmount work: a data channel opened webdartc↔webdartc never reached
open— the opener'sonOpennever fired andreadyStatestayedconnecting. (All existing DC e2e tests use a browser, so this went unnoticed.)Root cause — a DCEP ACK lost to an SCTP establishment race
DATA_CHANNEL_OPEN, which overtakes the COOKIE-ACK and reaches the client still inCOOKIE-ECHOED._handleDataprocessed that DATA regardless of state and firedonDataChannelOpen, but the DCEPACKreply failedsendData'sestablishedguard and was silently dropped — yet the OPEN was still SACKed, so the sender never retransmitted. Permanent stall.How libwebrtc/pion avoid this: pion's
handleDatareturns early whenstate != established(RFC 4960 §5.1) — it discards the DATA without SACKing, so the sender's T3-rtx retransmits it after the receiver establishes.Fix
_handleData(state_machine.dart) discards DATA received beforeestablishedand does not SACK it — matching RFC 4960 §5.1 / pion. Correctness is now independent of chunk ordering (the peer's T3-rtx recovers).scheduleMicrotaskso the COOKIE-ACK is flushed to the wire first — avoids the ~3s retransmit wait in the common case.The two are layered deliberately: the guard is the load-bearing correctness fix; the microtask is a latency nicety.
Tests
onOpen/ondatachanneland a message round-trips — completes in <1s.dart analyzeclean; 665 unit + 22 e2e (Chrome/Firefox) pass; closes the BACKLOG item. A latent follow-up (gate_handleSack/_handleHeartbeat/_handleReconfigpre-established, pion parity) is recorded in BACKLOG.Reviewed with
/simplify.🤖 Generated with Claude Code