summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/media/sctp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/media/sctp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/media/sctp')
-rw-r--r--third_party/libwebrtc/media/sctp/OWNERS3
-rw-r--r--third_party/libwebrtc/media/sctp/dcsctp_transport.cc668
-rw-r--r--third_party/libwebrtc/media/sctp/dcsctp_transport.h141
-rw-r--r--third_party/libwebrtc/media/sctp/dcsctp_transport_unittest.cc251
-rw-r--r--third_party/libwebrtc/media/sctp/sctp_transport_factory.cc38
-rw-r--r--third_party/libwebrtc/media/sctp/sctp_transport_factory.h35
-rw-r--r--third_party/libwebrtc/media/sctp/sctp_transport_internal.h150
7 files changed, 1286 insertions, 0 deletions
diff --git a/third_party/libwebrtc/media/sctp/OWNERS b/third_party/libwebrtc/media/sctp/OWNERS
new file mode 100644
index 0000000000..da2f0178a8
--- /dev/null
+++ b/third_party/libwebrtc/media/sctp/OWNERS
@@ -0,0 +1,3 @@
+boivie@webrtc.org
+deadbeef@webrtc.org
+orphis@webrtc.org
diff --git a/third_party/libwebrtc/media/sctp/dcsctp_transport.cc b/third_party/libwebrtc/media/sctp/dcsctp_transport.cc
new file mode 100644
index 0000000000..525075468c
--- /dev/null
+++ b/third_party/libwebrtc/media/sctp/dcsctp_transport.cc
@@ -0,0 +1,668 @@
+/*
+ * Copyright 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "media/sctp/dcsctp_transport.h"
+
+#include <atomic>
+#include <cstdint>
+#include <limits>
+#include <utility>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "api/array_view.h"
+#include "media/base/media_channel.h"
+#include "net/dcsctp/public/dcsctp_socket_factory.h"
+#include "net/dcsctp/public/packet_observer.h"
+#include "net/dcsctp/public/text_pcap_packet_observer.h"
+#include "net/dcsctp/public/types.h"
+#include "p2p/base/packet_transport_internal.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/socket.h"
+#include "rtc_base/strings/string_builder.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/trace_event.h"
+#include "system_wrappers/include/clock.h"
+
+namespace webrtc {
+
+namespace {
+using ::dcsctp::SendPacketStatus;
+
+// When there is packet loss for a long time, the SCTP retry timers will use
+// exponential backoff, which can grow to very long durations and when the
+// connection recovers, it may take a long time to reach the new backoff
+// duration. By limiting it to a reasonable limit, the time to recover reduces.
+constexpr dcsctp::DurationMs kMaxTimerBackoffDuration =
+ dcsctp::DurationMs(3000);
+
+enum class WebrtcPPID : dcsctp::PPID::UnderlyingType {
+ // https://www.rfc-editor.org/rfc/rfc8832.html#section-8.1
+ kDCEP = 50,
+ // https://www.rfc-editor.org/rfc/rfc8831.html#section-8
+ kString = 51,
+ kBinaryPartial = 52, // Deprecated
+ kBinary = 53,
+ kStringPartial = 54, // Deprecated
+ kStringEmpty = 56,
+ kBinaryEmpty = 57,
+};
+
+WebrtcPPID ToPPID(DataMessageType message_type, size_t size) {
+ switch (message_type) {
+ case DataMessageType::kControl:
+ return WebrtcPPID::kDCEP;
+ case DataMessageType::kText:
+ return size > 0 ? WebrtcPPID::kString : WebrtcPPID::kStringEmpty;
+ case DataMessageType::kBinary:
+ return size > 0 ? WebrtcPPID::kBinary : WebrtcPPID::kBinaryEmpty;
+ }
+}
+
+absl::optional<DataMessageType> ToDataMessageType(dcsctp::PPID ppid) {
+ switch (static_cast<WebrtcPPID>(ppid.value())) {
+ case WebrtcPPID::kDCEP:
+ return DataMessageType::kControl;
+ case WebrtcPPID::kString:
+ case WebrtcPPID::kStringPartial:
+ case WebrtcPPID::kStringEmpty:
+ return DataMessageType::kText;
+ case WebrtcPPID::kBinary:
+ case WebrtcPPID::kBinaryPartial:
+ case WebrtcPPID::kBinaryEmpty:
+ return DataMessageType::kBinary;
+ }
+ return absl::nullopt;
+}
+
+absl::optional<cricket::SctpErrorCauseCode> ToErrorCauseCode(
+ dcsctp::ErrorKind error) {
+ switch (error) {
+ case dcsctp::ErrorKind::kParseFailed:
+ return cricket::SctpErrorCauseCode::kUnrecognizedParameters;
+ case dcsctp::ErrorKind::kPeerReported:
+ return cricket::SctpErrorCauseCode::kUserInitiatedAbort;
+ case dcsctp::ErrorKind::kWrongSequence:
+ case dcsctp::ErrorKind::kProtocolViolation:
+ return cricket::SctpErrorCauseCode::kProtocolViolation;
+ case dcsctp::ErrorKind::kResourceExhaustion:
+ return cricket::SctpErrorCauseCode::kOutOfResource;
+ case dcsctp::ErrorKind::kTooManyRetries:
+ case dcsctp::ErrorKind::kUnsupportedOperation:
+ case dcsctp::ErrorKind::kNoError:
+ case dcsctp::ErrorKind::kNotConnected:
+ // No SCTP error cause code matches those
+ break;
+ }
+ return absl::nullopt;
+}
+
+bool IsEmptyPPID(dcsctp::PPID ppid) {
+ WebrtcPPID webrtc_ppid = static_cast<WebrtcPPID>(ppid.value());
+ return webrtc_ppid == WebrtcPPID::kStringEmpty ||
+ webrtc_ppid == WebrtcPPID::kBinaryEmpty;
+}
+} // namespace
+
+DcSctpTransport::DcSctpTransport(rtc::Thread* network_thread,
+ rtc::PacketTransportInternal* transport,
+ Clock* clock)
+ : DcSctpTransport(network_thread,
+ transport,
+ clock,
+ std::make_unique<dcsctp::DcSctpSocketFactory>()) {}
+
+DcSctpTransport::DcSctpTransport(
+ rtc::Thread* network_thread,
+ rtc::PacketTransportInternal* transport,
+ Clock* clock,
+ std::unique_ptr<dcsctp::DcSctpSocketFactory> socket_factory)
+ : network_thread_(network_thread),
+ transport_(transport),
+ clock_(clock),
+ random_(clock_->TimeInMicroseconds()),
+ socket_factory_(std::move(socket_factory)),
+ task_queue_timeout_factory_(
+ *network_thread,
+ [this]() { return TimeMillis(); },
+ [this](dcsctp::TimeoutID timeout_id) {
+ socket_->HandleTimeout(timeout_id);
+ }) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ static std::atomic<int> instance_count = 0;
+ rtc::StringBuilder sb;
+ sb << debug_name_ << instance_count++;
+ debug_name_ = sb.Release();
+ ConnectTransportSignals();
+}
+
+DcSctpTransport::~DcSctpTransport() {
+ if (socket_) {
+ socket_->Close();
+ }
+}
+
+void DcSctpTransport::SetOnConnectedCallback(std::function<void()> callback) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ on_connected_callback_ = std::move(callback);
+}
+
+void DcSctpTransport::SetDataChannelSink(DataChannelSink* sink) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ data_channel_sink_ = sink;
+ if (data_channel_sink_ && ready_to_send_data_) {
+ data_channel_sink_->OnReadyToSend();
+ }
+}
+
+void DcSctpTransport::SetDtlsTransport(
+ rtc::PacketTransportInternal* transport) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ DisconnectTransportSignals();
+ transport_ = transport;
+ ConnectTransportSignals();
+ MaybeConnectSocket();
+}
+
+bool DcSctpTransport::Start(int local_sctp_port,
+ int remote_sctp_port,
+ int max_message_size) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK(max_message_size > 0);
+ RTC_DLOG(LS_INFO) << debug_name_ << "->Start(local=" << local_sctp_port
+ << ", remote=" << remote_sctp_port
+ << ", max_message_size=" << max_message_size << ")";
+
+ if (!socket_) {
+ dcsctp::DcSctpOptions options;
+ options.local_port = local_sctp_port;
+ options.remote_port = remote_sctp_port;
+ options.max_message_size = max_message_size;
+ options.max_timer_backoff_duration = kMaxTimerBackoffDuration;
+ // Don't close the connection automatically on too many retransmissions.
+ options.max_retransmissions = absl::nullopt;
+ options.max_init_retransmits = absl::nullopt;
+
+ std::unique_ptr<dcsctp::PacketObserver> packet_observer;
+ if (RTC_LOG_CHECK_LEVEL(LS_VERBOSE)) {
+ packet_observer =
+ std::make_unique<dcsctp::TextPcapPacketObserver>(debug_name_);
+ }
+
+ socket_ = socket_factory_->Create(debug_name_, *this,
+ std::move(packet_observer), options);
+ } else {
+ if (local_sctp_port != socket_->options().local_port ||
+ remote_sctp_port != socket_->options().remote_port) {
+ RTC_LOG(LS_ERROR)
+ << debug_name_ << "->Start(local=" << local_sctp_port
+ << ", remote=" << remote_sctp_port
+ << "): Can't change ports on already started transport.";
+ return false;
+ }
+ socket_->SetMaxMessageSize(max_message_size);
+ }
+
+ MaybeConnectSocket();
+
+ return true;
+}
+
+bool DcSctpTransport::OpenStream(int sid) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DLOG(LS_INFO) << debug_name_ << "->OpenStream(" << sid << ").";
+
+ StreamState stream_state;
+ stream_states_.insert_or_assign(dcsctp::StreamID(static_cast<uint16_t>(sid)),
+ stream_state);
+ return true;
+}
+
+bool DcSctpTransport::ResetStream(int sid) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DLOG(LS_INFO) << debug_name_ << "->ResetStream(" << sid << ").";
+ if (!socket_) {
+ RTC_LOG(LS_ERROR) << debug_name_ << "->ResetStream(sid=" << sid
+ << "): Transport is not started.";
+ return false;
+ }
+
+ dcsctp::StreamID streams[1] = {dcsctp::StreamID(static_cast<uint16_t>(sid))};
+
+ auto it = stream_states_.find(streams[0]);
+ if (it == stream_states_.end()) {
+ RTC_LOG(LS_ERROR) << debug_name_ << "->ResetStream(sid=" << sid
+ << "): Stream is not open.";
+ return false;
+ }
+
+ StreamState& stream_state = it->second;
+ if (stream_state.closure_initiated || stream_state.incoming_reset_done ||
+ stream_state.outgoing_reset_done) {
+ // The closing procedure was already initiated by the remote, don't do
+ // anything.
+ return false;
+ }
+ stream_state.closure_initiated = true;
+ socket_->ResetStreams(streams);
+ return true;
+}
+
+RTCError DcSctpTransport::SendData(int sid,
+ const SendDataParams& params,
+ const rtc::CopyOnWriteBuffer& payload) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DLOG(LS_VERBOSE) << debug_name_ << "->SendData(sid=" << sid
+ << ", type=" << static_cast<int>(params.type)
+ << ", length=" << payload.size() << ").";
+
+ if (!socket_) {
+ RTC_LOG(LS_ERROR) << debug_name_
+ << "->SendData(...): Transport is not started.";
+ return RTCError(RTCErrorType::INVALID_STATE);
+ }
+
+ // It is possible for a message to be sent from the signaling thread at the
+ // same time a data-channel is closing, but before the signaling thread is
+ // aware of it. So we need to keep track of currently active data channels and
+ // skip sending messages for the ones that are not open or closing.
+ // The sending errors are not impacting the data channel API contract as
+ // it is allowed to discard queued messages when the channel is closing.
+ auto stream_state =
+ stream_states_.find(dcsctp::StreamID(static_cast<uint16_t>(sid)));
+ if (stream_state == stream_states_.end()) {
+ RTC_LOG(LS_VERBOSE) << "Skipping message on non-open stream with sid: "
+ << sid;
+ return RTCError(RTCErrorType::INVALID_STATE);
+ }
+
+ if (stream_state->second.closure_initiated ||
+ stream_state->second.incoming_reset_done ||
+ stream_state->second.outgoing_reset_done) {
+ RTC_LOG(LS_VERBOSE) << "Skipping message on closing stream with sid: "
+ << sid;
+ return RTCError(RTCErrorType::INVALID_STATE);
+ }
+
+ auto max_message_size = socket_->options().max_message_size;
+ if (max_message_size > 0 && payload.size() > max_message_size) {
+ RTC_LOG(LS_WARNING) << debug_name_
+ << "->SendData(...): "
+ "Trying to send packet bigger "
+ "than the max message size: "
+ << payload.size() << " vs max of " << max_message_size;
+ return RTCError(RTCErrorType::INVALID_RANGE);
+ }
+
+ std::vector<uint8_t> message_payload(payload.cdata(),
+ payload.cdata() + payload.size());
+ if (message_payload.empty()) {
+ // https://www.rfc-editor.org/rfc/rfc8831.html#section-6.6
+ // SCTP does not support the sending of empty user messages. Therefore, if
+ // an empty message has to be sent, the appropriate PPID (WebRTC String
+ // Empty or WebRTC Binary Empty) is used, and the SCTP user message of one
+ // zero byte is sent.
+ message_payload.push_back('\0');
+ }
+
+ dcsctp::DcSctpMessage message(
+ dcsctp::StreamID(static_cast<uint16_t>(sid)),
+ dcsctp::PPID(static_cast<uint16_t>(ToPPID(params.type, payload.size()))),
+ std::move(message_payload));
+
+ dcsctp::SendOptions send_options;
+ send_options.unordered = dcsctp::IsUnordered(!params.ordered);
+ if (params.max_rtx_ms.has_value()) {
+ RTC_DCHECK(*params.max_rtx_ms >= 0 &&
+ *params.max_rtx_ms <= std::numeric_limits<uint16_t>::max());
+ send_options.lifetime = dcsctp::DurationMs(*params.max_rtx_ms);
+ }
+ if (params.max_rtx_count.has_value()) {
+ RTC_DCHECK(*params.max_rtx_count >= 0 &&
+ *params.max_rtx_count <= std::numeric_limits<uint16_t>::max());
+ send_options.max_retransmissions = *params.max_rtx_count;
+ }
+
+ dcsctp::SendStatus error = socket_->Send(std::move(message), send_options);
+ switch (error) {
+ case dcsctp::SendStatus::kSuccess:
+ return RTCError::OK();
+ case dcsctp::SendStatus::kErrorResourceExhaustion:
+ ready_to_send_data_ = false;
+ return RTCError(RTCErrorType::RESOURCE_EXHAUSTED);
+ default:
+ absl::string_view message = dcsctp::ToString(error);
+ RTC_LOG(LS_ERROR) << debug_name_
+ << "->SendData(...): send() failed with error "
+ << message << ".";
+ return RTCError(RTCErrorType::NETWORK_ERROR, message);
+ }
+}
+
+bool DcSctpTransport::ReadyToSendData() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ return ready_to_send_data_;
+}
+
+int DcSctpTransport::max_message_size() const {
+ if (!socket_) {
+ RTC_LOG(LS_ERROR) << debug_name_
+ << "->max_message_size(...): Transport is not started.";
+ return 0;
+ }
+ return socket_->options().max_message_size;
+}
+
+absl::optional<int> DcSctpTransport::max_outbound_streams() const {
+ if (!socket_)
+ return absl::nullopt;
+ return socket_->options().announced_maximum_outgoing_streams;
+}
+
+absl::optional<int> DcSctpTransport::max_inbound_streams() const {
+ if (!socket_)
+ return absl::nullopt;
+ return socket_->options().announced_maximum_incoming_streams;
+}
+
+void DcSctpTransport::set_debug_name_for_testing(const char* debug_name) {
+ debug_name_ = debug_name;
+}
+
+SendPacketStatus DcSctpTransport::SendPacketWithStatus(
+ rtc::ArrayView<const uint8_t> data) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK(socket_);
+
+ if (data.size() > (socket_->options().mtu)) {
+ RTC_LOG(LS_ERROR) << debug_name_
+ << "->SendPacket(...): "
+ "SCTP seems to have made a packet that is bigger "
+ "than its official MTU: "
+ << data.size() << " vs max of " << socket_->options().mtu;
+ return SendPacketStatus::kError;
+ }
+ TRACE_EVENT0("webrtc", "DcSctpTransport::SendPacket");
+
+ if (!transport_ || !transport_->writable())
+ return SendPacketStatus::kError;
+
+ RTC_DLOG(LS_VERBOSE) << debug_name_ << "->SendPacket(length=" << data.size()
+ << ")";
+
+ auto result =
+ transport_->SendPacket(reinterpret_cast<const char*>(data.data()),
+ data.size(), rtc::PacketOptions(), 0);
+
+ if (result < 0) {
+ RTC_LOG(LS_WARNING) << debug_name_ << "->SendPacket(length=" << data.size()
+ << ") failed with error: " << transport_->GetError()
+ << ".";
+
+ if (rtc::IsBlockingError(transport_->GetError())) {
+ return SendPacketStatus::kTemporaryFailure;
+ }
+ return SendPacketStatus::kError;
+ }
+ return SendPacketStatus::kSuccess;
+}
+
+std::unique_ptr<dcsctp::Timeout> DcSctpTransport::CreateTimeout(
+ TaskQueueBase::DelayPrecision precision) {
+ return task_queue_timeout_factory_.CreateTimeout(precision);
+}
+
+dcsctp::TimeMs DcSctpTransport::TimeMillis() {
+ return dcsctp::TimeMs(clock_->TimeInMilliseconds());
+}
+
+uint32_t DcSctpTransport::GetRandomInt(uint32_t low, uint32_t high) {
+ return random_.Rand(low, high);
+}
+
+void DcSctpTransport::OnTotalBufferedAmountLow() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!ready_to_send_data_) {
+ ready_to_send_data_ = true;
+ if (data_channel_sink_) {
+ data_channel_sink_->OnReadyToSend();
+ }
+ }
+}
+
+void DcSctpTransport::OnMessageReceived(dcsctp::DcSctpMessage message) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DLOG(LS_VERBOSE) << debug_name_ << "->OnMessageReceived(sid="
+ << message.stream_id().value()
+ << ", ppid=" << message.ppid().value()
+ << ", length=" << message.payload().size() << ").";
+ auto type = ToDataMessageType(message.ppid());
+ if (!type.has_value()) {
+ RTC_LOG(LS_VERBOSE) << debug_name_
+ << "->OnMessageReceived(): Received an unknown PPID "
+ << message.ppid().value()
+ << " on an SCTP packet. Dropping.";
+ return;
+ }
+ receive_buffer_.Clear();
+ if (!IsEmptyPPID(message.ppid()))
+ receive_buffer_.AppendData(message.payload().data(),
+ message.payload().size());
+
+ if (data_channel_sink_) {
+ data_channel_sink_->OnDataReceived(message.stream_id().value(), *type,
+ receive_buffer_);
+ }
+}
+
+void DcSctpTransport::OnError(dcsctp::ErrorKind error,
+ absl::string_view message) {
+ if (error == dcsctp::ErrorKind::kResourceExhaustion) {
+ // Indicates that a message failed to be enqueued, because the send buffer
+ // is full, which is a very common (and wanted) state for high throughput
+ // sending/benchmarks.
+ RTC_LOG(LS_VERBOSE) << debug_name_
+ << "->OnError(error=" << dcsctp::ToString(error)
+ << ", message=" << message << ").";
+ } else {
+ RTC_LOG(LS_ERROR) << debug_name_
+ << "->OnError(error=" << dcsctp::ToString(error)
+ << ", message=" << message << ").";
+ }
+}
+
+void DcSctpTransport::OnAborted(dcsctp::ErrorKind error,
+ absl::string_view message) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_LOG(LS_ERROR) << debug_name_
+ << "->OnAborted(error=" << dcsctp::ToString(error)
+ << ", message=" << message << ").";
+ ready_to_send_data_ = false;
+ RTCError rtc_error(RTCErrorType::OPERATION_ERROR_WITH_DATA,
+ std::string(message));
+ rtc_error.set_error_detail(RTCErrorDetailType::SCTP_FAILURE);
+ auto code = ToErrorCauseCode(error);
+ if (code.has_value()) {
+ rtc_error.set_sctp_cause_code(static_cast<uint16_t>(*code));
+ }
+ if (data_channel_sink_) {
+ data_channel_sink_->OnTransportClosed(rtc_error);
+ }
+}
+
+void DcSctpTransport::OnConnected() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DLOG(LS_INFO) << debug_name_ << "->OnConnected().";
+ ready_to_send_data_ = true;
+ if (data_channel_sink_) {
+ data_channel_sink_->OnReadyToSend();
+ }
+ if (on_connected_callback_) {
+ on_connected_callback_();
+ }
+}
+
+void DcSctpTransport::OnClosed() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DLOG(LS_INFO) << debug_name_ << "->OnClosed().";
+ ready_to_send_data_ = false;
+}
+
+void DcSctpTransport::OnConnectionRestarted() {
+ RTC_DLOG(LS_INFO) << debug_name_ << "->OnConnectionRestarted().";
+}
+
+void DcSctpTransport::OnStreamsResetFailed(
+ rtc::ArrayView<const dcsctp::StreamID> outgoing_streams,
+ absl::string_view reason) {
+ // TODO(orphis): Need a test to check for correct behavior
+ for (auto& stream_id : outgoing_streams) {
+ RTC_LOG(LS_WARNING)
+ << debug_name_
+ << "->OnStreamsResetFailed(...): Outgoing stream reset failed"
+ << ", sid=" << stream_id.value() << ", reason: " << reason << ".";
+ }
+}
+
+void DcSctpTransport::OnStreamsResetPerformed(
+ rtc::ArrayView<const dcsctp::StreamID> outgoing_streams) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ for (auto& stream_id : outgoing_streams) {
+ RTC_LOG(LS_INFO) << debug_name_
+ << "->OnStreamsResetPerformed(...): Outgoing stream reset"
+ << ", sid=" << stream_id.value();
+
+ auto it = stream_states_.find(stream_id);
+ if (it == stream_states_.end()) {
+ // Ignoring an outgoing stream reset for a closed stream
+ return;
+ }
+
+ StreamState& stream_state = it->second;
+ stream_state.outgoing_reset_done = true;
+
+ if (stream_state.incoming_reset_done) {
+ // When the close was not initiated locally, we can signal the end of the
+ // data channel close procedure when the remote ACKs the reset.
+ if (data_channel_sink_) {
+ data_channel_sink_->OnChannelClosed(stream_id.value());
+ }
+ stream_states_.erase(stream_id);
+ }
+ }
+}
+
+void DcSctpTransport::OnIncomingStreamsReset(
+ rtc::ArrayView<const dcsctp::StreamID> incoming_streams) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ for (auto& stream_id : incoming_streams) {
+ RTC_LOG(LS_INFO) << debug_name_
+ << "->OnIncomingStreamsReset(...): Incoming stream reset"
+ << ", sid=" << stream_id.value();
+
+ auto it = stream_states_.find(stream_id);
+ if (it == stream_states_.end())
+ return;
+
+ StreamState& stream_state = it->second;
+ stream_state.incoming_reset_done = true;
+
+ if (!stream_state.closure_initiated) {
+ // When receiving an incoming stream reset event for a non local close
+ // procedure, the transport needs to reset the stream in the other
+ // direction too.
+ dcsctp::StreamID streams[1] = {stream_id};
+ socket_->ResetStreams(streams);
+ if (data_channel_sink_) {
+ data_channel_sink_->OnChannelClosing(stream_id.value());
+ }
+ }
+
+ if (stream_state.outgoing_reset_done) {
+ // The close procedure that was initiated locally is complete when we
+ // receive and incoming reset event.
+ if (data_channel_sink_) {
+ data_channel_sink_->OnChannelClosed(stream_id.value());
+ }
+ stream_states_.erase(stream_id);
+ }
+ }
+}
+
+void DcSctpTransport::ConnectTransportSignals() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!transport_) {
+ return;
+ }
+ transport_->SignalWritableState.connect(
+ this, &DcSctpTransport::OnTransportWritableState);
+ transport_->SignalReadPacket.connect(this,
+ &DcSctpTransport::OnTransportReadPacket);
+ transport_->SignalClosed.connect(this, &DcSctpTransport::OnTransportClosed);
+}
+
+void DcSctpTransport::DisconnectTransportSignals() {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!transport_) {
+ return;
+ }
+ transport_->SignalWritableState.disconnect(this);
+ transport_->SignalReadPacket.disconnect(this);
+ transport_->SignalClosed.disconnect(this);
+}
+
+void DcSctpTransport::OnTransportWritableState(
+ rtc::PacketTransportInternal* transport) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DCHECK_EQ(transport_, transport);
+ RTC_DLOG(LS_VERBOSE) << debug_name_
+ << "->OnTransportWritableState(), writable="
+ << transport->writable();
+ MaybeConnectSocket();
+}
+
+void DcSctpTransport::OnTransportReadPacket(
+ rtc::PacketTransportInternal* transport,
+ const char* data,
+ size_t length,
+ const int64_t& /* packet_time_us */,
+ int flags) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (flags) {
+ // We are only interested in SCTP packets.
+ return;
+ }
+
+ RTC_DLOG(LS_VERBOSE) << debug_name_
+ << "->OnTransportReadPacket(), length=" << length;
+ if (socket_) {
+ socket_->ReceivePacket(rtc::ArrayView<const uint8_t>(
+ reinterpret_cast<const uint8_t*>(data), length));
+ }
+}
+
+void DcSctpTransport::OnTransportClosed(
+ rtc::PacketTransportInternal* transport) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ RTC_DLOG(LS_VERBOSE) << debug_name_ << "->OnTransportClosed().";
+ if (data_channel_sink_) {
+ data_channel_sink_->OnTransportClosed({});
+ }
+}
+
+void DcSctpTransport::MaybeConnectSocket() {
+ if (transport_ && transport_->writable() && socket_ &&
+ socket_->state() == dcsctp::SocketState::kClosed) {
+ socket_->Connect();
+ }
+}
+} // namespace webrtc
diff --git a/third_party/libwebrtc/media/sctp/dcsctp_transport.h b/third_party/libwebrtc/media/sctp/dcsctp_transport.h
new file mode 100644
index 0000000000..7ae0d64134
--- /dev/null
+++ b/third_party/libwebrtc/media/sctp/dcsctp_transport.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MEDIA_SCTP_DCSCTP_TRANSPORT_H_
+#define MEDIA_SCTP_DCSCTP_TRANSPORT_H_
+
+#include <memory>
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "api/array_view.h"
+#include "api/task_queue/task_queue_base.h"
+#include "media/sctp/sctp_transport_internal.h"
+#include "net/dcsctp/public/dcsctp_options.h"
+#include "net/dcsctp/public/dcsctp_socket.h"
+#include "net/dcsctp/public/dcsctp_socket_factory.h"
+#include "net/dcsctp/public/types.h"
+#include "net/dcsctp/timer/task_queue_timeout.h"
+#include "p2p/base/packet_transport_internal.h"
+#include "rtc_base/containers/flat_map.h"
+#include "rtc_base/copy_on_write_buffer.h"
+#include "rtc_base/random.h"
+#include "rtc_base/third_party/sigslot/sigslot.h"
+#include "rtc_base/thread.h"
+#include "rtc_base/thread_annotations.h"
+#include "system_wrappers/include/clock.h"
+
+namespace webrtc {
+
+class DcSctpTransport : public cricket::SctpTransportInternal,
+ public dcsctp::DcSctpSocketCallbacks,
+ public sigslot::has_slots<> {
+ public:
+ DcSctpTransport(rtc::Thread* network_thread,
+ rtc::PacketTransportInternal* transport,
+ Clock* clock);
+ DcSctpTransport(rtc::Thread* network_thread,
+ rtc::PacketTransportInternal* transport,
+ Clock* clock,
+ std::unique_ptr<dcsctp::DcSctpSocketFactory> socket_factory);
+ ~DcSctpTransport() override;
+
+ // cricket::SctpTransportInternal
+ void SetOnConnectedCallback(std::function<void()> callback) override;
+ void SetDataChannelSink(DataChannelSink* sink) override;
+ void SetDtlsTransport(rtc::PacketTransportInternal* transport) override;
+ bool Start(int local_sctp_port,
+ int remote_sctp_port,
+ int max_message_size) override;
+ bool OpenStream(int sid) override;
+ bool ResetStream(int sid) override;
+ RTCError SendData(int sid,
+ const SendDataParams& params,
+ const rtc::CopyOnWriteBuffer& payload) override;
+ bool ReadyToSendData() override;
+ int max_message_size() const override;
+ absl::optional<int> max_outbound_streams() const override;
+ absl::optional<int> max_inbound_streams() const override;
+ void set_debug_name_for_testing(const char* debug_name) override;
+
+ private:
+ // dcsctp::DcSctpSocketCallbacks
+ dcsctp::SendPacketStatus SendPacketWithStatus(
+ rtc::ArrayView<const uint8_t> data) override;
+ std::unique_ptr<dcsctp::Timeout> CreateTimeout(
+ TaskQueueBase::DelayPrecision precision) override;
+ dcsctp::TimeMs TimeMillis() override;
+ uint32_t GetRandomInt(uint32_t low, uint32_t high) override;
+ void OnTotalBufferedAmountLow() override;
+ void OnMessageReceived(dcsctp::DcSctpMessage message) override;
+ void OnError(dcsctp::ErrorKind error, absl::string_view message) override;
+ void OnAborted(dcsctp::ErrorKind error, absl::string_view message) override;
+ void OnConnected() override;
+ void OnClosed() override;
+ void OnConnectionRestarted() override;
+ void OnStreamsResetFailed(
+ rtc::ArrayView<const dcsctp::StreamID> outgoing_streams,
+ absl::string_view reason) override;
+ void OnStreamsResetPerformed(
+ rtc::ArrayView<const dcsctp::StreamID> outgoing_streams) override;
+ void OnIncomingStreamsReset(
+ rtc::ArrayView<const dcsctp::StreamID> incoming_streams) override;
+
+ // Transport callbacks
+ void ConnectTransportSignals();
+ void DisconnectTransportSignals();
+ void OnTransportWritableState(rtc::PacketTransportInternal* transport);
+ void OnTransportReadPacket(rtc::PacketTransportInternal* transport,
+ const char* data,
+ size_t length,
+ const int64_t& /* packet_time_us */,
+ int flags);
+ void OnTransportClosed(rtc::PacketTransportInternal* transport);
+
+ void MaybeConnectSocket();
+
+ rtc::Thread* network_thread_;
+ rtc::PacketTransportInternal* transport_;
+ Clock* clock_;
+ Random random_;
+
+ std::unique_ptr<dcsctp::DcSctpSocketFactory> socket_factory_;
+ dcsctp::TaskQueueTimeoutFactory task_queue_timeout_factory_;
+ std::unique_ptr<dcsctp::DcSctpSocketInterface> socket_;
+ std::string debug_name_ = "DcSctpTransport";
+ rtc::CopyOnWriteBuffer receive_buffer_;
+
+ // Used to keep track of the state of data channels.
+ // Reset needs to happen both ways before signaling the transport
+ // is closed.
+ struct StreamState {
+ // True when the local connection has initiated the reset.
+ // If a connection receives a reset for a stream that isn't
+ // already being reset locally, it needs to fire the signal
+ // SignalClosingProcedureStartedRemotely.
+ bool closure_initiated = false;
+ // True when the local connection received OnIncomingStreamsReset
+ bool incoming_reset_done = false;
+ // True when the local connection received OnStreamsResetPerformed
+ bool outgoing_reset_done = false;
+ };
+
+ // Map of all currently open or closing data channels
+ flat_map<dcsctp::StreamID, StreamState> stream_states_
+ RTC_GUARDED_BY(network_thread_);
+ bool ready_to_send_data_ RTC_GUARDED_BY(network_thread_) = false;
+ std::function<void()> on_connected_callback_ RTC_GUARDED_BY(network_thread_);
+ DataChannelSink* data_channel_sink_ RTC_GUARDED_BY(network_thread_) = nullptr;
+};
+
+} // namespace webrtc
+
+#endif // MEDIA_SCTP_DCSCTP_TRANSPORT_H_
diff --git a/third_party/libwebrtc/media/sctp/dcsctp_transport_unittest.cc b/third_party/libwebrtc/media/sctp/dcsctp_transport_unittest.cc
new file mode 100644
index 0000000000..65fc3a1690
--- /dev/null
+++ b/third_party/libwebrtc/media/sctp/dcsctp_transport_unittest.cc
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "media/sctp/dcsctp_transport.h"
+
+#include <memory>
+#include <utility>
+
+#include "net/dcsctp/public/mock_dcsctp_socket.h"
+#include "net/dcsctp/public/mock_dcsctp_socket_factory.h"
+#include "p2p/base/fake_packet_transport.h"
+#include "test/gtest.h"
+
+using ::testing::_;
+using ::testing::ByMove;
+using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::NiceMock;
+using ::testing::Return;
+using ::testing::ReturnPointee;
+
+namespace webrtc {
+
+namespace {
+class MockDataChannelSink : public DataChannelSink {
+ public:
+ MOCK_METHOD(void, OnConnected, ());
+
+ // DataChannelSink
+ MOCK_METHOD(void,
+ OnDataReceived,
+ (int, DataMessageType, const rtc::CopyOnWriteBuffer&));
+ MOCK_METHOD(void, OnChannelClosing, (int));
+ MOCK_METHOD(void, OnChannelClosed, (int));
+ MOCK_METHOD(void, OnReadyToSend, ());
+ MOCK_METHOD(void, OnTransportClosed, (RTCError));
+};
+
+static_assert(!std::is_abstract_v<MockDataChannelSink>);
+
+class Peer {
+ public:
+ Peer() : fake_packet_transport_("transport"), simulated_clock_(1000) {
+ auto socket_ptr = std::make_unique<dcsctp::MockDcSctpSocket>();
+ socket_ = socket_ptr.get();
+
+ auto mock_dcsctp_socket_factory =
+ std::make_unique<dcsctp::MockDcSctpSocketFactory>();
+ EXPECT_CALL(*mock_dcsctp_socket_factory, Create)
+ .Times(1)
+ .WillOnce(Return(ByMove(std::move(socket_ptr))));
+
+ sctp_transport_ = std::make_unique<webrtc::DcSctpTransport>(
+ rtc::Thread::Current(), &fake_packet_transport_, &simulated_clock_,
+ std::move(mock_dcsctp_socket_factory));
+ sctp_transport_->SetDataChannelSink(&sink_);
+ sctp_transport_->SetOnConnectedCallback([this]() { sink_.OnConnected(); });
+ }
+
+ rtc::FakePacketTransport fake_packet_transport_;
+ webrtc::SimulatedClock simulated_clock_;
+ dcsctp::MockDcSctpSocket* socket_;
+ std::unique_ptr<webrtc::DcSctpTransport> sctp_transport_;
+ NiceMock<MockDataChannelSink> sink_;
+};
+} // namespace
+
+TEST(DcSctpTransportTest, OpenSequence) {
+ rtc::AutoThread main_thread;
+ Peer peer_a;
+ peer_a.fake_packet_transport_.SetWritable(true);
+
+ EXPECT_CALL(*peer_a.socket_, Connect)
+ .Times(1)
+ .WillOnce(Invoke(peer_a.sctp_transport_.get(),
+ &dcsctp::DcSctpSocketCallbacks::OnConnected));
+ EXPECT_CALL(peer_a.sink_, OnReadyToSend);
+ EXPECT_CALL(peer_a.sink_, OnConnected);
+
+ peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
+}
+
+// Tests that the close sequence invoked from one end results in the stream to
+// be reset from both ends and all the proper signals are sent.
+TEST(DcSctpTransportTest, CloseSequence) {
+ rtc::AutoThread main_thread;
+ Peer peer_a;
+ Peer peer_b;
+ peer_a.fake_packet_transport_.SetDestination(&peer_b.fake_packet_transport_,
+ false);
+ {
+ InSequence sequence;
+
+ EXPECT_CALL(*peer_a.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1))))
+ .WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed));
+
+ EXPECT_CALL(*peer_b.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1))))
+ .WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed));
+
+ EXPECT_CALL(peer_a.sink_, OnChannelClosing(1)).Times(0);
+ EXPECT_CALL(peer_b.sink_, OnChannelClosing(1));
+ EXPECT_CALL(peer_a.sink_, OnChannelClosed(1));
+ EXPECT_CALL(peer_b.sink_, OnChannelClosed(1));
+ }
+
+ peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
+ peer_b.sctp_transport_->Start(5000, 5000, 256 * 1024);
+ peer_a.sctp_transport_->OpenStream(1);
+ peer_b.sctp_transport_->OpenStream(1);
+ peer_a.sctp_transport_->ResetStream(1);
+
+ // Simulate the callbacks from the stream resets
+ dcsctp::StreamID streams[1] = {dcsctp::StreamID(1)};
+ static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
+ ->OnStreamsResetPerformed(streams);
+ static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get())
+ ->OnIncomingStreamsReset(streams);
+ static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
+ ->OnIncomingStreamsReset(streams);
+ static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get())
+ ->OnStreamsResetPerformed(streams);
+}
+
+// Tests that the close sequence initiated from both peers at the same time
+// terminates properly. Both peers will think they initiated it, so no
+// OnClosingProcedureStartedRemotely should be called.
+TEST(DcSctpTransportTest, CloseSequenceSimultaneous) {
+ rtc::AutoThread main_thread;
+ Peer peer_a;
+ Peer peer_b;
+ peer_a.fake_packet_transport_.SetDestination(&peer_b.fake_packet_transport_,
+ false);
+ {
+ InSequence sequence;
+
+ EXPECT_CALL(*peer_a.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1))))
+ .WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed));
+
+ EXPECT_CALL(*peer_b.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1))))
+ .WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed));
+
+ EXPECT_CALL(peer_a.sink_, OnChannelClosing(1)).Times(0);
+ EXPECT_CALL(peer_b.sink_, OnChannelClosing(1)).Times(0);
+ EXPECT_CALL(peer_a.sink_, OnChannelClosed(1));
+ EXPECT_CALL(peer_b.sink_, OnChannelClosed(1));
+ }
+
+ peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
+ peer_b.sctp_transport_->Start(5000, 5000, 256 * 1024);
+ peer_a.sctp_transport_->OpenStream(1);
+ peer_b.sctp_transport_->OpenStream(1);
+ peer_a.sctp_transport_->ResetStream(1);
+ peer_b.sctp_transport_->ResetStream(1);
+
+ // Simulate the callbacks from the stream resets
+ dcsctp::StreamID streams[1] = {dcsctp::StreamID(1)};
+ static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
+ ->OnStreamsResetPerformed(streams);
+ static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get())
+ ->OnStreamsResetPerformed(streams);
+ static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
+ ->OnIncomingStreamsReset(streams);
+ static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get())
+ ->OnIncomingStreamsReset(streams);
+}
+
+TEST(DcSctpTransportTest, DiscardMessageClosedChannel) {
+ rtc::AutoThread main_thread;
+ Peer peer_a;
+
+ EXPECT_CALL(*peer_a.socket_, Send(_, _)).Times(0);
+
+ peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
+
+ SendDataParams params;
+ rtc::CopyOnWriteBuffer payload;
+ EXPECT_EQ(peer_a.sctp_transport_->SendData(1, params, payload).type(),
+ RTCErrorType::INVALID_STATE);
+}
+
+TEST(DcSctpTransportTest, DiscardMessageClosingChannel) {
+ rtc::AutoThread main_thread;
+ Peer peer_a;
+
+ EXPECT_CALL(*peer_a.socket_, Send(_, _)).Times(0);
+
+ peer_a.sctp_transport_->OpenStream(1);
+ peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
+ peer_a.sctp_transport_->ResetStream(1);
+
+ SendDataParams params;
+ rtc::CopyOnWriteBuffer payload;
+ EXPECT_EQ(peer_a.sctp_transport_->SendData(1, params, payload).type(),
+ RTCErrorType::INVALID_STATE);
+}
+
+TEST(DcSctpTransportTest, SendDataOpenChannel) {
+ rtc::AutoThread main_thread;
+ Peer peer_a;
+ dcsctp::DcSctpOptions options;
+
+ EXPECT_CALL(*peer_a.socket_, Send(_, _)).Times(1);
+ EXPECT_CALL(*peer_a.socket_, options()).WillOnce(ReturnPointee(&options));
+
+ peer_a.sctp_transport_->OpenStream(1);
+ peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
+
+ SendDataParams params;
+ rtc::CopyOnWriteBuffer payload;
+ EXPECT_TRUE(peer_a.sctp_transport_->SendData(1, params, payload).ok());
+}
+
+TEST(DcSctpTransportTest, DeliversMessage) {
+ rtc::AutoThread main_thread;
+ Peer peer_a;
+
+ EXPECT_CALL(peer_a.sink_,
+ OnDataReceived(1, webrtc::DataMessageType::kBinary, _))
+ .Times(1);
+
+ peer_a.sctp_transport_->OpenStream(1);
+ peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
+
+ static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
+ ->OnMessageReceived(
+ dcsctp::DcSctpMessage(dcsctp::StreamID(1), dcsctp::PPID(53), {0}));
+}
+
+TEST(DcSctpTransportTest, DropMessageWithUnknownPpid) {
+ rtc::AutoThread main_thread;
+ Peer peer_a;
+
+ EXPECT_CALL(peer_a.sink_, OnDataReceived(_, _, _)).Times(0);
+
+ peer_a.sctp_transport_->OpenStream(1);
+ peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024);
+
+ static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get())
+ ->OnMessageReceived(
+ dcsctp::DcSctpMessage(dcsctp::StreamID(1), dcsctp::PPID(1337), {0}));
+}
+} // namespace webrtc
diff --git a/third_party/libwebrtc/media/sctp/sctp_transport_factory.cc b/third_party/libwebrtc/media/sctp/sctp_transport_factory.cc
new file mode 100644
index 0000000000..457bc5f889
--- /dev/null
+++ b/third_party/libwebrtc/media/sctp/sctp_transport_factory.cc
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "media/sctp/sctp_transport_factory.h"
+
+#include "rtc_base/system/unused.h"
+
+#ifdef WEBRTC_HAVE_DCSCTP
+#include "media/sctp/dcsctp_transport.h" // nogncheck
+#include "system_wrappers/include/clock.h" // nogncheck
+#endif
+
+namespace cricket {
+
+SctpTransportFactory::SctpTransportFactory(rtc::Thread* network_thread)
+ : network_thread_(network_thread) {
+ RTC_UNUSED(network_thread_);
+}
+
+std::unique_ptr<SctpTransportInternal>
+SctpTransportFactory::CreateSctpTransport(
+ rtc::PacketTransportInternal* transport) {
+ std::unique_ptr<SctpTransportInternal> result;
+#ifdef WEBRTC_HAVE_DCSCTP
+ result = std::unique_ptr<SctpTransportInternal>(new webrtc::DcSctpTransport(
+ network_thread_, transport, webrtc::Clock::GetRealTimeClock()));
+#endif
+ return result;
+}
+
+} // namespace cricket
diff --git a/third_party/libwebrtc/media/sctp/sctp_transport_factory.h b/third_party/libwebrtc/media/sctp/sctp_transport_factory.h
new file mode 100644
index 0000000000..4fff214129
--- /dev/null
+++ b/third_party/libwebrtc/media/sctp/sctp_transport_factory.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MEDIA_SCTP_SCTP_TRANSPORT_FACTORY_H_
+#define MEDIA_SCTP_SCTP_TRANSPORT_FACTORY_H_
+
+#include <memory>
+
+#include "api/transport/sctp_transport_factory_interface.h"
+#include "media/sctp/sctp_transport_internal.h"
+#include "rtc_base/thread.h"
+
+namespace cricket {
+
+class SctpTransportFactory : public webrtc::SctpTransportFactoryInterface {
+ public:
+ explicit SctpTransportFactory(rtc::Thread* network_thread);
+
+ std::unique_ptr<SctpTransportInternal> CreateSctpTransport(
+ rtc::PacketTransportInternal* transport) override;
+
+ private:
+ rtc::Thread* network_thread_;
+};
+
+} // namespace cricket
+
+#endif // MEDIA_SCTP_SCTP_TRANSPORT_FACTORY_H__
diff --git a/third_party/libwebrtc/media/sctp/sctp_transport_internal.h b/third_party/libwebrtc/media/sctp/sctp_transport_internal.h
new file mode 100644
index 0000000000..8a7450f405
--- /dev/null
+++ b/third_party/libwebrtc/media/sctp/sctp_transport_internal.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MEDIA_SCTP_SCTP_TRANSPORT_INTERNAL_H_
+#define MEDIA_SCTP_SCTP_TRANSPORT_INTERNAL_H_
+
+// TODO(deadbeef): Move SCTP code out of media/, and make it not depend on
+// anything in media/.
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "api/rtc_error.h"
+#include "api/transport/data_channel_transport_interface.h"
+#include "media/base/media_channel.h"
+#include "p2p/base/packet_transport_internal.h"
+#include "rtc_base/copy_on_write_buffer.h"
+#include "rtc_base/thread.h"
+
+namespace cricket {
+
+// Constants that are important to API users
+// The size of the SCTP association send buffer. 256kB, the usrsctp default.
+constexpr int kSctpSendBufferSize = 256 * 1024;
+
+// The number of outgoing streams that we'll negotiate. Since stream IDs (SIDs)
+// are 0-based, the highest usable SID is 1023.
+//
+// It's recommended to use the maximum of 65535 in:
+// https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.2
+// However, we use 1024 in order to save memory. usrsctp allocates 104 bytes
+// for each pair of incoming/outgoing streams (on a 64-bit system), so 65535
+// streams would waste ~6MB.
+//
+// Note: "max" and "min" here are inclusive.
+constexpr uint16_t kMaxSctpStreams = 1024;
+constexpr uint16_t kMaxSctpSid = kMaxSctpStreams - 1;
+constexpr uint16_t kMinSctpSid = 0;
+// The maximum number of streams that can be negotiated according to spec.
+constexpr uint16_t kSpecMaxSctpSid = 65535;
+
+// This is the default SCTP port to use. It is passed along the wire and the
+// connectee and connector must be using the same port. It is not related to the
+// ports at the IP level. (Corresponds to: sockaddr_conn.sconn_port in
+// usrsctp.h)
+const int kSctpDefaultPort = 5000;
+
+// Error cause codes defined at
+// https://www.iana.org/assignments/sctp-parameters/sctp-parameters.xhtml#sctp-parameters-24
+enum class SctpErrorCauseCode : uint16_t {
+ kInvalidStreamIdentifier = 1,
+ kMissingMandatoryParameter = 2,
+ kStaleCookieError = 3,
+ kOutOfResource = 4,
+ kUnresolvableAddress = 5,
+ kUnrecognizedChunkType = 6,
+ kInvalidMandatoryParameter = 7,
+ kUnrecognizedParameters = 8,
+ kNoUserData = 9,
+ kCookieReceivedWhileShuttingDown = 10,
+ kRestartWithNewAddresses = 11,
+ kUserInitiatedAbort = 12,
+ kProtocolViolation = 13,
+};
+
+// Abstract SctpTransport interface for use internally (by PeerConnection etc.).
+// Exists to allow mock/fake SctpTransports to be created.
+class SctpTransportInternal {
+ public:
+ virtual ~SctpTransportInternal() {}
+
+ virtual void SetOnConnectedCallback(std::function<void()> callback) = 0;
+ virtual void SetDataChannelSink(webrtc::DataChannelSink* sink) = 0;
+
+ // Changes what underlying DTLS transport is uses. Used when switching which
+ // bundled transport the SctpTransport uses.
+ virtual void SetDtlsTransport(rtc::PacketTransportInternal* transport) = 0;
+
+ // When Start is called, connects as soon as possible; this can be called
+ // before DTLS completes, in which case the connection will begin when DTLS
+ // completes. This method can be called multiple times, though not if either
+ // of the ports are changed.
+ //
+ // `local_sctp_port` and `remote_sctp_port` are passed along the wire and the
+ // listener and connector must be using the same port. They are not related
+ // to the ports at the IP level. If set to -1, we default to
+ // kSctpDefaultPort.
+ // `max_message_size_` sets the max message size on the connection.
+ // It must be smaller than or equal to kSctpSendBufferSize.
+ // It can be changed by a secons Start() call.
+ //
+ // TODO(deadbeef): Support calling Start with different local/remote ports
+ // and create a new association? Not clear if this is something we need to
+ // support though. See: https://github.com/w3c/webrtc-pc/issues/979
+ virtual bool Start(int local_sctp_port,
+ int remote_sctp_port,
+ int max_message_size) = 0;
+
+ // NOTE: Initially there was a "Stop" method here, but it was never used, so
+ // it was removed.
+
+ // Informs SctpTransport that `sid` will start being used. Returns false if
+ // it is impossible to use `sid`, or if it's already in use.
+ // Until calling this, can't send data using `sid`.
+ // TODO(deadbeef): Actually implement the "returns false if `sid` can't be
+ // used" part. See:
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=619849
+ virtual bool OpenStream(int sid) = 0;
+ // The inverse of OpenStream. Begins the closing procedure, which will
+ // eventually result in SignalClosingProcedureComplete on the side that
+ // initiates it, and both SignalClosingProcedureStartedRemotely and
+ // SignalClosingProcedureComplete on the other side.
+ virtual bool ResetStream(int sid) = 0;
+ // Send data down this channel.
+ // Returns RTCError::OK() if successful an error otherwise. Notably
+ // RTCErrorType::RESOURCE_EXHAUSTED for blocked operations.
+ virtual webrtc::RTCError SendData(int sid,
+ const webrtc::SendDataParams& params,
+ const rtc::CopyOnWriteBuffer& payload) = 0;
+
+ // Indicates when the SCTP socket is created and not blocked by congestion
+ // control. This changes to false when SDR_BLOCK is returned from SendData,
+ // and
+ // changes to true when SignalReadyToSendData is fired. The underlying DTLS/
+ // ICE channels may be unwritable while ReadyToSendData is true, because data
+ // can still be queued in usrsctp.
+ virtual bool ReadyToSendData() = 0;
+ // Returns the current max message size, set with Start().
+ virtual int max_message_size() const = 0;
+ // Returns the current negotiated max # of outbound streams.
+ // Will return absl::nullopt if negotiation is incomplete.
+ virtual absl::optional<int> max_outbound_streams() const = 0;
+ // Returns the current negotiated max # of inbound streams.
+ virtual absl::optional<int> max_inbound_streams() const = 0;
+
+ // Helper for debugging.
+ virtual void set_debug_name_for_testing(const char* debug_name) = 0;
+};
+
+} // namespace cricket
+
+#endif // MEDIA_SCTP_SCTP_TRANSPORT_INTERNAL_H_