Implement proper SCTP data channel closing procedure.
The proper closing procedure is:
1. Alice resets outgoing stream.
2. Bob receives incoming stream reset, resets his outgoing stream.
3. Alice receives incoming stream reset; channel closed!
4. Bob receives acknowledgement of reset; channel closed!
https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.7
However, up until now we've been sending both an incoming and outgoing reset
from the side initiating the closing procedure, and doing nothing on the remote
side.
This means that if you call "Close" and the remote endpoint is using an old
version of WebRTC, the channel's state will be stuck at "closing" since the
remote endpoint won't send a reset. Which is already what happens when Firefox
is talking to Chrome.
This CL also fixes an issue where the DataChannel's state prematurely went to
"closed" before the closing procedure was complete. Which could result in a
new DataChannel attempting to re-use the ID and failing.
TBR=magjed@webrtc.org
Bug: chromium:449934, webrtc:4453
Change-Id: Ic1ba813e46538c6c65868961aae6a9780d68a5e2
Reviewed-on: https://webrtc-review.googlesource.com/79061
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Commit-Queue: Taylor Brandstetter <deadbeef@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#23478}
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index 40aced4..0a984a7 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -4479,6 +4479,8 @@
++it) {
if (it->get() == channel) {
if (channel->id() >= 0) {
+ // After the closing procedure is done, it's safe to use this ID for
+ // another data channel.
sid_allocator_.ReleaseSid(channel->id());
}
// Since this method is triggered by a signal from the DataChannel,
@@ -5062,8 +5064,10 @@
&DataChannel::OnChannelReady);
SignalSctpDataReceived.connect(webrtc_data_channel,
&DataChannel::OnDataReceived);
- SignalSctpStreamClosedRemotely.connect(
- webrtc_data_channel, &DataChannel::OnStreamClosedRemotely);
+ SignalSctpClosingProcedureStartedRemotely.connect(
+ webrtc_data_channel, &DataChannel::OnClosingProcedureStartedRemotely);
+ SignalSctpClosingProcedureComplete.connect(
+ webrtc_data_channel, &DataChannel::OnClosingProcedureComplete);
}
return true;
}
@@ -5081,7 +5085,8 @@
} else {
SignalSctpReadyToSendData.disconnect(webrtc_data_channel);
SignalSctpDataReceived.disconnect(webrtc_data_channel);
- SignalSctpStreamClosedRemotely.disconnect(webrtc_data_channel);
+ SignalSctpClosingProcedureStartedRemotely.disconnect(webrtc_data_channel);
+ SignalSctpClosingProcedureComplete.disconnect(webrtc_data_channel);
}
}
@@ -5601,8 +5606,14 @@
this, &PeerConnection::OnSctpTransportReadyToSendData_n);
sctp_transport_->SignalDataReceived.connect(
this, &PeerConnection::OnSctpTransportDataReceived_n);
- sctp_transport_->SignalStreamClosedRemotely.connect(
- this, &PeerConnection::OnSctpStreamClosedRemotely_n);
+ // TODO(deadbeef): All we do here is AsyncInvoke to fire the signal on
+ // another thread. Would be nice if there was a helper class similar to
+ // sigslot::repeater that did this for us, eliminating a bunch of boilerplate
+ // code.
+ sctp_transport_->SignalClosingProcedureStartedRemotely.connect(
+ this, &PeerConnection::OnSctpClosingProcedureStartedRemotely_n);
+ sctp_transport_->SignalClosingProcedureComplete.connect(
+ this, &PeerConnection::OnSctpClosingProcedureComplete_n);
sctp_mid_ = mid;
sctp_transport_->SetDtlsTransport(dtls_transport);
return true;
@@ -5674,13 +5685,22 @@
}
}
-void PeerConnection::OnSctpStreamClosedRemotely_n(int sid) {
+void PeerConnection::OnSctpClosingProcedureStartedRemotely_n(int sid) {
RTC_DCHECK(data_channel_type_ == cricket::DCT_SCTP);
RTC_DCHECK(network_thread()->IsCurrent());
sctp_invoker_->AsyncInvoke<void>(
RTC_FROM_HERE, signaling_thread(),
rtc::Bind(&sigslot::signal1<int>::operator(),
- &SignalSctpStreamClosedRemotely, sid));
+ &SignalSctpClosingProcedureStartedRemotely, sid));
+}
+
+void PeerConnection::OnSctpClosingProcedureComplete_n(int sid) {
+ RTC_DCHECK(data_channel_type_ == cricket::DCT_SCTP);
+ RTC_DCHECK(network_thread()->IsCurrent());
+ sctp_invoker_->AsyncInvoke<void>(
+ RTC_FROM_HERE, signaling_thread(),
+ rtc::Bind(&sigslot::signal1<int>::operator(),
+ &SignalSctpClosingProcedureComplete, sid));
}
// Returns false if bundle is enabled and rtcp_mux is disabled.