sctp: implement RFC 6525 stream reset for DataChannel close#39
Merged
Conversation
DataChannel.close() previously flipped local state only — it never sent an
OUTGOING_SSN_RESET_REQUEST, the SCTP layer dropped incoming RE-CONFIG
chunks (declared but unparsed), and the W3C `closing` state plus
`onclosing` event were missing. The result: closing a channel never told
the peer, and a remote-initiated close was silently ignored, so neither
side fired onclose.
Implement SCTP stream reconfiguration (RFC 6525 / RFC 8831 §6.7):
- chunk.dart: RE-CONFIG chunk (0x82) + Outgoing/Incoming SSN Reset Request
and Re-config Response parameters, with encode + parse.
- state_machine.dart: resetStreams() sends an Outgoing SSN Reset Request
(one in flight at a time, with a retransmit timer bounded by Max.Retrans);
incoming requests reset the inbound stream, respond, and reset our own
outbound side too (loop-guarded) so the channel closes both ways;
responses finalize the outgoing reset. Streams are reported once via a
new onStreamReset callback. Duplicate requests resend the cached response.
- state_machine.dart: advertise RE-CONFIG support in INIT/INIT-ACK via the
Supported Extensions Parameter (0x8008). Without this Chrome's dcSCTP
rejects close() with UNSUPPORTED_OPERATION ("peer doesn't support it") —
caught only by the live-Chrome e2e below.
- data_channel.dart: real `closing` state + `onClosing` event; close()
initiates the reset and defers `closed`/onClose to _finalizeClose() when
the reset completes (immediate close when there is no association).
- peer_connection.dart: wire close → resetStreams and onStreamReset →
_finalizeClose; PC.close() finalizes channels immediately.
Tested with a webdartc↔webdartc loopback reset, chunk round-trips,
duplicate-request handling, DataChannel close-state transitions, and a new
live-Chrome e2e (Scenario 1): webdartc closes the channel and both peers
fire onclose (webdartc's onClose + Chrome's dcClosed). Full suite incl.
e2e passes.
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.
Closes the "DataChannel close doesn't send SCTP stream reset (RECONFIG)" BACKLOG item (RFC 6525 / RFC 8831 §6.7).
Problem
DataChannel.close()flipped local state only — it never sent anOUTGOING_SSN_RESET_REQUEST, the SCTP layer dropped incoming RE-CONFIG chunks (declared but unparsed), and the W3Cclosingstate +onclosingevent were missing. Result: closing a channel never told the peer, and a remote-initiated close was silently ignored, so neither side firedonclose.Change
_encodeTlv/_concatBytes.resetStreams()sends an Outgoing SSN Reset Request (one in flight at a time, retransmit timer bounded by Max.Retrans); incoming requests reset the inbound stream, respond, and reset our own outbound side too (loop-guarded) so the channel closes both ways; responses finalize the reset. Streams reported once via a newonStreamResetcallback; duplicate requests resend the cached response.close()withUNSUPPORTED_OPERATION("peer doesn't support it") — a gap the live-Chrome e2e caught that loopback tests could not.closingstate +onClosingevent;close()initiates the reset and defersclosed/onCloseto_finalizeClose()when the reset completes (immediate close when there's no association).close→resetStreamsandonStreamReset→_finalizeClose;PeerConnection.close()finalizes channels immediately.Tests
onClose+ Chrome'sdcClosed).dart analyzeclean; full suite passes (635 unit + 22 e2e). Reviewed with/simplify.🤖 Generated with Claude Code