Add SCTP transport to the public API.

This involves inserting an extra layer between jsep_transport_controller
and the cricket::SctpTransportInternal layer. The objects at this layer
are reference counted.

Bug: chromium:818643
Change-Id: Ibed57c4a538de981cee63e0f7f1f319f029cab39
Reviewed-on: https://webrtc-review.googlesource.com/c/123884
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26889}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index 32f4c8e..9e2bc91 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -113,6 +113,8 @@
     "rtp_sender_interface.h",
     "rtp_transceiver_interface.cc",
     "rtp_transceiver_interface.h",
+    "sctp_transport_interface.cc",
+    "sctp_transport_interface.h",
     "set_remote_description_observer_interface.h",
     "stats_types.cc",
     "stats_types.h",
diff --git a/api/DEPS b/api/DEPS
index f5a0201..db8fdda 100644
--- a/api/DEPS
+++ b/api/DEPS
@@ -166,6 +166,10 @@
     "+rtc_base/ref_count.h",
   ],
 
+  "sctp_transport_interface\.h": [
+    "+rtc_base/ref_count.h",
+  ],
+
   "set_remote_description_observer_interface\.h": [
     "+rtc_base/ref_count.h",
   ],
diff --git a/api/peer_connection_interface.cc b/api/peer_connection_interface.cc
index 944b28e..244ee73 100644
--- a/api/peer_connection_interface.cc
+++ b/api/peer_connection_interface.cc
@@ -10,6 +10,7 @@
 
 #include "api/peer_connection_interface.h"
 #include "api/dtls_transport_interface.h"
+#include "api/sctp_transport_interface.h"
 
 namespace webrtc {
 
@@ -187,6 +188,12 @@
   return nullptr;
 }
 
+rtc::scoped_refptr<SctpTransportInterface>
+PeerConnectionInterface::GetSctpTransport() const {
+  RTC_NOTREACHED();
+  return nullptr;
+}
+
 PeerConnectionInterface::BitrateParameters::BitrateParameters() = default;
 
 PeerConnectionInterface::BitrateParameters::~BitrateParameters() = default;
diff --git a/api/peer_connection_interface.h b/api/peer_connection_interface.h
index 541a6b4..be4dcb9 100644
--- a/api/peer_connection_interface.h
+++ b/api/peer_connection_interface.h
@@ -122,6 +122,7 @@
 class AudioMixer;
 class AudioProcessing;
 class DtlsTransportInterface;
+class SctpTransportInterface;
 class VideoDecoderFactory;
 class VideoEncoderFactory;
 
@@ -1038,6 +1039,10 @@
   virtual rtc::scoped_refptr<DtlsTransportInterface> LookupDtlsTransportByMid(
       const std::string& mid);
 
+  // Returns the SCTP transport, if any.
+  // TODO(hta): Remove default implementation after updating Chrome.
+  virtual rtc::scoped_refptr<SctpTransportInterface> GetSctpTransport() const;
+
   // Returns the current SignalingState.
   virtual SignalingState signaling_state() = 0;
 
diff --git a/api/sctp_transport_interface.cc b/api/sctp_transport_interface.cc
new file mode 100644
index 0000000..c6c1fbe
--- /dev/null
+++ b/api/sctp_transport_interface.cc
@@ -0,0 +1,32 @@
+/*
+ *  Copyright 2019 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 <utility>
+
+#include "api/sctp_transport_interface.h"
+
+namespace webrtc {
+
+SctpTransportInformation::SctpTransportInformation(SctpTransportState state)
+    : state_(state) {}
+
+SctpTransportInformation::SctpTransportInformation(
+    SctpTransportState state,
+    rtc::scoped_refptr<DtlsTransportInterface> dtls_transport,
+    absl::optional<double> max_message_size,
+    absl::optional<int> max_channels)
+    : state_(state),
+      dtls_transport_(std::move(dtls_transport)),
+      max_message_size_(max_message_size),
+      max_channels_(max_channels) {}
+
+SctpTransportInformation::~SctpTransportInformation() {}
+
+}  // namespace webrtc
diff --git a/api/sctp_transport_interface.h b/api/sctp_transport_interface.h
new file mode 100644
index 0000000..3698fc2
--- /dev/null
+++ b/api/sctp_transport_interface.h
@@ -0,0 +1,90 @@
+/*
+ *  Copyright 2019 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 API_SCTP_TRANSPORT_INTERFACE_H_
+#define API_SCTP_TRANSPORT_INTERFACE_H_
+
+#include "absl/types/optional.h"
+#include "api/dtls_transport_interface.h"
+#include "api/rtc_error.h"
+#include "api/scoped_refptr.h"
+#include "rtc_base/ref_count.h"
+
+namespace webrtc {
+
+// States of a SCTP transport, corresponding to the JS API specification.
+// http://w3c.github.io/webrtc-pc/#dom-rtcsctptransportstate
+enum class SctpTransportState {
+  kNew,         // Has not started negotiating yet. Non-standard state.
+  kConnecting,  // In the process of negotiating an association.
+  kConnected,   // Completed negotiation of an association.
+  kClosed,      // Closed by local or remote party.
+  kNumValues
+};
+
+// This object gives snapshot information about the changeable state of a
+// SctpTransport.
+// It reflects the readonly attributes of the object in the specification.
+// http://w3c.github.io/webrtc-pc/#rtcsctptransport-interface
+class SctpTransportInformation {
+ public:
+  explicit SctpTransportInformation(SctpTransportState state);
+  SctpTransportInformation(
+      SctpTransportState state,
+      rtc::scoped_refptr<DtlsTransportInterface> dtls_transport,
+      absl::optional<double> max_message_size,
+      absl::optional<int> max_channels);
+  ~SctpTransportInformation();
+  // The DTLS transport that supports this SCTP transport.
+  rtc::scoped_refptr<DtlsTransportInterface> dtls_transport() const {
+    return dtls_transport_;
+  }
+  SctpTransportState state() const { return state_; }
+  absl::optional<double> MaxMessageSize() const { return max_message_size_; }
+  absl::optional<int> MaxChannels() const { return max_channels_; }
+
+ private:
+  SctpTransportState state_;
+  rtc::scoped_refptr<DtlsTransportInterface> dtls_transport_;
+  absl::optional<double> max_message_size_;
+  absl::optional<int> max_channels_;
+};
+
+class SctpTransportObserverInterface {
+ public:
+  // This callback carries information about the state of the transport.
+  // The argument is a pass-by-value snapshot of the state.
+  // The callback will be called on the network thread.
+  virtual void OnStateChange(SctpTransportInformation info) = 0;
+
+ protected:
+  virtual ~SctpTransportObserverInterface() = default;
+};
+
+// A SCTP transport, as represented to the outside world.
+// This object is created on the network thread, and can only be
+// accessed on that thread, except for functions explicitly marked otherwise.
+// References can be held by other threads, and destruction can therefore
+// be initiated by other threads.
+class SctpTransportInterface : public rtc::RefCountInterface {
+ public:
+  // This function can be called from other threads.
+  virtual rtc::scoped_refptr<DtlsTransportInterface> dtls_transport() const = 0;
+  // Returns information on the state of the SctpTransport.
+  // This function can be called from other threads.
+  virtual SctpTransportInformation Information() const = 0;
+  // Observer management.
+  virtual void RegisterObserver(SctpTransportObserverInterface* observer) = 0;
+  virtual void UnregisterObserver() = 0;
+};
+
+}  // namespace webrtc
+
+#endif  // API_SCTP_TRANSPORT_INTERFACE_H_
diff --git a/media/sctp/sctp_transport.cc b/media/sctp/sctp_transport.cc
index b656018..8bcec41 100644
--- a/media/sctp/sctp_transport.cc
+++ b/media/sctp/sctp_transport.cc
@@ -383,9 +383,8 @@
                              rtc::PacketTransportInternal* transport)
     : network_thread_(network_thread),
       transport_(transport),
-      was_ever_writable_(transport->writable()) {
+      was_ever_writable_(transport ? transport->writable() : false) {
   RTC_DCHECK(network_thread_);
-  RTC_DCHECK(transport_);
   RTC_DCHECK_RUN_ON(network_thread_);
   ConnectTransportSignals();
 }
diff --git a/media/sctp/sctp_transport.h b/media/sctp/sctp_transport.h
index f7ddd87..dccecd8 100644
--- a/media/sctp/sctp_transport.h
+++ b/media/sctp/sctp_transport.h
@@ -36,10 +36,10 @@
 struct socket;
 namespace cricket {
 
-// Holds data to be passed on to a channel.
+// Holds data to be passed on to a transport.
 struct SctpInboundPacket;
 
-// From channel calls, data flows like this:
+// From transport calls, data flows like this:
 // [network thread (although it can in princple be another thread)]
 //  1.  SctpTransport::SendData(data)
 //  2.  usrsctp_sendv(data)
@@ -59,16 +59,15 @@
 //  12. SctpTransport::SignalDataReceived(data)
 // [from the same thread, methods registered/connected to
 //  SctpTransport are called with the recieved data]
-// TODO(zhihuang): Rename "channel" to "transport" on network-level.
 class SctpTransport : public SctpTransportInternal,
                       public sigslot::has_slots<> {
  public:
   // |network_thread| is where packets will be processed and callbacks from
   // this transport will be posted, and is the only thread on which public
   // methods can be called.
-  // |channel| is required (must not be null).
+  // |transport| is not required (can be null).
   SctpTransport(rtc::Thread* network_thread,
-                rtc::PacketTransportInternal* channel);
+                rtc::PacketTransportInternal* transport);
   ~SctpTransport() override;
 
   // SctpTransportInternal overrides (see sctptransportinternal.h for comments).
@@ -108,7 +107,7 @@
   // Sets the "ready to send" flag and fires signal if needed.
   void SetReadyToSendData();
 
-  // Callbacks from DTLS channel.
+  // Callbacks from DTLS transport.
   void OnWritableState(rtc::PacketTransportInternal* transport);
   virtual void OnPacketRead(rtc::PacketTransportInternal* transport,
                             const char* data,
@@ -135,12 +134,12 @@
 
   void OnStreamResetEvent(const struct sctp_stream_reset_event* evt);
 
-  // Responsible for marshalling incoming data to the channels listeners, and
+  // Responsible for marshalling incoming data to the transports listeners, and
   // outgoing data to the network interface.
   rtc::Thread* network_thread_;
   // Helps pass inbound/outbound packets asynchronously to the network thread.
   rtc::AsyncInvoker invoker_;
-  // Underlying DTLS channel.
+  // Underlying DTLS transport.
   rtc::PacketTransportInternal* transport_ = nullptr;
 
   // Track the data received from usrsctp between callbacks until the EOR bit
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 9540ad6..b20f064 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -55,6 +55,8 @@
     "rtp_transport.cc",
     "rtp_transport.h",
     "rtp_transport_internal.h",
+    "sctp_transport.cc",
+    "sctp_transport.h",
     "session_description.cc",
     "session_description.h",
     "simulcast_description.cc",
@@ -258,6 +260,7 @@
       "media_session_unittest.cc",
       "rtcp_mux_filter_unittest.cc",
       "rtp_transport_unittest.cc",
+      "sctp_transport_unittest.cc",
       "session_description_unittest.cc",
       "srtp_filter_unittest.cc",
       "srtp_session_unittest.cc",
diff --git a/pc/jsep_transport_controller.cc b/pc/jsep_transport_controller.cc
index c1b8e4e..97f6bdd 100644
--- a/pc/jsep_transport_controller.cc
+++ b/pc/jsep_transport_controller.cc
@@ -818,8 +818,8 @@
 
   mid_to_transport_[mid] = jsep_transport;
   return config_.transport_observer->OnTransportChanged(
-      mid, jsep_transport->rtp_transport(),
-      jsep_transport->rtp_dtls_transport(), jsep_transport->media_transport());
+      mid, jsep_transport->rtp_transport(), jsep_transport->RtpDtlsTransport(),
+      jsep_transport->media_transport());
 }
 
 void JsepTransportController::RemoveTransportForMid(const std::string& mid) {
diff --git a/pc/jsep_transport_controller.h b/pc/jsep_transport_controller.h
index 1363907..c98ff25 100644
--- a/pc/jsep_transport_controller.h
+++ b/pc/jsep_transport_controller.h
@@ -58,7 +58,7 @@
     virtual bool OnTransportChanged(
         const std::string& mid,
         RtpTransportInternal* rtp_transport,
-        cricket::DtlsTransportInternal* dtls_transport,
+        rtc::scoped_refptr<DtlsTransport> dtls_transport,
         MediaTransportInterface* media_transport) = 0;
   };
 
diff --git a/pc/jsep_transport_controller_unittest.cc b/pc/jsep_transport_controller_unittest.cc
index 590f57d..4375080 100644
--- a/pc/jsep_transport_controller_unittest.cc
+++ b/pc/jsep_transport_controller_unittest.cc
@@ -305,10 +305,14 @@
   // JsepTransportController::Observer overrides.
   bool OnTransportChanged(const std::string& mid,
                           RtpTransportInternal* rtp_transport,
-                          cricket::DtlsTransportInternal* dtls_transport,
+                          rtc::scoped_refptr<DtlsTransport> dtls_transport,
                           MediaTransportInterface* media_transport) override {
     changed_rtp_transport_by_mid_[mid] = rtp_transport;
-    changed_dtls_transport_by_mid_[mid] = dtls_transport;
+    if (dtls_transport) {
+      changed_dtls_transport_by_mid_[mid] = dtls_transport->internal();
+    } else {
+      changed_dtls_transport_by_mid_[mid] = nullptr;
+    }
     changed_media_transport_by_mid_[mid] = media_transport;
     return true;
   }
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index d3b9258..44617dd 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -41,6 +41,7 @@
 #include "pc/rtp_media_utils.h"
 #include "pc/rtp_receiver.h"
 #include "pc/rtp_sender.h"
+#include "pc/sctp_transport.h"
 #include "pc/sctp_utils.h"
 #include "pc/sdp_utils.h"
 #include "pc/stream_collection.h"
@@ -3658,6 +3659,11 @@
   return transport_controller_->LookupDtlsTransportByMid(mid);
 }
 
+rtc::scoped_refptr<SctpTransportInterface> PeerConnection::GetSctpTransport()
+    const {
+  return sctp_transport_;
+}
+
 const SessionDescriptionInterface* PeerConnection::local_description() const {
   return pending_local_description_ ? pending_local_description_.get()
                                     : current_local_description_.get();
@@ -5497,7 +5503,7 @@
   RTC_DCHECK(remote_description());
   // Apply the SCTP port (which is hidden inside a DataCodec structure...)
   // When we support "max-message-size", that would also be pushed down here.
-  return sctp_transport_->Start(
+  return cricket_sctp_transport()->Start(
       GetSctpPort(local_description()->description()),
       GetSctpPort(remote_description()->description()));
 }
@@ -5631,7 +5637,7 @@
              : network_thread()->Invoke<bool>(
                    RTC_FROM_HERE,
                    Bind(&cricket::SctpTransportInternal::SendData,
-                        sctp_transport_.get(), params, payload, result));
+                        cricket_sctp_transport(), params, payload, result));
 }
 
 bool PeerConnection::ConnectDataChannel(DataChannel* webrtc_data_channel) {
@@ -5705,7 +5711,7 @@
   }
   network_thread()->Invoke<void>(
       RTC_FROM_HERE, rtc::Bind(&cricket::SctpTransportInternal::OpenStream,
-                               sctp_transport_.get(), sid));
+                               cricket_sctp_transport(), sid));
 }
 
 void PeerConnection::RemoveSctpDataStream(int sid) {
@@ -5720,7 +5726,7 @@
   }
   network_thread()->Invoke<void>(
       RTC_FROM_HERE, rtc::Bind(&cricket::SctpTransportInternal::ResetStream,
-                               sctp_transport_.get(), sid));
+                               cricket_sctp_transport(), sid));
 }
 
 bool PeerConnection::ReadyToSendData() const {
@@ -6243,32 +6249,38 @@
 bool PeerConnection::CreateSctpTransport_n(const std::string& mid) {
   RTC_DCHECK(network_thread()->IsCurrent());
   RTC_DCHECK(sctp_factory_);
+  rtc::scoped_refptr<DtlsTransport> webrtc_dtls_transport =
+      transport_controller_->LookupDtlsTransportByMid(mid);
   cricket::DtlsTransportInternal* dtls_transport =
-      transport_controller_->GetDtlsTransport(mid);
+      webrtc_dtls_transport->internal();
   RTC_DCHECK(dtls_transport);
-  sctp_transport_ = sctp_factory_->CreateSctpTransport(dtls_transport);
-  RTC_DCHECK(sctp_transport_);
+  std::unique_ptr<cricket::SctpTransportInternal> cricket_sctp_transport =
+      sctp_factory_->CreateSctpTransport(dtls_transport);
+  RTC_DCHECK(cricket_sctp_transport);
   sctp_invoker_.reset(new rtc::AsyncInvoker());
-  sctp_transport_->SignalReadyToSendData.connect(
+  cricket_sctp_transport->SignalReadyToSendData.connect(
       this, &PeerConnection::OnSctpTransportReadyToSendData_n);
-  sctp_transport_->SignalDataReceived.connect(
+  cricket_sctp_transport->SignalDataReceived.connect(
       this, &PeerConnection::OnSctpTransportDataReceived_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(
+  cricket_sctp_transport->SignalClosingProcedureStartedRemotely.connect(
       this, &PeerConnection::OnSctpClosingProcedureStartedRemotely_n);
-  sctp_transport_->SignalClosingProcedureComplete.connect(
+  cricket_sctp_transport->SignalClosingProcedureComplete.connect(
       this, &PeerConnection::OnSctpClosingProcedureComplete_n);
   sctp_mid_ = mid;
-  sctp_transport_->SetDtlsTransport(dtls_transport);
+  sctp_transport_ = new rtc::RefCountedObject<SctpTransport>(
+      std::move(cricket_sctp_transport));
+  sctp_transport_->SetDtlsTransport(std::move(webrtc_dtls_transport));
   return true;
 }
 
 void PeerConnection::DestroySctpTransport_n() {
   RTC_DCHECK(network_thread()->IsCurrent());
-  sctp_transport_.reset(nullptr);
+  sctp_transport_->Clear();
+  sctp_transport_ = nullptr;
   sctp_mid_.reset();
   sctp_invoker_.reset(nullptr);
   sctp_ready_to_send_data_ = false;
@@ -6949,7 +6961,7 @@
 bool PeerConnection::OnTransportChanged(
     const std::string& mid,
     RtpTransportInternal* rtp_transport,
-    cricket::DtlsTransportInternal* dtls_transport,
+    rtc::scoped_refptr<DtlsTransport> dtls_transport,
     MediaTransportInterface* media_transport) {
   RTC_DCHECK_RUNS_SERIALIZED(&use_media_transport_race_checker_);
   bool ret = true;
diff --git a/pc/peer_connection.h b/pc/peer_connection.h
index 376dcc0..20b66ac 100644
--- a/pc/peer_connection.h
+++ b/pc/peer_connection.h
@@ -26,6 +26,7 @@
 #include "pc/peer_connection_internal.h"
 #include "pc/rtc_stats_collector.h"
 #include "pc/rtp_transceiver.h"
+#include "pc/sctp_transport.h"
 #include "pc/stats_collector.h"
 #include "pc/stream_collection.h"
 #include "pc/webrtc_session_description_factory.h"
@@ -201,6 +202,8 @@
   rtc::scoped_refptr<DtlsTransport> LookupDtlsTransportByMidInternal(
       const std::string& mid);
 
+  rtc::scoped_refptr<SctpTransportInterface> GetSctpTransport() const override;
+
   RTC_DEPRECATED bool StartRtcEventLog(rtc::PlatformFile file,
                                        int64_t max_size_bytes) override;
   bool StartRtcEventLog(std::unique_ptr<RtcEventLogOutput> output,
@@ -1015,7 +1018,7 @@
   // rejected).
   bool OnTransportChanged(const std::string& mid,
                           RtpTransportInternal* rtp_transport,
-                          cricket::DtlsTransportInternal* dtls_transport,
+                          rtc::scoped_refptr<DtlsTransport> dtls_transport,
                           MediaTransportInterface* media_transport) override;
 
   // Returns the observer. Will crash on CHECK if the observer is removed.
@@ -1149,7 +1152,10 @@
   // when using SCTP.
   cricket::RtpDataChannel* rtp_data_channel_ = nullptr;
 
-  std::unique_ptr<cricket::SctpTransportInternal> sctp_transport_;
+  cricket::SctpTransportInternal* cricket_sctp_transport() {
+    return sctp_transport_->internal();
+  }
+  rtc::scoped_refptr<SctpTransport> sctp_transport_;
   // |sctp_mid_| is the content name (MID) in SDP.
   absl::optional<std::string> sctp_mid_;
   // Value cached on signaling thread. Only updated when SctpReadyToSendData
diff --git a/pc/sctp_transport.cc b/pc/sctp_transport.cc
new file mode 100644
index 0000000..99270e2
--- /dev/null
+++ b/pc/sctp_transport.cc
@@ -0,0 +1,119 @@
+/*
+ *  Copyright 2018 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 "pc/sctp_transport.h"
+
+#include <utility>
+
+namespace webrtc {
+
+SctpTransport::SctpTransport(
+    std::unique_ptr<cricket::SctpTransportInternal> internal)
+    : owner_thread_(rtc::Thread::Current()),
+      info_(SctpTransportState::kNew),
+      internal_sctp_transport_(std::move(internal)) {
+  RTC_DCHECK(internal_sctp_transport_.get());
+  internal_sctp_transport_->SignalReadyToSendData.connect(
+      this, &SctpTransport::OnInternalReadyToSendData);
+  // TODO(https://bugs.webrtc.org/10360): Add handlers for transport closing.
+
+  if (dtls_transport_) {
+    UpdateInformation(SctpTransportState::kConnecting);
+  } else {
+    UpdateInformation(SctpTransportState::kNew);
+  }
+}
+
+SctpTransport::~SctpTransport() {
+  // We depend on the network thread to call Clear() before dropping
+  // its last reference to this object.
+  RTC_DCHECK(owner_thread_->IsCurrent() || !internal_sctp_transport_);
+}
+
+SctpTransportInformation SctpTransport::Information() const {
+  rtc::CritScope scope(&lock_);
+  return info_;
+}
+
+void SctpTransport::RegisterObserver(SctpTransportObserverInterface* observer) {
+  RTC_DCHECK_RUN_ON(owner_thread_);
+  RTC_DCHECK(observer);
+  RTC_DCHECK(!observer_);
+  observer_ = observer;
+}
+
+void SctpTransport::UnregisterObserver() {
+  RTC_DCHECK_RUN_ON(owner_thread_);
+  observer_ = nullptr;
+}
+
+rtc::scoped_refptr<DtlsTransportInterface> SctpTransport::dtls_transport()
+    const {
+  RTC_DCHECK_RUN_ON(owner_thread_);
+  return dtls_transport_;
+}
+
+// Internal functions
+void SctpTransport::Clear() {
+  RTC_DCHECK_RUN_ON(owner_thread_);
+  RTC_DCHECK(internal());
+  {
+    rtc::CritScope scope(&lock_);
+    // Note that we delete internal_sctp_transport_, but
+    // only drop the reference to dtls_transport_.
+    dtls_transport_ = nullptr;
+    internal_sctp_transport_ = nullptr;
+  }
+  UpdateInformation(SctpTransportState::kClosed);
+}
+
+void SctpTransport::SetDtlsTransport(
+    rtc::scoped_refptr<DtlsTransport> transport) {
+  RTC_DCHECK_RUN_ON(owner_thread_);
+  rtc::CritScope scope(&lock_);
+  dtls_transport_ = transport;
+  if (internal_sctp_transport_) {
+    if (transport) {
+      internal_sctp_transport_->SetDtlsTransport(transport->internal());
+      if (info_.state() == SctpTransportState::kNew) {
+        UpdateInformation(SctpTransportState::kConnecting);
+      }
+    } else {
+      internal_sctp_transport_->SetDtlsTransport(nullptr);
+    }
+  }
+}
+
+void SctpTransport::UpdateInformation(SctpTransportState state) {
+  RTC_DCHECK_RUN_ON(owner_thread_);
+  bool must_send_update;
+  SctpTransportInformation info_copy(SctpTransportState::kNew);
+  {
+    rtc::CritScope scope(&lock_);
+    must_send_update = (state != info_.state());
+    // TODO(https://bugs.webrtc.org/10358): Update max message size and
+    // max channels from internal SCTP transport when available.
+    info_ = SctpTransportInformation(
+        state, dtls_transport_, info_.MaxMessageSize(), info_.MaxChannels());
+    if (observer_ && must_send_update) {
+      info_copy = info_;
+    }
+  }
+  // We call the observer without holding the lock.
+  if (observer_ && must_send_update) {
+    observer_->OnStateChange(info_copy);
+  }
+}
+
+void SctpTransport::OnInternalReadyToSendData() {
+  UpdateInformation(SctpTransportState::kConnected);
+}
+
+}  // namespace webrtc
diff --git a/pc/sctp_transport.h b/pc/sctp_transport.h
new file mode 100644
index 0000000..f1e5092
--- /dev/null
+++ b/pc/sctp_transport.h
@@ -0,0 +1,75 @@
+/*
+ *  Copyright 2019 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 PC_SCTP_TRANSPORT_H_
+#define PC_SCTP_TRANSPORT_H_
+
+#include <memory>
+
+#include "api/scoped_refptr.h"
+#include "api/sctp_transport_interface.h"
+#include "media/sctp/sctp_transport.h"
+#include "pc/dtls_transport.h"
+
+namespace webrtc {
+
+// This implementation wraps a cricket::SctpTransport, and takes
+// ownership of it.
+// This object must be constructed and updated on the networking thread,
+// the same thread as the one the cricket::SctpTransportInternal object
+// lives on.
+class SctpTransport : public SctpTransportInterface,
+                      public sigslot::has_slots<> {
+ public:
+  explicit SctpTransport(
+      std::unique_ptr<cricket::SctpTransportInternal> internal);
+
+  rtc::scoped_refptr<DtlsTransportInterface> dtls_transport() const override;
+  SctpTransportInformation Information() const override;
+  void RegisterObserver(SctpTransportObserverInterface* observer) override;
+  void UnregisterObserver() override;
+
+  void Clear();
+  void SetDtlsTransport(rtc::scoped_refptr<DtlsTransport>);
+
+  cricket::SctpTransportInternal* internal() {
+    rtc::CritScope scope(&lock_);
+    return internal_sctp_transport_.get();
+  }
+
+  const cricket::SctpTransportInternal* internal() const {
+    rtc::CritScope scope(&lock_);
+    return internal_sctp_transport_.get();
+  }
+
+ protected:
+  ~SctpTransport() override;
+
+ private:
+  void UpdateInformation(SctpTransportState state);
+  void OnInternalReadyToSendData();
+  void OnInternalClosingProcedureStartedRemotely(int sid);
+  void OnInternalClosingProcedureComplete(int sid);
+
+  const rtc::Thread* owner_thread_;
+  rtc::CriticalSection lock_;
+  // Variables accessible off-thread, guarded by lock_
+  SctpTransportInformation info_ RTC_GUARDED_BY(lock_);
+  std::unique_ptr<cricket::SctpTransportInternal> internal_sctp_transport_
+      RTC_GUARDED_BY(lock_);
+  // Variables only accessed on-thread
+  SctpTransportObserverInterface* observer_ RTC_GUARDED_BY(owner_thread_) =
+      nullptr;
+  rtc::scoped_refptr<DtlsTransport> dtls_transport_
+      RTC_GUARDED_BY(owner_thread_);
+};
+
+}  // namespace webrtc
+#endif  // PC_SCTP_TRANSPORT_H_
diff --git a/pc/sctp_transport_unittest.cc b/pc/sctp_transport_unittest.cc
new file mode 100644
index 0000000..e2bdf44
--- /dev/null
+++ b/pc/sctp_transport_unittest.cc
@@ -0,0 +1,149 @@
+/*
+ *  Copyright 2019 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 "pc/sctp_transport.h"
+
+#include <utility>
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "p2p/base/fake_dtls_transport.h"
+#include "pc/dtls_transport.h"
+#include "rtc_base/gunit.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+constexpr int kDefaultTimeout = 1000;  // milliseconds
+
+using cricket::FakeDtlsTransport;
+using ::testing::ElementsAre;
+
+namespace webrtc {
+
+namespace {
+
+class FakeCricketSctpTransport : public cricket::SctpTransportInternal {
+ public:
+  void SetDtlsTransport(rtc::PacketTransportInternal* transport) override {}
+  bool Start(int local_port, int remote_port) override { return true; }
+  bool OpenStream(int sid) override { return true; }
+  bool ResetStream(int sid) override { return true; }
+  bool SendData(const cricket::SendDataParams& params,
+                const rtc::CopyOnWriteBuffer& payload,
+                cricket::SendDataResult* result = nullptr) override {
+    return true;
+  }
+  bool ReadyToSendData() override { return true; }
+  void set_debug_name_for_testing(const char* debug_name) override {}
+  // Methods exposed for testing
+  void SendSignalReadyToSendData() { SignalReadyToSendData(); }
+
+  void SendSignalClosingProcedureStartedRemotely() {
+    SignalClosingProcedureStartedRemotely(1);
+  }
+
+  void SendSignalClosingProcedureComplete() {
+    SignalClosingProcedureComplete(1);
+  }
+};
+
+}  // namespace
+
+class TestSctpTransportObserver : public SctpTransportObserverInterface {
+ public:
+  void OnStateChange(SctpTransportInformation info) override {
+    states_.push_back(info.state());
+  }
+
+  SctpTransportState State() {
+    if (states_.size() > 0) {
+      return states_[states_.size() - 1];
+    } else {
+      return SctpTransportState::kNew;
+    }
+  }
+
+  const std::vector<SctpTransportState>& States() { return states_; }
+
+ private:
+  std::vector<SctpTransportState> states_;
+};
+
+class SctpTransportTest : public testing::Test {
+ public:
+  SctpTransport* transport() { return transport_.get(); }
+  SctpTransportObserverInterface* observer() { return &observer_; }
+
+  void CreateTransport() {
+    auto cricket_sctp_transport =
+        absl::WrapUnique(new FakeCricketSctpTransport());
+    transport_ = new rtc::RefCountedObject<SctpTransport>(
+        std::move(cricket_sctp_transport));
+  }
+
+  void AddDtlsTransport() {
+    std::unique_ptr<cricket::DtlsTransportInternal> cricket_transport =
+        absl::make_unique<FakeDtlsTransport>(
+            "audio", cricket::ICE_CANDIDATE_COMPONENT_RTP);
+    dtls_transport_ =
+        new rtc::RefCountedObject<DtlsTransport>(std::move(cricket_transport));
+    transport_->SetDtlsTransport(dtls_transport_);
+  }
+
+  void CompleteSctpHandshake() {
+    CricketSctpTransport()->SendSignalReadyToSendData();
+  }
+
+  FakeCricketSctpTransport* CricketSctpTransport() {
+    return static_cast<FakeCricketSctpTransport*>(transport_->internal());
+  }
+
+  rtc::scoped_refptr<SctpTransport> transport_;
+  rtc::scoped_refptr<DtlsTransport> dtls_transport_;
+  TestSctpTransportObserver observer_;
+};
+
+TEST(SctpTransportSimpleTest, CreateClearDelete) {
+  std::unique_ptr<cricket::SctpTransportInternal> fake_cricket_sctp_transport =
+      absl::WrapUnique(new FakeCricketSctpTransport());
+  rtc::scoped_refptr<SctpTransport> sctp_transport =
+      new rtc::RefCountedObject<SctpTransport>(
+          std::move(fake_cricket_sctp_transport));
+  ASSERT_TRUE(sctp_transport->internal());
+  ASSERT_EQ(SctpTransportState::kNew, sctp_transport->Information().state());
+  sctp_transport->Clear();
+  ASSERT_FALSE(sctp_transport->internal());
+  ASSERT_EQ(SctpTransportState::kClosed, sctp_transport->Information().state());
+}
+
+TEST_F(SctpTransportTest, EventsObservedWhenConnecting) {
+  CreateTransport();
+  transport()->RegisterObserver(observer());
+  AddDtlsTransport();
+  CompleteSctpHandshake();
+  ASSERT_EQ_WAIT(SctpTransportState::kConnected, observer_.State(),
+                 kDefaultTimeout);
+  EXPECT_THAT(observer_.States(), ElementsAre(SctpTransportState::kConnecting,
+                                              SctpTransportState::kConnected));
+}
+
+TEST_F(SctpTransportTest, CloseWhenClearing) {
+  CreateTransport();
+  transport()->RegisterObserver(observer());
+  AddDtlsTransport();
+  CompleteSctpHandshake();
+  ASSERT_EQ_WAIT(SctpTransportState::kConnected, observer_.State(),
+                 kDefaultTimeout);
+  transport()->Clear();
+  ASSERT_EQ_WAIT(SctpTransportState::kClosed, observer_.State(),
+                 kDefaultTimeout);
+}
+
+}  // namespace webrtc