Refactoring DataContentDescription class

This CL splits the cricket::DataContentDescription class into
two classes: cricket::DataContentDescription (used for RTP data) and
cricket::SctpDataContentDescription (used for SCTP only).

SctpDataContentDescription no longer inherits from
MediaContentDescriptionImpl, and no longer contains "codecs".

Design document:
https://docs.google.com/document/d/1H5LfQxJA2ikMWTQ8FZ3_GAmaXM7knfVQWiSz6ph8VQ0/edit#

Bug: webrtc:10358
Change-Id: Ie7160610506aeef56d1f821b5fdb5d9492201f43
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/132700
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27651}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index 7326409..dbfd096 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -72,6 +72,7 @@
   ]
 
   deps = [
+    ":media_protocol_names",
     "../api:array_view",
     "../api:audio_options_api",
     "../api:call_api",
@@ -120,6 +121,13 @@
   ]
 }
 
+rtc_source_set("media_protocol_names") {
+  sources = [
+    "media_protocol_names.cc",
+    "media_protocol_names.h",
+  ]
+}
+
 rtc_static_library("peerconnection") {
   visibility = [ "*" ]
   cflags = []
diff --git a/pc/media_protocol_names.cc b/pc/media_protocol_names.cc
new file mode 100644
index 0000000..6ce2f02
--- /dev/null
+++ b/pc/media_protocol_names.cc
@@ -0,0 +1,41 @@
+/*
+ *  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/media_protocol_names.h"
+
+namespace cricket {
+
+const char kMediaProtocolRtpPrefix[] = "RTP/";
+
+const char kMediaProtocolSctp[] = "SCTP";
+const char kMediaProtocolDtlsSctp[] = "DTLS/SCTP";
+const char kMediaProtocolUdpDtlsSctp[] = "UDP/DTLS/SCTP";
+const char kMediaProtocolTcpDtlsSctp[] = "TCP/DTLS/SCTP";
+
+bool IsDtlsSctp(const std::string& protocol) {
+  return protocol == kMediaProtocolDtlsSctp ||
+         protocol == kMediaProtocolUdpDtlsSctp ||
+         protocol == kMediaProtocolTcpDtlsSctp;
+}
+
+bool IsPlainSctp(const std::string& protocol) {
+  return protocol == kMediaProtocolSctp;
+}
+
+bool IsRtpProtocol(const std::string& protocol) {
+  return protocol.empty() ||
+         (protocol.find(cricket::kMediaProtocolRtpPrefix) != std::string::npos);
+}
+
+bool IsSctpProtocol(const std::string& protocol) {
+  return IsPlainSctp(protocol) || IsDtlsSctp(protocol);
+}
+
+}  // namespace cricket
diff --git a/pc/media_protocol_names.h b/pc/media_protocol_names.h
new file mode 100644
index 0000000..f97055d
--- /dev/null
+++ b/pc/media_protocol_names.h
@@ -0,0 +1,35 @@
+/*
+ *  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_MEDIA_PROTOCOL_NAMES_H_
+#define PC_MEDIA_PROTOCOL_NAMES_H_
+
+#include <string>
+
+namespace cricket {
+
+// Names or name prefixes of protocols as defined by SDP specifications.
+extern const char kMediaProtocolRtpPrefix[];
+extern const char kMediaProtocolSctp[];
+extern const char kMediaProtocolDtlsSctp[];
+extern const char kMediaProtocolUdpDtlsSctp[];
+extern const char kMediaProtocolTcpDtlsSctp[];
+
+// Returns true if the given media section protocol indicates use of RTP.
+bool IsRtpProtocol(const std::string& protocol);
+// Returns true if the given media section protocol indicates use of SCTP.
+bool IsSctpProtocol(const std::string& protocol);
+
+bool IsDtlsSctp(const std::string& protocol);
+bool IsPlainSctp(const std::string& protocol);
+
+}  // namespace cricket
+
+#endif  // PC_MEDIA_PROTOCOL_NAMES_H_
diff --git a/pc/media_session.cc b/pc/media_session.cc
index 8377f10..b739e90 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -27,6 +27,7 @@
 #include "media/base/media_constants.h"
 #include "p2p/base/p2p_constants.h"
 #include "pc/channel_manager.h"
+#include "pc/media_protocol_names.h"
 #include "pc/rtp_media_utils.h"
 #include "pc/srtp_filter.h"
 #include "rtc_base/checks.h"
@@ -68,13 +69,6 @@
 // but we tolerate "RTP/SAVPF" in offers we receive, for compatibility.
 const char kMediaProtocolSavpf[] = "RTP/SAVPF";
 
-const char kMediaProtocolRtpPrefix[] = "RTP/";
-
-const char kMediaProtocolSctp[] = "SCTP";
-const char kMediaProtocolDtlsSctp[] = "DTLS/SCTP";
-const char kMediaProtocolUdpDtlsSctp[] = "UDP/DTLS/SCTP";
-const char kMediaProtocolTcpDtlsSctp[] = "TCP/DTLS/SCTP";
-
 // Note that the below functions support some protocol strings purely for
 // legacy compatibility, as required by JSEP in Section 5.1.2, Profile Names
 // and Interoperability.
@@ -91,20 +85,6 @@
          protocol == "RTP/SAVP" || protocol == "RTP/AVP";
 }
 
-static bool IsDtlsSctp(const std::string& protocol) {
-  return protocol == kMediaProtocolDtlsSctp ||
-         protocol == kMediaProtocolUdpDtlsSctp ||
-         protocol == kMediaProtocolTcpDtlsSctp;
-}
-
-static bool IsPlainSctp(const std::string& protocol) {
-  return protocol == kMediaProtocolSctp;
-}
-
-static bool IsSctp(const std::string& protocol) {
-  return IsPlainSctp(protocol) || IsDtlsSctp(protocol);
-}
-
 static RtpTransceiverDirection NegotiateRtpTransceiverDirection(
     RtpTransceiverDirection offer,
     RtpTransceiverDirection wants) {
@@ -489,7 +469,7 @@
     StreamParamsVec* current_streams,
     MediaContentDescriptionImpl<C>* content_description) {
   // SCTP streams are not negotiated using SDP/ContentDescriptions.
-  if (IsSctp(content_description->protocol())) {
+  if (IsSctpProtocol(content_description->protocol())) {
     return true;
   }
 
@@ -608,11 +588,6 @@
       target_cryptos->end());
 }
 
-bool IsRtpProtocol(const std::string& protocol) {
-  return protocol.empty() ||
-         (protocol.find(cricket::kMediaProtocolRtpPrefix) != std::string::npos);
-}
-
 static bool IsRtpContent(SessionDescription* sdesc,
                          const std::string& content_name) {
   bool is_rtp = false;
@@ -741,32 +716,22 @@
 // crypto (in current_cryptos) and it is enabled (in secure_policy), crypto is
 // created (according to crypto_suites). The created content is added to the
 // offer.
-template <class C>
-static bool CreateMediaContentOffer(
+static bool CreateContentOffer(
     const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
-    const std::vector<C>& codecs,
     const SecurePolicy& secure_policy,
     const CryptoParamsVec* current_cryptos,
     const std::vector<std::string>& crypto_suites,
     const RtpHeaderExtensions& rtp_extensions,
     UniqueRandomIdGenerator* ssrc_generator,
     StreamParamsVec* current_streams,
-    MediaContentDescriptionImpl<C>* offer) {
-  offer->AddCodecs(codecs);
-
+    MediaContentDescription* offer) {
   offer->set_rtcp_mux(session_options.rtcp_mux_enabled);
   if (offer->type() == cricket::MEDIA_TYPE_VIDEO) {
     offer->set_rtcp_reduced_size(true);
   }
   offer->set_rtp_header_extensions(rtp_extensions);
 
-  if (!AddStreamParams(media_description_options.sender_options,
-                       session_options.rtcp_cname, ssrc_generator,
-                       current_streams, offer)) {
-    return false;
-  }
-
   AddSimulcastToMediaDescription(media_description_options, offer);
 
   if (secure_policy != SEC_DISABLED) {
@@ -785,6 +750,30 @@
   }
   return true;
 }
+template <class C>
+static bool CreateMediaContentOffer(
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const std::vector<C>& codecs,
+    const SecurePolicy& secure_policy,
+    const CryptoParamsVec* current_cryptos,
+    const std::vector<std::string>& crypto_suites,
+    const RtpHeaderExtensions& rtp_extensions,
+    UniqueRandomIdGenerator* ssrc_generator,
+    StreamParamsVec* current_streams,
+    MediaContentDescriptionImpl<C>* offer) {
+  offer->AddCodecs(codecs);
+  if (!AddStreamParams(media_description_options.sender_options,
+                       session_options.rtcp_cname, ssrc_generator,
+                       current_streams, offer)) {
+    return false;
+  }
+
+  return CreateContentOffer(media_description_options, session_options,
+                            secure_policy, current_cryptos, crypto_suites,
+                            rtp_extensions, ssrc_generator, current_streams,
+                            offer);
+}
 
 template <class C>
 static bool ReferencedCodecsMatch(const std::vector<C>& codecs1,
@@ -1137,6 +1126,27 @@
                       audio_codecs->end());
 }
 
+template <class C>
+static bool SetCodecsInAnswer(
+    const MediaContentDescriptionImpl<C>* offer,
+    const std::vector<C>& local_codecs,
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    UniqueRandomIdGenerator* ssrc_generator,
+    StreamParamsVec* current_streams,
+    MediaContentDescriptionImpl<C>* answer) {
+  std::vector<C> negotiated_codecs;
+  NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs);
+  answer->AddCodecs(negotiated_codecs);
+  answer->set_protocol(offer->protocol());
+  if (!AddStreamParams(media_description_options.sender_options,
+                       session_options.rtcp_cname, ssrc_generator,
+                       current_streams, answer)) {
+    return false;  // Something went seriously wrong.
+  }
+  return true;
+}
+
 // Create a media content to be answered for the given |sender_options|
 // according to the given session_options.rtcp_mux, session_options.streams,
 // codecs, crypto, and current_streams.  If we don't currently have crypto (in
@@ -1144,12 +1154,10 @@
 // (according to crypto_suites). The codecs, rtcp_mux, and crypto are all
 // negotiated with the offer. If the negotiation fails, this method returns
 // false.  The created content is added to the offer.
-template <class C>
 static bool CreateMediaContentAnswer(
-    const MediaContentDescriptionImpl<C>* offer,
+    const MediaContentDescription* offer,
     const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
-    const std::vector<C>& local_codecs,
     const SecurePolicy& sdes_policy,
     const CryptoParamsVec* current_cryptos,
     const RtpHeaderExtensions& local_rtp_extenstions,
@@ -1157,12 +1165,7 @@
     bool enable_encrypted_rtp_header_extensions,
     StreamParamsVec* current_streams,
     bool bundle_enabled,
-    MediaContentDescriptionImpl<C>* answer) {
-  std::vector<C> negotiated_codecs;
-  NegotiateCodecs(local_codecs, offer->codecs(), &negotiated_codecs);
-  answer->AddCodecs(negotiated_codecs);
-  answer->set_protocol(offer->protocol());
-
+    MediaContentDescription* answer) {
   answer->set_extmap_allow_mixed_enum(offer->extmap_allow_mixed_enum());
   RtpHeaderExtensions negotiated_rtp_extensions;
   NegotiateRtpHeaderExtensions(
@@ -1190,12 +1193,6 @@
     return false;
   }
 
-  if (!AddStreamParams(media_description_options.sender_options,
-                       session_options.rtcp_cname, ssrc_generator,
-                       current_streams, answer)) {
-    return false;  // Something went seriously wrong.
-  }
-
   AddSimulcastToMediaDescription(media_description_options, answer);
 
   answer->set_direction(NegotiateRtpTransceiverDirection(
@@ -1780,7 +1777,10 @@
     } else if (IsMediaContentOfType(content, MEDIA_TYPE_DATA)) {
       const DataContentDescription* data =
           content->media_description()->as_data();
-      MergeCodecs<DataCodec>(data->codecs(), data_codecs, used_pltypes);
+      if (data) {
+        // Only relevant for RTP datachannels
+        MergeCodecs<DataCodec>(data->codecs(), data_codecs, used_pltypes);
+      }
     }
   }
 }
@@ -1861,13 +1861,16 @@
     } else if (IsMediaContentOfType(&content, MEDIA_TYPE_DATA)) {
       const DataContentDescription* data =
           content.media_description()->as_data();
-      for (const DataCodec& offered_data_codec : data->codecs()) {
-        if (!FindMatchingCodec<DataCodec>(data->codecs(),
-                                          filtered_offered_data_codecs,
-                                          offered_data_codec, nullptr) &&
-            FindMatchingCodec<DataCodec>(data->codecs(), data_codecs_,
-                                         offered_data_codec, nullptr)) {
-          filtered_offered_data_codecs.push_back(offered_data_codec);
+      if (data) {
+        // RTP data. This part is inactive for SCTP data.
+        for (const DataCodec& offered_data_codec : data->codecs()) {
+          if (!FindMatchingCodec<DataCodec>(data->codecs(),
+                                            filtered_offered_data_codecs,
+                                            offered_data_codec, nullptr) &&
+              FindMatchingCodec<DataCodec>(data->codecs(), data_codecs_,
+                                           offered_data_codec, nullptr)) {
+            filtered_offered_data_codecs.push_back(offered_data_codec);
+          }
         }
       }
     }
@@ -2140,6 +2143,90 @@
   return true;
 }
 
+bool MediaSessionDescriptionFactory::AddSctpDataContentForOffer(
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const ContentInfo* current_content,
+    const SessionDescription* current_description,
+    StreamParamsVec* current_streams,
+    SessionDescription* desc,
+    IceCredentialsIterator* ice_credentials) const {
+  std::unique_ptr<SctpDataContentDescription> data(
+      new SctpDataContentDescription());
+
+  bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
+
+  cricket::SecurePolicy sdes_policy =
+      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
+                                                         : secure();
+  std::vector<std::string> crypto_suites;
+  // SDES doesn't make sense for SCTP, so we disable it, and we only
+  // get SDES crypto suites for RTP-based data channels.
+  sdes_policy = cricket::SEC_DISABLED;
+  // Unlike SetMediaProtocol below, we need to set the protocol
+  // before we call CreateMediaContentOffer.  Otherwise,
+  // CreateMediaContentOffer won't know this is SCTP and will
+  // generate SSRCs rather than SIDs.
+  // TODO(deadbeef): Offer kMediaProtocolUdpDtlsSctp (or TcpDtlsSctp), once
+  // it's safe to do so. Older versions of webrtc would reject these
+  // protocols; see https://bugs.chromium.org/p/webrtc/issues/detail?id=7706.
+  data->set_protocol(secure_transport ? kMediaProtocolDtlsSctp
+                                      : kMediaProtocolSctp);
+
+  if (!CreateContentOffer(media_description_options, session_options,
+                          sdes_policy, GetCryptos(current_content),
+                          crypto_suites, RtpHeaderExtensions(), ssrc_generator_,
+                          current_streams, data.get())) {
+    return false;
+  }
+
+  desc->AddContent(media_description_options.mid, MediaProtocolType::kSctp,
+                   data.release());
+  if (!AddTransportOffer(media_description_options.mid,
+                         media_description_options.transport_options,
+                         current_description, desc, ice_credentials)) {
+    return false;
+  }
+  return true;
+}
+
+bool MediaSessionDescriptionFactory::AddRtpDataContentForOffer(
+    const MediaDescriptionOptions& media_description_options,
+    const MediaSessionOptions& session_options,
+    const ContentInfo* current_content,
+    const SessionDescription* current_description,
+    const DataCodecs& data_codecs,
+    StreamParamsVec* current_streams,
+    SessionDescription* desc,
+    IceCredentialsIterator* ice_credentials) const {
+  std::unique_ptr<DataContentDescription> data(new DataContentDescription());
+  bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
+
+  cricket::SecurePolicy sdes_policy =
+      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
+                                                         : secure();
+  std::vector<std::string> crypto_suites;
+    GetSupportedDataSdesCryptoSuiteNames(session_options.crypto_options,
+                                         &crypto_suites);
+  if (!CreateMediaContentOffer(
+          media_description_options, session_options, data_codecs, sdes_policy,
+          GetCryptos(current_content), crypto_suites, RtpHeaderExtensions(),
+          ssrc_generator_, current_streams, data.get())) {
+    return false;
+  }
+
+  data->set_bandwidth(kDataMaxBandwidth);
+  SetMediaProtocol(secure_transport, data.get());
+  desc->AddContent(media_description_options.mid, MediaProtocolType::kRtp,
+                   media_description_options.stopped, data.release());
+  if (!AddTransportOffer(media_description_options.mid,
+                         media_description_options.transport_options,
+                         current_description, desc, ice_credentials)) {
+    return false;
+  }
+  return true;
+}
+
 bool MediaSessionDescriptionFactory::AddDataContentForOffer(
     const MediaDescriptionOptions& media_description_options,
     const MediaSessionOptions& session_options,
@@ -2149,9 +2236,6 @@
     StreamParamsVec* current_streams,
     SessionDescription* desc,
     IceCredentialsIterator* ice_credentials) const {
-  bool secure_transport = (transport_desc_factory_->secure() != SEC_DISABLED);
-
-  std::unique_ptr<DataContentDescription> data(new DataContentDescription());
   bool is_sctp = (session_options.data_channel_type == DCT_SCTP);
   // If the DataChannel type is not specified, use the DataChannel type in
   // the current description.
@@ -2160,52 +2244,16 @@
     is_sctp = (current_content->media_description()->protocol() ==
                kMediaProtocolSctp);
   }
-
-  cricket::SecurePolicy sdes_policy =
-      IsDtlsActive(current_content, current_description) ? cricket::SEC_DISABLED
-                                                         : secure();
-  std::vector<std::string> crypto_suites;
   if (is_sctp) {
-    // SDES doesn't make sense for SCTP, so we disable it, and we only
-    // get SDES crypto suites for RTP-based data channels.
-    sdes_policy = cricket::SEC_DISABLED;
-    // Unlike SetMediaProtocol below, we need to set the protocol
-    // before we call CreateMediaContentOffer.  Otherwise,
-    // CreateMediaContentOffer won't know this is SCTP and will
-    // generate SSRCs rather than SIDs.
-    // TODO(deadbeef): Offer kMediaProtocolUdpDtlsSctp (or TcpDtlsSctp), once
-    // it's safe to do so. Older versions of webrtc would reject these
-    // protocols; see https://bugs.chromium.org/p/webrtc/issues/detail?id=7706.
-    data->set_protocol(secure_transport ? kMediaProtocolDtlsSctp
-                                        : kMediaProtocolSctp);
+    return AddSctpDataContentForOffer(
+        media_description_options, session_options, current_content,
+        current_description, current_streams, desc, ice_credentials);
   } else {
-    GetSupportedDataSdesCryptoSuiteNames(session_options.crypto_options,
-                                         &crypto_suites);
+    return AddRtpDataContentForOffer(media_description_options, session_options,
+                                     current_content, current_description,
+                                     data_codecs, current_streams, desc,
+                                     ice_credentials);
   }
-
-  // Even SCTP uses a "codec".
-  if (!CreateMediaContentOffer(
-          media_description_options, session_options, data_codecs, sdes_policy,
-          GetCryptos(current_content), crypto_suites, RtpHeaderExtensions(),
-          ssrc_generator_, current_streams, data.get())) {
-    return false;
-  }
-
-  if (is_sctp) {
-    desc->AddContent(media_description_options.mid, MediaProtocolType::kSctp,
-                     data.release());
-  } else {
-    data->set_bandwidth(kDataMaxBandwidth);
-    SetMediaProtocol(secure_transport, data.get());
-    desc->AddContent(media_description_options.mid, MediaProtocolType::kRtp,
-                     media_description_options.stopped, data.release());
-  }
-  if (!AddTransportOffer(media_description_options.mid,
-                         media_description_options.transport_options,
-                         current_description, desc, ice_credentials)) {
-    return false;
-  }
-  return true;
 }
 
 // |audio_codecs| = set of all possible codecs that can be used, with correct
@@ -2287,9 +2335,15 @@
   // Do not require or create SDES cryptos if DTLS is used.
   cricket::SecurePolicy sdes_policy =
       audio_transport->secure() ? cricket::SEC_DISABLED : secure();
+  if (!SetCodecsInAnswer(offer_audio_description, filtered_codecs,
+                         media_description_options, session_options,
+                         ssrc_generator_, current_streams,
+                         audio_answer.get())) {
+    return false;
+  }
   if (!CreateMediaContentAnswer(
           offer_audio_description, media_description_options, session_options,
-          filtered_codecs, sdes_policy, GetCryptos(current_content),
+          sdes_policy, GetCryptos(current_content),
           audio_rtp_header_extensions(), ssrc_generator_,
           enable_encrypted_rtp_header_extensions_, current_streams,
           bundle_enabled, audio_answer.get())) {
@@ -2376,9 +2430,15 @@
   // Do not require or create SDES cryptos if DTLS is used.
   cricket::SecurePolicy sdes_policy =
       video_transport->secure() ? cricket::SEC_DISABLED : secure();
+  if (!SetCodecsInAnswer(offer_video_description, filtered_codecs,
+                         media_description_options, session_options,
+                         ssrc_generator_, current_streams,
+                         video_answer.get())) {
+    return false;
+  }
   if (!CreateMediaContentAnswer(
           offer_video_description, media_description_options, session_options,
-          filtered_codecs, sdes_policy, GetCryptos(current_content),
+          sdes_policy, GetCryptos(current_content),
           video_rtp_header_extensions(), ssrc_generator_,
           enable_encrypted_rtp_header_extensions_, current_streams,
           bundle_enabled, video_answer.get())) {
@@ -2426,28 +2486,51 @@
     return false;
   }
 
-  std::unique_ptr<DataContentDescription> data_answer(
-      new DataContentDescription());
   // Do not require or create SDES cryptos if DTLS is used.
   cricket::SecurePolicy sdes_policy =
       data_transport->secure() ? cricket::SEC_DISABLED : secure();
   bool bundle_enabled = offer_description->HasGroup(GROUP_TYPE_BUNDLE) &&
                         session_options.bundle_enabled;
   RTC_CHECK(IsMediaContentOfType(offer_content, MEDIA_TYPE_DATA));
-  const DataContentDescription* offer_data_description =
-      offer_content->media_description()->as_data();
-  if (!CreateMediaContentAnswer(
-          offer_data_description, media_description_options, session_options,
-          data_codecs, sdes_policy, GetCryptos(current_content),
-          RtpHeaderExtensions(), ssrc_generator_,
-          enable_encrypted_rtp_header_extensions_, current_streams,
-          bundle_enabled, data_answer.get())) {
-    return false;  // Fails the session setup.
-  }
+  std::unique_ptr<MediaContentDescription> data_answer;
+  if (offer_content->media_description()->as_sctp()) {
+    // SCTP data content
+    data_answer = absl::make_unique<SctpDataContentDescription>();
+    const SctpDataContentDescription* offer_data_description =
+        offer_content->media_description()->as_sctp();
+    // Respond with the offerer's proto, whatever it is.
+    data_answer->as_sctp()->set_protocol(offer_data_description->protocol());
+    if (!CreateMediaContentAnswer(
+            offer_data_description, media_description_options, session_options,
+            sdes_policy, GetCryptos(current_content), RtpHeaderExtensions(),
+            ssrc_generator_, enable_encrypted_rtp_header_extensions_,
+            current_streams, bundle_enabled, data_answer.get())) {
+      return false;  // Fails the session setup.
+    }
+    // Respond with sctpmap if the offer uses sctpmap.
+    bool offer_uses_sctpmap = offer_data_description->use_sctpmap();
+    data_answer->as_sctp()->set_use_sctpmap(offer_uses_sctpmap);
+  } else {
+    // RTP offer
+    data_answer = absl::make_unique<DataContentDescription>();
 
-  // Respond with sctpmap if the offer uses sctpmap.
-  bool offer_uses_sctpmap = offer_data_description->use_sctpmap();
-  data_answer->set_use_sctpmap(offer_uses_sctpmap);
+    RTC_CHECK(offer_content->media_description()->as_data());
+    const DataContentDescription* offer_data_description =
+        offer_content->media_description()->as_data();
+    if (!SetCodecsInAnswer(offer_data_description, data_codecs,
+                           media_description_options, session_options,
+                           ssrc_generator_, current_streams,
+                           data_answer->as_data())) {
+      return false;
+    }
+    if (!CreateMediaContentAnswer(
+            offer_data_description, media_description_options, session_options,
+            sdes_policy, GetCryptos(current_content), RtpHeaderExtensions(),
+            ssrc_generator_, enable_encrypted_rtp_header_extensions_,
+            current_streams, bundle_enabled, data_answer.get())) {
+      return false;  // Fails the session setup.
+    }
+  }
 
   bool secure = bundle_transport ? bundle_transport->description.secure()
                                  : data_transport->secure();
@@ -2571,20 +2654,26 @@
 
 const AudioContentDescription* GetFirstAudioContentDescription(
     const SessionDescription* sdesc) {
-  return static_cast<const AudioContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO);
+  return desc ? desc->as_audio() : nullptr;
 }
 
 const VideoContentDescription* GetFirstVideoContentDescription(
     const SessionDescription* sdesc) {
-  return static_cast<const VideoContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO);
+  return desc ? desc->as_video() : nullptr;
 }
 
 const DataContentDescription* GetFirstDataContentDescription(
     const SessionDescription* sdesc) {
-  return static_cast<const DataContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA);
+  return desc ? desc->as_data() : nullptr;
+}
+
+const SctpDataContentDescription* GetFirstSctpDataContentDescription(
+    const SessionDescription* sdesc) {
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA);
+  return desc ? desc->as_sctp() : nullptr;
 }
 
 //
@@ -2643,20 +2732,26 @@
 
 AudioContentDescription* GetFirstAudioContentDescription(
     SessionDescription* sdesc) {
-  return static_cast<AudioContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_AUDIO);
+  return desc ? desc->as_audio() : nullptr;
 }
 
 VideoContentDescription* GetFirstVideoContentDescription(
     SessionDescription* sdesc) {
-  return static_cast<VideoContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_VIDEO);
+  return desc ? desc->as_video() : nullptr;
 }
 
 DataContentDescription* GetFirstDataContentDescription(
     SessionDescription* sdesc) {
-  return static_cast<DataContentDescription*>(
-      GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA));
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA);
+  return desc ? desc->as_data() : nullptr;
+}
+
+SctpDataContentDescription* GetFirstSctpDataContentDescription(
+    SessionDescription* sdesc) {
+  auto desc = GetFirstMediaContentDescription(sdesc, MEDIA_TYPE_DATA);
+  return desc ? desc->as_sctp() : nullptr;
 }
 
 }  // namespace cricket
diff --git a/pc/media_session.h b/pc/media_session.h
index 33c8c17..112508e 100644
--- a/pc/media_session.h
+++ b/pc/media_session.h
@@ -24,6 +24,7 @@
 #include "p2p/base/ice_credentials_iterator.h"
 #include "p2p/base/transport_description_factory.h"
 #include "pc/jsep_transport.h"
+#include "pc/media_protocol_names.h"
 #include "pc/session_description.h"
 #include "rtc_base/unique_id_generator.h"
 
@@ -239,6 +240,23 @@
       SessionDescription* desc,
       IceCredentialsIterator* ice_credentials) const;
 
+  bool AddSctpDataContentForOffer(
+      const MediaDescriptionOptions& media_description_options,
+      const MediaSessionOptions& session_options,
+      const ContentInfo* current_content,
+      const SessionDescription* current_description,
+      StreamParamsVec* current_streams,
+      SessionDescription* desc,
+      IceCredentialsIterator* ice_credentials) const;
+  bool AddRtpDataContentForOffer(
+      const MediaDescriptionOptions& media_description_options,
+      const MediaSessionOptions& session_options,
+      const ContentInfo* current_content,
+      const SessionDescription* current_description,
+      const DataCodecs& data_codecs,
+      StreamParamsVec* current_streams,
+      SessionDescription* desc,
+      IceCredentialsIterator* ice_credentials) const;
   bool AddDataContentForOffer(
       const MediaDescriptionOptions& media_description_options,
       const MediaSessionOptions& session_options,
@@ -331,6 +349,8 @@
     const SessionDescription* sdesc);
 const DataContentDescription* GetFirstDataContentDescription(
     const SessionDescription* sdesc);
+const SctpDataContentDescription* GetFirstSctpDataContentDescription(
+    const SessionDescription* sdesc);
 // Non-const versions of the above functions.
 // Useful when modifying an existing description.
 ContentInfo* GetFirstMediaContent(ContentInfos* contents, MediaType media_type);
@@ -348,6 +368,8 @@
     SessionDescription* sdesc);
 DataContentDescription* GetFirstDataContentDescription(
     SessionDescription* sdesc);
+SctpDataContentDescription* GetFirstSctpDataContentDescription(
+    SessionDescription* sdesc);
 
 // Helper functions to return crypto suites used for SDES.
 void GetSupportedAudioSdesCryptoSuites(
@@ -369,9 +391,6 @@
     const webrtc::CryptoOptions& crypto_options,
     std::vector<std::string>* crypto_suite_names);
 
-// Returns true if the given media section protocol indicates use of RTP.
-bool IsRtpProtocol(const std::string& protocol);
-
 }  // namespace cricket
 
 #endif  // PC_MEDIA_SESSION_H_
diff --git a/pc/media_session_unittest.cc b/pc/media_session_unittest.cc
index 1136607..bd01041 100644
--- a/pc/media_session_unittest.cc
+++ b/pc/media_session_unittest.cc
@@ -62,6 +62,7 @@
 using cricket::MediaType;
 using cricket::RidDescription;
 using cricket::RidDirection;
+using cricket::SctpDataContentDescription;
 using cricket::SEC_DISABLED;
 using cricket::SEC_ENABLED;
 using cricket::SEC_REQUIRED;
@@ -1336,15 +1337,16 @@
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* dc_offer = offer->GetContentByName("data");
   ASSERT_TRUE(dc_offer != NULL);
-  DataContentDescription* dcd_offer = dc_offer->media_description()->as_data();
+  SctpDataContentDescription* dcd_offer =
+      dc_offer->media_description()->as_sctp();
   EXPECT_TRUE(dcd_offer->use_sctpmap());
 
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, NULL);
   const ContentInfo* dc_answer = answer->GetContentByName("data");
   ASSERT_TRUE(dc_answer != NULL);
-  const DataContentDescription* dcd_answer =
-      dc_answer->media_description()->as_data();
+  const SctpDataContentDescription* dcd_answer =
+      dc_answer->media_description()->as_sctp();
   EXPECT_TRUE(dcd_answer->use_sctpmap());
 }
 
@@ -1356,15 +1358,16 @@
   ASSERT_TRUE(offer.get() != NULL);
   ContentInfo* dc_offer = offer->GetContentByName("data");
   ASSERT_TRUE(dc_offer != NULL);
-  DataContentDescription* dcd_offer = dc_offer->media_description()->as_data();
+  SctpDataContentDescription* dcd_offer =
+      dc_offer->media_description()->as_sctp();
   dcd_offer->set_use_sctpmap(false);
 
   std::unique_ptr<SessionDescription> answer =
       f2_.CreateAnswer(offer.get(), opts, NULL);
   const ContentInfo* dc_answer = answer->GetContentByName("data");
   ASSERT_TRUE(dc_answer != NULL);
-  const DataContentDescription* dcd_answer =
-      dc_answer->media_description()->as_data();
+  const SctpDataContentDescription* dcd_answer =
+      dc_answer->media_description()->as_sctp();
   EXPECT_FALSE(dcd_answer->use_sctpmap());
 }
 
@@ -1385,7 +1388,9 @@
   ASSERT_TRUE(offer.get() != nullptr);
   ContentInfo* dc_offer = offer->GetContentByName("data");
   ASSERT_TRUE(dc_offer != nullptr);
-  DataContentDescription* dcd_offer = dc_offer->media_description()->as_data();
+  SctpDataContentDescription* dcd_offer =
+      dc_offer->media_description()->as_sctp();
+  ASSERT_TRUE(dcd_offer);
 
   std::vector<std::string> protos = {"DTLS/SCTP", "UDP/DTLS/SCTP",
                                      "TCP/DTLS/SCTP"};
@@ -1395,8 +1400,8 @@
         f2_.CreateAnswer(offer.get(), opts, nullptr);
     const ContentInfo* dc_answer = answer->GetContentByName("data");
     ASSERT_TRUE(dc_answer != nullptr);
-    const DataContentDescription* dcd_answer =
-        dc_answer->media_description()->as_data();
+    const SctpDataContentDescription* dcd_answer =
+        dc_answer->media_description()->as_sctp();
     EXPECT_FALSE(dc_answer->rejected);
     EXPECT_EQ(proto, dcd_answer->protocol());
   }
@@ -1480,7 +1485,8 @@
   ASSERT_TRUE(dc_offer != NULL);
   DataContentDescription* dcd_offer = dc_offer->media_description()->as_data();
   ASSERT_TRUE(dcd_offer != NULL);
-  std::string protocol = "a weird unknown protocol";
+  // Offer must be acceptable as an RTP protocol in order to be set.
+  std::string protocol = "RTP/a weird unknown protocol";
   dcd_offer->set_protocol(protocol);
 
   std::unique_ptr<SessionDescription> answer =
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 8ac1e83..21dfcd4 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -558,24 +558,13 @@
 // Get the SCTP port out of a SessionDescription.
 // Return -1 if not found.
 int GetSctpPort(const SessionDescription* session_description) {
-  const cricket::DataContentDescription* data_desc =
-      GetFirstDataContentDescription(session_description);
+  const cricket::SctpDataContentDescription* data_desc =
+      GetFirstSctpDataContentDescription(session_description);
   RTC_DCHECK(data_desc);
   if (!data_desc) {
     return -1;
   }
-  std::string value;
-  cricket::DataCodec match_pattern(cricket::kGoogleSctpDataCodecPlType,
-                                   cricket::kGoogleSctpDataCodecName);
-  for (const cricket::DataCodec& codec : data_desc->codecs()) {
-    if (!codec.Matches(match_pattern)) {
-      continue;
-    }
-    if (codec.GetParam(cricket::kCodecParamPort, &value)) {
-      return rtc::FromString<int>(value);
-    }
-  }
-  return -1;
+  return data_desc->port();
 }
 
 // Returns true if |new_desc| requests an ICE restart (i.e., new ufrag/pwd).
@@ -2415,8 +2404,9 @@
   if (data_content) {
     const cricket::DataContentDescription* data_desc =
         data_content->media_description()->as_data();
-    if (absl::StartsWith(data_desc->protocol(),
-                         cricket::kMediaProtocolRtpPrefix)) {
+    // data_desc will be null if this is an SCTP description.
+    if (data_desc && absl::StartsWith(data_desc->protocol(),
+                                      cricket::kMediaProtocolRtpPrefix)) {
       UpdateLocalRtpDataChannels(data_desc->streams());
     }
   }
diff --git a/pc/peer_connection_data_channel_unittest.cc b/pc/peer_connection_data_channel_unittest.cc
index ad3817e..4080dd9 100644
--- a/pc/peer_connection_data_channel_unittest.cc
+++ b/pc/peer_connection_data_channel_unittest.cc
@@ -193,14 +193,11 @@
   // Changes the SCTP data channel port on the given session description.
   void ChangeSctpPortOnDescription(cricket::SessionDescription* desc,
                                    int port) {
-    cricket::DataCodec sctp_codec(cricket::kGoogleSctpDataCodecPlType,
-                                  cricket::kGoogleSctpDataCodecName);
-    sctp_codec.SetParam(cricket::kCodecParamPort, port);
-
     auto* data_content = cricket::GetFirstDataContent(desc);
     RTC_DCHECK(data_content);
-    auto* data_desc = data_content->media_description()->as_data();
-    data_desc->set_codecs({sctp_codec});
+    auto* data_desc = data_content->media_description()->as_sctp();
+    RTC_DCHECK(data_desc);
+    data_desc->set_port(port);
   }
 
   std::unique_ptr<rtc::VirtualSocketServer> vss_;
diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc
index 8aa7675..a49ee39 100644
--- a/pc/peer_connection_integrationtest.cc
+++ b/pc/peer_connection_integrationtest.cc
@@ -3443,8 +3443,8 @@
 }
 
 static void MakeSpecCompliantSctpOffer(cricket::SessionDescription* desc) {
-  cricket::DataContentDescription* dcd_offer =
-      GetFirstDataContentDescription(desc);
+  cricket::SctpDataContentDescription* dcd_offer =
+      GetFirstSctpDataContentDescription(desc);
   ASSERT_TRUE(dcd_offer);
   dcd_offer->set_use_sctpmap(false);
   dcd_offer->set_protocol("UDP/DTLS/SCTP");
diff --git a/pc/session_description.h b/pc/session_description.h
index 7b70ddf..e5a7dfa 100644
--- a/pc/session_description.h
+++ b/pc/session_description.h
@@ -26,6 +26,7 @@
 #include "media/base/stream_params.h"
 #include "p2p/base/transport_description.h"
 #include "p2p/base/transport_info.h"
+#include "pc/media_protocol_names.h"
 #include "pc/simulcast_description.h"
 #include "rtc_base/socket_address.h"
 
@@ -44,12 +45,6 @@
 
 extern const char kMediaProtocolDtlsSavpf[];
 
-extern const char kMediaProtocolRtpPrefix[];
-
-extern const char kMediaProtocolSctp[];
-extern const char kMediaProtocolDtlsSctp[];
-extern const char kMediaProtocolUdpDtlsSctp[];
-extern const char kMediaProtocolTcpDtlsSctp[];
 
 // Options to control how session descriptions are generated.
 const int kAutoBandwidth = -1;
@@ -57,6 +52,7 @@
 class AudioContentDescription;
 class DataContentDescription;
 class VideoContentDescription;
+class SctpDataContentDescription;
 
 // Describes a session description media section. There are subclasses for each
 // media type (audio, video, data) that will have additional information.
@@ -82,6 +78,9 @@
   virtual DataContentDescription* as_data() { return nullptr; }
   virtual const DataContentDescription* as_data() const { return nullptr; }
 
+  virtual SctpDataContentDescription* as_sctp() { return nullptr; }
+  virtual const SctpDataContentDescription* as_sctp() const { return nullptr; }
+
   virtual bool has_codecs() const = 0;
 
   virtual MediaContentDescription* Copy() const = 0;
@@ -89,7 +88,9 @@
   // |protocol| is the expected media transport protocol, such as RTP/AVPF,
   // RTP/SAVPF or SCTP/DTLS.
   std::string protocol() const { return protocol_; }
-  void set_protocol(const std::string& protocol) { protocol_ = protocol; }
+  virtual void set_protocol(const std::string& protocol) {
+    protocol_ = protocol;
+  }
 
   webrtc::RtpTransceiverDirection direction() const { return direction_; }
   void set_direction(webrtc::RtpTransceiverDirection direction) {
@@ -247,12 +248,17 @@
 template <class C>
 class MediaContentDescriptionImpl : public MediaContentDescription {
  public:
+  void set_protocol(const std::string& protocol) override {
+    RTC_DCHECK(IsRtpProtocol(protocol));
+    protocol_ = protocol;
+  }
+
   typedef C CodecType;
 
   // Codecs should be in preference order (most preferred codec first).
   const std::vector<C>& codecs() const { return codecs_; }
   void set_codecs(const std::vector<C>& codecs) { codecs_ = codecs; }
-  virtual bool has_codecs() const { return !codecs_.empty(); }
+  bool has_codecs() const override { return !codecs_.empty(); }
   bool HasCodec(int id) {
     bool found = false;
     for (typename std::vector<C>::iterator iter = codecs_.begin();
@@ -318,12 +324,37 @@
   virtual MediaType type() const { return MEDIA_TYPE_DATA; }
   virtual DataContentDescription* as_data() { return this; }
   virtual const DataContentDescription* as_data() const { return this; }
+};
+
+class SctpDataContentDescription : public MediaContentDescription {
+ public:
+  SctpDataContentDescription() {}
+  SctpDataContentDescription* Copy() const override {
+    return new SctpDataContentDescription(*this);
+  }
+  MediaType type() const override { return MEDIA_TYPE_DATA; }
+  SctpDataContentDescription* as_sctp() override { return this; }
+  const SctpDataContentDescription* as_sctp() const override { return this; }
+  bool has_codecs() const override { return false; }
+  void set_protocol(const std::string& protocol) override {
+    RTC_DCHECK(IsSctpProtocol(protocol));
+    protocol_ = protocol;
+  }
 
   bool use_sctpmap() const { return use_sctpmap_; }
   void set_use_sctpmap(bool enable) { use_sctpmap_ = enable; }
+  int port() const { return port_; }
+  void set_port(int port) { port_ = port; }
+  int max_message_size() const { return max_message_size_; }
+  void set_max_message_size(int max_message_size) {
+    max_message_size_ = max_message_size;
+  }
 
  private:
-  bool use_sctpmap_ = true;
+  bool use_sctpmap_ = true;  // Note: "true" is no longer conformant.
+  // Defaults should be constants imported from SCTP. Quick hack.
+  int port_ = 5000;
+  int max_message_size_ = 256 * 1024;
 };
 
 // Protocol used for encoding media. This is the "top level" protocol that may
diff --git a/pc/webrtc_sdp.cc b/pc/webrtc_sdp.cc
index 984a1e1..7c65f87 100644
--- a/pc/webrtc_sdp.cc
+++ b/pc/webrtc_sdp.cc
@@ -54,29 +54,30 @@
 using cricket::ContentInfo;
 using cricket::CryptoParams;
 using cricket::DataContentDescription;
-using cricket::ICE_CANDIDATE_COMPONENT_RTP;
 using cricket::ICE_CANDIDATE_COMPONENT_RTCP;
+using cricket::ICE_CANDIDATE_COMPONENT_RTP;
+using cricket::kCodecParamAssociatedPayloadType;
+using cricket::kCodecParamMaxAverageBitrate;
 using cricket::kCodecParamMaxBitrate;
+using cricket::kCodecParamMaxPlaybackRate;
 using cricket::kCodecParamMaxPTime;
 using cricket::kCodecParamMaxQuantization;
 using cricket::kCodecParamMinBitrate;
 using cricket::kCodecParamMinPTime;
 using cricket::kCodecParamPTime;
+using cricket::kCodecParamSctpProtocol;
+using cricket::kCodecParamSctpStreams;
 using cricket::kCodecParamSPropStereo;
 using cricket::kCodecParamStartBitrate;
 using cricket::kCodecParamStereo;
-using cricket::kCodecParamUseInbandFec;
 using cricket::kCodecParamUseDtx;
-using cricket::kCodecParamSctpProtocol;
-using cricket::kCodecParamSctpStreams;
-using cricket::kCodecParamMaxAverageBitrate;
-using cricket::kCodecParamMaxPlaybackRate;
-using cricket::kCodecParamAssociatedPayloadType;
+using cricket::kCodecParamUseInbandFec;
 using cricket::MediaContentDescription;
-using cricket::MediaType;
-using cricket::RtpHeaderExtensions;
 using cricket::MediaProtocolType;
+using cricket::MediaType;
 using cricket::RidDescription;
+using cricket::RtpHeaderExtensions;
+using cricket::SctpDataContentDescription;
 using cricket::SimulcastDescription;
 using cricket::SimulcastLayer;
 using cricket::SimulcastLayerList;
@@ -1337,8 +1338,6 @@
   const MediaContentDescription* media_desc = content_info->media_description();
   RTC_DCHECK(media_desc);
 
-  int sctp_port = cricket::kSctpDefaultPort;
-
   // RFC 4566
   // m=<media> <port> <proto> <fmt>
   // fmt is a list of payload type numbers that MAY be used in the session.
@@ -1366,24 +1365,18 @@
       fmt.append(rtc::ToString(codec.id));
     }
   } else if (media_type == cricket::MEDIA_TYPE_DATA) {
-    const DataContentDescription* data_desc = media_desc->as_data();
     if (IsDtlsSctp(media_desc->protocol())) {
+      const cricket::SctpDataContentDescription* data_desc =
+          media_desc->as_sctp();
       fmt.append(" ");
 
       if (data_desc->use_sctpmap()) {
-        for (const cricket::DataCodec& codec : data_desc->codecs()) {
-          if (absl::EqualsIgnoreCase(codec.name,
-                                     cricket::kGoogleSctpDataCodecName) &&
-              codec.GetParam(cricket::kCodecParamPort, &sctp_port)) {
-            break;
-          }
-        }
-
-        fmt.append(rtc::ToString(sctp_port));
+        fmt.append(rtc::ToString(data_desc->port()));
       } else {
         fmt.append(kDefaultSctpmapProtocol);
       }
     } else {
+      const DataContentDescription* data_desc = media_desc->as_data();
       for (const cricket::DataCodec& codec : data_desc->codecs()) {
         fmt.append(" ");
         fmt.append(rtc::ToString(codec.id));
@@ -1523,9 +1516,10 @@
   AddLine(os.str(), message);
 
   if (IsDtlsSctp(media_desc->protocol())) {
-    const DataContentDescription* data_desc = media_desc->as_data();
+    const cricket::SctpDataContentDescription* data_desc =
+        media_desc->as_sctp();
     bool use_sctpmap = data_desc->use_sctpmap();
-    BuildSctpContentAttributes(message, sctp_port, use_sctpmap);
+    BuildSctpContentAttributes(message, data_desc->port(), use_sctpmap);
   } else if (IsRtp(media_desc->protocol())) {
     BuildRtpContentAttributes(media_desc, media_type, msid_signaling, message);
   }
@@ -1834,43 +1828,6 @@
   }
 }
 
-cricket::DataCodec FindOrMakeSctpDataCodec(DataContentDescription* media_desc) {
-  for (const auto& codec : media_desc->codecs()) {
-    if (absl::EqualsIgnoreCase(codec.name, cricket::kGoogleSctpDataCodecName)) {
-      return codec;
-    }
-  }
-  cricket::DataCodec codec_port(cricket::kGoogleSctpDataCodecPlType,
-                                cricket::kGoogleSctpDataCodecName);
-  return codec_port;
-}
-
-bool AddOrModifySctpDataCodecPort(DataContentDescription* media_desc,
-                                  int sctp_port) {
-  // Add the SCTP Port number as a pseudo-codec "port" parameter
-  auto codec = FindOrMakeSctpDataCodec(media_desc);
-  int dummy;
-  if (codec.GetParam(cricket::kCodecParamPort, &dummy)) {
-    return false;
-  }
-  codec.SetParam(cricket::kCodecParamPort, sctp_port);
-  media_desc->AddOrReplaceCodec(codec);
-  return true;
-}
-
-bool AddOrModifySctpDataMaxMessageSize(DataContentDescription* media_desc,
-                                       int max_message_size) {
-  // Add the SCTP Max Message Size as a pseudo-parameter to the codec
-  auto codec = FindOrMakeSctpDataCodec(media_desc);
-  int dummy;
-  if (codec.GetParam(cricket::kCodecParamMaxMessageSize, &dummy)) {
-    return false;
-  }
-  codec.SetParam(cricket::kCodecParamMaxMessageSize, max_message_size);
-  media_desc->AddOrReplaceCodec(codec);
-  return true;
-}
-
 bool GetMinValue(const std::vector<int>& values, int* value) {
   if (values.empty()) {
     return false;
@@ -2748,24 +2705,30 @@
           payload_types, pos, &content_name, &bundle_only,
           &section_msid_signaling, &transport, candidates, error);
     } else if (HasAttribute(line, kMediaTypeData)) {
-      std::unique_ptr<DataContentDescription> data_desc =
-          ParseContentDescription<DataContentDescription>(
-              message, cricket::MEDIA_TYPE_DATA, mline_index, protocol,
-              payload_types, pos, &content_name, &bundle_only,
-              &section_msid_signaling, &transport, candidates, error);
-
-      if (data_desc && IsDtlsSctp(protocol)) {
+      if (IsDtlsSctp(protocol)) {
+        auto data_desc = absl::make_unique<SctpDataContentDescription>();
         int p;
         if (rtc::FromString(fields[3], &p)) {
-          if (!AddOrModifySctpDataCodecPort(data_desc.get(), p)) {
-            return false;
-          }
+          data_desc->set_port(p);
         } else if (fields[3] == kDefaultSctpmapProtocol) {
           data_desc->set_use_sctpmap(false);
         }
+        if (!ParseContent(message, cricket::MEDIA_TYPE_DATA, mline_index,
+                          protocol, payload_types, pos, &content_name,
+                          &bundle_only, &section_msid_signaling,
+                          data_desc.get(), &transport, candidates, error)) {
+          return false;
+        }
+        content = std::move(data_desc);
+      } else {
+        // RTP
+        std::unique_ptr<DataContentDescription> data_desc =
+            ParseContentDescription<DataContentDescription>(
+                message, cricket::MEDIA_TYPE_DATA, mline_index, protocol,
+                payload_types, pos, &content_name, &bundle_only,
+                &section_msid_signaling, &transport, candidates, error);
+        content = std::move(data_desc);
       }
-
-      content = std::move(data_desc);
     } else {
       RTC_LOG(LS_WARNING) << "Unsupported media type: " << line;
       continue;
@@ -3138,13 +3101,15 @@
             line, "sctp-port attribute found in non-data media description.",
             error);
       }
+      if (media_desc->as_sctp()->use_sctpmap()) {
+        return ParseFailed(
+            line, "sctp-port attribute can't be used with sctpmap.", error);
+      }
       int sctp_port;
       if (!ParseSctpPort(line, &sctp_port, error)) {
         return false;
       }
-      if (!AddOrModifySctpDataCodecPort(media_desc->as_data(), sctp_port)) {
-        return false;
-      }
+      media_desc->as_sctp()->set_port(sctp_port);
     } else if (IsDtlsSctp(protocol) &&
                HasAttribute(line, kAttributeMaxMessageSize)) {
       if (media_type != cricket::MEDIA_TYPE_DATA) {
@@ -3157,10 +3122,7 @@
       if (!ParseSctpMaxMessageSize(line, &max_message_size, error)) {
         return false;
       }
-      if (!AddOrModifySctpDataMaxMessageSize(media_desc->as_data(),
-                                             max_message_size)) {
-        return false;
-      }
+      media_desc->as_sctp()->set_max_message_size(max_message_size);
     } else if (IsRtp(protocol)) {
       //
       // RTP specific attrubtes
diff --git a/pc/webrtc_sdp_unittest.cc b/pc/webrtc_sdp_unittest.cc
index 3de2b60..78fc8e0 100644
--- a/pc/webrtc_sdp_unittest.cc
+++ b/pc/webrtc_sdp_unittest.cc
@@ -65,6 +65,7 @@
 using cricket::RELAY_PORT_TYPE;
 using cricket::RidDescription;
 using cricket::RidDirection;
+using cricket::SctpDataContentDescription;
 using cricket::SessionDescription;
 using cricket::SimulcastDescription;
 using cricket::SimulcastLayer;
@@ -1445,10 +1446,17 @@
 
   void CompareDataContentDescription(const DataContentDescription* dcd1,
                                      const DataContentDescription* dcd2) {
-    EXPECT_EQ(dcd1->use_sctpmap(), dcd2->use_sctpmap());
     CompareMediaContentDescription<DataContentDescription>(dcd1, dcd2);
   }
 
+  void CompareSctpDataContentDescription(
+      const SctpDataContentDescription* dcd1,
+      const SctpDataContentDescription* dcd2) {
+    EXPECT_EQ(dcd1->use_sctpmap(), dcd2->use_sctpmap());
+    EXPECT_EQ(dcd1->port(), dcd2->port());
+    EXPECT_EQ(dcd1->max_message_size(), dcd2->max_message_size());
+  }
+
   void CompareSessionDescription(const SessionDescription& desc1,
                                  const SessionDescription& desc2) {
     // Compare content descriptions.
@@ -1484,10 +1492,21 @@
       }
 
       ASSERT_EQ(IsDataContent(&c1), IsDataContent(&c2));
-      if (IsDataContent(&c1)) {
-        const DataContentDescription* dcd1 = c1.media_description()->as_data();
-        const DataContentDescription* dcd2 = c2.media_description()->as_data();
-        CompareDataContentDescription(dcd1, dcd2);
+      if (c1.media_description()->as_sctp()) {
+        ASSERT_TRUE(c2.media_description()->as_sctp());
+        const SctpDataContentDescription* scd1 =
+            c1.media_description()->as_sctp();
+        const SctpDataContentDescription* scd2 =
+            c2.media_description()->as_sctp();
+        CompareSctpDataContentDescription(scd1, scd2);
+      } else {
+        if (IsDataContent(&c1)) {
+          const DataContentDescription* dcd1 =
+              c1.media_description()->as_data();
+          const DataContentDescription* dcd2 =
+              c2.media_description()->as_data();
+          CompareDataContentDescription(dcd1, dcd2);
+        }
       }
 
       CompareSimulcastDescription(
@@ -1760,14 +1779,12 @@
   }
 
   void AddSctpDataChannel(bool use_sctpmap) {
-    std::unique_ptr<DataContentDescription> data(new DataContentDescription());
-    data_desc_ = data.get();
-    data_desc_->set_use_sctpmap(use_sctpmap);
-    data_desc_->set_protocol(cricket::kMediaProtocolDtlsSctp);
-    DataCodec codec(cricket::kGoogleSctpDataCodecPlType,
-                    cricket::kGoogleSctpDataCodecName);
-    codec.SetParam(cricket::kCodecParamPort, kDefaultSctpPort);
-    data_desc_->AddCodec(codec);
+    std::unique_ptr<SctpDataContentDescription> data(
+        new SctpDataContentDescription());
+    sctp_desc_ = data.get();
+    sctp_desc_->set_use_sctpmap(use_sctpmap);
+    sctp_desc_->set_protocol(cricket::kMediaProtocolDtlsSctp);
+    sctp_desc_->set_port(kDefaultSctpPort);
     desc_.AddContent(kDataContentName, MediaProtocolType::kSctp,
                      data.release());
     desc_.AddTransportInfo(TransportInfo(
@@ -2044,6 +2061,7 @@
   AudioContentDescription* audio_desc_;
   VideoContentDescription* video_desc_;
   DataContentDescription* data_desc_;
+  SctpDataContentDescription* sctp_desc_;
   Candidates candidates_;
   std::unique_ptr<IceCandidateInterface> jcandidate_;
   JsepSessionDescription jdesc_;
@@ -2215,21 +2233,26 @@
   EXPECT_EQ(message, expected_sdp);
 }
 
+void MutateJsepSctpPort(JsepSessionDescription* jdesc,
+                        const SessionDescription& desc,
+                        int port) {
+  // Take our pre-built session description and change the SCTP port.
+  cricket::SessionDescription* mutant = desc.Copy();
+  SctpDataContentDescription* dcdesc =
+      mutant->GetContentDescriptionByName(kDataContentName)->as_sctp();
+  dcdesc->set_port(port);
+  // Note: mutant's owned by jdesc now.
+  ASSERT_TRUE(jdesc->Initialize(mutant, kSessionId, kSessionVersion));
+}
+
 TEST_F(WebRtcSdpTest, SerializeWithSctpDataChannelAndNewPort) {
   bool use_sctpmap = true;
   AddSctpDataChannel(use_sctpmap);
   JsepSessionDescription jsep_desc(kDummyType);
   MakeDescriptionWithoutCandidates(&jsep_desc);
-  DataContentDescription* dcdesc =
-      jsep_desc.description()
-          ->GetContentDescriptionByName(kDataContentName)
-          ->as_data();
 
   const int kNewPort = 1234;
-  cricket::DataCodec codec(cricket::kGoogleSctpDataCodecPlType,
-                           cricket::kGoogleSctpDataCodecName);
-  codec.SetParam(cricket::kCodecParamPort, kNewPort);
-  dcdesc->AddOrReplaceCodec(codec);
+  MutateJsepSctpPort(&jsep_desc, desc_, kNewPort);
 
   std::string message = webrtc::SdpSerialize(jsep_desc);
 
@@ -2868,14 +2891,12 @@
 // Helper function to set the max-message-size parameter in the
 // SCTP data codec.
 void MutateJsepSctpMaxMessageSize(const SessionDescription& desc,
-                                  const std::string& new_value,
+                                  int new_value,
                                   JsepSessionDescription* jdesc) {
   cricket::SessionDescription* mutant = desc.Copy();
-  DataContentDescription* dcdesc =
-      mutant->GetContentDescriptionByName(kDataContentName)->as_data();
-  std::vector<cricket::DataCodec> codecs(dcdesc->codecs());
-  codecs[0].SetParam(cricket::kCodecParamMaxMessageSize, new_value);
-  dcdesc->set_codecs(codecs);
+  SctpDataContentDescription* dcdesc =
+      mutant->GetContentDescriptionByName(kDataContentName)->as_sctp();
+  dcdesc->set_max_message_size(new_value);
   jdesc->Initialize(mutant, kSessionId, kSessionVersion);
 }
 
@@ -2887,7 +2908,7 @@
 
   sdp_with_data.append(kSdpSctpDataChannelStringWithSctpColonPort);
   sdp_with_data.append("a=max-message-size:12345\r\n");
-  MutateJsepSctpMaxMessageSize(desc_, "12345", &jdesc);
+  MutateJsepSctpMaxMessageSize(desc_, 12345, &jdesc);
   JsepSessionDescription jdesc_output(kDummyType);
 
   // Verify with DTLS/SCTP.
@@ -2937,29 +2958,13 @@
   // No crash is a pass.
 }
 
-void MutateJsepSctpPort(JsepSessionDescription* jdesc,
-                        const SessionDescription& desc) {
-  // take our pre-built session description and change the SCTP port.
-  std::unique_ptr<cricket::SessionDescription> mutant = desc.Clone();
-  DataContentDescription* dcdesc =
-      mutant->GetContentDescriptionByName(kDataContentName)->as_data();
-  std::vector<cricket::DataCodec> codecs(dcdesc->codecs());
-  EXPECT_EQ(1U, codecs.size());
-  EXPECT_EQ(cricket::kGoogleSctpDataCodecPlType, codecs[0].id);
-  codecs[0].SetParam(cricket::kCodecParamPort, kUnusualSctpPort);
-  dcdesc->set_codecs(codecs);
-
-  ASSERT_TRUE(
-      jdesc->Initialize(std::move(mutant), kSessionId, kSessionVersion));
-}
-
 TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelAndUnusualPort) {
   bool use_sctpmap = true;
   AddSctpDataChannel(use_sctpmap);
 
   // First setup the expected JsepSessionDescription.
   JsepSessionDescription jdesc(kDummyType);
-  MutateJsepSctpPort(&jdesc, desc_);
+  MutateJsepSctpPort(&jdesc, desc_, kUnusualSctpPort);
 
   // Then get the deserialized JsepSessionDescription.
   std::string sdp_with_data = kSdpString;
@@ -2979,7 +2984,7 @@
   AddSctpDataChannel(use_sctpmap);
 
   JsepSessionDescription jdesc(kDummyType);
-  MutateJsepSctpPort(&jdesc, desc_);
+  MutateJsepSctpPort(&jdesc, desc_, kUnusualSctpPort);
 
   // We need to test the deserialized JsepSessionDescription from
   // kSdpSctpDataChannelStringWithSctpPort for
@@ -3015,7 +3020,7 @@
   bool use_sctpmap = true;
   AddSctpDataChannel(use_sctpmap);
   JsepSessionDescription jdesc(kDummyType);
-  DataContentDescription* dcd = GetFirstDataContentDescription(&desc_);
+  SctpDataContentDescription* dcd = GetFirstSctpDataContentDescription(&desc_);
   dcd->set_bandwidth(100 * 1000);
   ASSERT_TRUE(jdesc.Initialize(desc_.Clone(), kSessionId, kSessionVersion));