From 1dea4fa8d9e769cbde0a08f8825795459013d2f9 Mon Sep 17 00:00:00 2001 From: nightness Date: Wed, 1 Apr 2026 06:09:26 -0500 Subject: [PATCH] fix(datachannel): send DataChannelOpen for pre-negotiated channels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes webrtc-rs/rtc#61 — negotiated DataChannels could open but not send. Root cause: DataChannel::dial() skipped queuing the DataChannelOpen DCEP message for pre-negotiated channels (negotiated=true). The SctpHandler calls conn.open_stream() only when it processes a DataChannelOpen write, so no stream was ever registered in the SCTP association. Any subsequent write failed with "Stream not existed". Fix: always queue DataChannelOpen in dial(), for both negotiated and non-negotiated channels. The DCEP exchange opens the SCTP stream on both sides. Also handle ErrStreamAlreadyExist in the DataChannelOpen write path: when both pre-negotiated peers race to send DataChannelOpen simultaneously the remote's message may auto-create the stream first via get_or_create_stream; treat that as non-fatal. Co-Authored-By: Claude Sonnet 4.6 --- rtc-datachannel/src/data_channel/mod.rs | 39 ++++++++++++++----------- rtc/src/peer_connection/handler/sctp.rs | 15 +++++++++- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/rtc-datachannel/src/data_channel/mod.rs b/rtc-datachannel/src/data_channel/mod.rs index 1411abd1..85a19587 100644 --- a/rtc-datachannel/src/data_channel/mod.rs +++ b/rtc-datachannel/src/data_channel/mod.rs @@ -71,23 +71,28 @@ impl DataChannel { ) -> Result { let mut data_channel = DataChannel::new(config.clone(), association_handle, stream_id); - if !config.negotiated { - let msg = Message::DataChannelOpen(DataChannelOpen { - channel_type: config.channel_type, - priority: config.priority, - reliability_parameter: config.reliability_parameter, - label: config.label.bytes().collect(), - protocol: config.protocol.bytes().collect(), - }) - .marshal()?; - - data_channel.write_outs.push_back(DataChannelMessage { - association_handle, - stream_id, - ppi: PayloadProtocolIdentifier::Dcep, - payload: msg, - }); - } + // Send DataChannelOpen for all channels — including out-of-band negotiated ones. + // + // For non-negotiated channels this initiates the DCEP handshake per RFC 8832 §3. + // For pre-negotiated channels (negotiated=true) the DCEP exchange also opens the + // underlying SCTP stream on both sides. Without it the SCTP association never + // registers the stream, causing every subsequent write to fail with + // "Stream not existed" (issue webrtc-rs/rtc#61). + let msg = Message::DataChannelOpen(DataChannelOpen { + channel_type: config.channel_type, + priority: config.priority, + reliability_parameter: config.reliability_parameter, + label: config.label.bytes().collect(), + protocol: config.protocol.bytes().collect(), + }) + .marshal()?; + + data_channel.write_outs.push_back(DataChannelMessage { + association_handle, + stream_id, + ppi: PayloadProtocolIdentifier::Dcep, + payload: msg, + }); Ok(data_channel) } diff --git a/rtc/src/peer_connection/handler/sctp.rs b/rtc/src/peer_connection/handler/sctp.rs index 8f1d4f41..6bfd0ca2 100644 --- a/rtc/src/peer_connection/handler/sctp.rs +++ b/rtc/src/peer_connection/handler/sctp.rs @@ -281,7 +281,20 @@ impl<'a> sansio::Protocol s, + Err(Error::ErrStreamAlreadyExist) => { + conn.stream(message.stream_id)? + } + Err(e) => return Err(e), + }; stream.set_reliability_params( unordered, reliability_type,