Add support for data channels with Unified Plan

Bug: webrtc:7600
Change-Id: Idca1219fa692b24ced104aff7e89cde8a1bfe301
Reviewed-on: https://webrtc-review.googlesource.com/36240
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Peter Thatcher <pthatcher@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21478}
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index 94f2db1..ee1cffe 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -2099,8 +2099,14 @@
         return error;
       }
     } else if (media_type == cricket::MEDIA_TYPE_DATA) {
-      // TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
-      // Plan.
+      if (GetDataMid() && new_content.name != *GetDataMid()) {
+        // Ignore all but the first data section.
+        continue;
+      }
+      RTCError error = UpdateDataChannel(source, new_content, bundle_group);
+      if (!error.ok()) {
+        return error;
+      }
     } else {
       LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
                            "Unknown section type.");
@@ -2147,6 +2153,34 @@
   return RTCError::OK();
 }
 
+RTCError PeerConnection::UpdateDataChannel(
+    cricket::ContentSource source,
+    const cricket::ContentInfo& content,
+    const cricket::ContentGroup* bundle_group) {
+  if (data_channel_type_ == cricket::DCT_NONE) {
+    LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
+                         "Data channels are not supported.");
+  }
+  if (content.rejected) {
+    DestroyDataChannel();
+  } else {
+    if (!rtp_data_channel_ && !sctp_transport_) {
+      if (!CreateDataChannel(content.name, GetTransportNameForMediaSection(
+                                               content.name, bundle_group))) {
+        LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
+                             "Failed to create data channel.");
+      }
+    }
+    if (source == cricket::CS_REMOTE) {
+      const MediaContentDescription* data_desc = content.media_description();
+      if (data_desc && cricket::IsRtpProtocol(data_desc->protocol())) {
+        UpdateRemoteRtpDataChannels(GetActiveStreams(data_desc));
+      }
+    }
+  }
+  return RTCError::OK();
+}
+
 RTCErrorOr<rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
 PeerConnection::AssociateTransceiver(cricket::ContentSource source,
                                      size_t mline_index,
@@ -2946,6 +2980,15 @@
     GetOptionsForPlanBOffer(offer_answer_options, session_options);
   }
 
+  // Intentionally unset the data channel type for RTP data channel with the
+  // second condition. Otherwise the RTP data channels would be successfully
+  // negotiated by default and the unit tests in WebRtcDataBrowserTest will fail
+  // when building with chromium. We want to leave RTP data channels broken, so
+  // people won't try to use them.
+  if (!rtp_data_channels_.empty() || data_channel_type() != cricket::DCT_RTP) {
+    session_options->data_channel_type = data_channel_type();
+  }
+
   // Apply ICE restart flag and renomination flag.
   for (auto& options : session_options->media_description_options) {
     options.transport_options.ice_restart = offer_answer_options.ice_restart;
@@ -3022,9 +3065,7 @@
   }
   if (!data_index && offer_new_data_description) {
     session_options->media_description_options.push_back(
-        cricket::MediaDescriptionOptions(
-            cricket::MEDIA_TYPE_DATA, cricket::CN_DATA,
-            RtpTransceiverDirection::kSendRecv, false));
+        GetMediaDescriptionOptionsForActiveData(cricket::CN_DATA));
     data_index = session_options->media_description_options.size() - 1;
   }
 
@@ -3034,22 +3075,9 @@
   cricket::MediaDescriptionOptions* video_media_description_options =
       !video_index ? nullptr
                    : &session_options->media_description_options[*video_index];
-  cricket::MediaDescriptionOptions* data_media_description_options =
-      !data_index ? nullptr
-                  : &session_options->media_description_options[*data_index];
 
   AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
                       video_media_description_options);
-  AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_options);
-
-  // Intentionally unset the data channel type for RTP data channel with the
-  // second condition. Otherwise the RTP data channels would be successfully
-  // negotiated by default and the unit tests in WebRtcDataBrowserTest will fail
-  // when building with chromium. We want to leave RTP data channels broken, so
-  // people won't try to use them.
-  if (!rtp_data_channels_.empty() || data_channel_type() != cricket::DCT_RTP) {
-    session_options->data_channel_type = data_channel_type();
-  }
 }
 
 // Find a new MID that is not already in |used_mids|, then add it to |used_mids|
@@ -3150,8 +3178,14 @@
       }
     } else {
       RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
-      // TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
-      // Plan.
+      RTC_CHECK(GetDataMid());
+      if (had_been_rejected || mid != *GetDataMid()) {
+        session_options->media_description_options.push_back(
+            GetMediaDescriptionOptionsForRejectedData(mid));
+      } else {
+        session_options->media_description_options.push_back(
+            GetMediaDescriptionOptionsForActiveData(mid));
+      }
     }
   }
   // Next, look for transceivers that are newly added (that is, are not stopped
@@ -3178,8 +3212,12 @@
     // See comment above for why CreateOffer changes the transceiver's state.
     transceiver->internal()->set_mline_index(mline_index);
   }
-  // TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
-  // Plan.
+  // Lastly, add a m-section if we have local data channels and an m section
+  // does not already exist.
+  if (!GetDataMid() && HasDataChannels()) {
+    session_options->media_description_options.push_back(
+        GetMediaDescriptionOptionsForActiveData(AllocateMid(&used_mids)));
+  }
 }
 
 void PeerConnection::GetOptionsForAnswer(
@@ -3193,6 +3231,14 @@
     GetOptionsForPlanBAnswer(offer_answer_options, session_options);
   }
 
+  // Intentionally unset the data channel type for RTP data channel. Otherwise
+  // the RTP data channels would be successfully negotiated by default and the
+  // unit tests in WebRtcDataBrowserTest will fail when building with chromium.
+  // We want to leave RTP data channels broken, so people won't try to use them.
+  if (!rtp_data_channels_.empty() || data_channel_type() != cricket::DCT_RTP) {
+    session_options->data_channel_type = data_channel_type();
+  }
+
   // Apply ICE renomination flag.
   for (auto& options : session_options->media_description_options) {
     options.transport_options.enable_ice_renomination =
@@ -3247,21 +3293,9 @@
   cricket::MediaDescriptionOptions* video_media_description_options =
       !video_index ? nullptr
                    : &session_options->media_description_options[*video_index];
-  cricket::MediaDescriptionOptions* data_media_description_options =
-      !data_index ? nullptr
-                  : &session_options->media_description_options[*data_index];
 
   AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
                       video_media_description_options);
-  AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_options);
-
-  // Intentionally unset the data channel type for RTP data channel. Otherwise
-  // the RTP data channels would be successfully negotiated by default and the
-  // unit tests in WebRtcDataBrowserTest will fail when building with chromium.
-  // We want to leave RTP data channels broken, so people won't try to use them.
-  if (!rtp_data_channels_.empty() || data_channel_type() != cricket::DCT_RTP) {
-    session_options->data_channel_type = data_channel_type();
-  }
 }
 
 void PeerConnection::GetOptionsForUnifiedPlanAnswer(
@@ -3282,8 +3316,16 @@
           GetMediaDescriptionOptionsForTransceiver(transceiver, content.name));
     } else {
       RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
-      // TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
-      // Plan.
+      RTC_CHECK(GetDataMid());
+      // Always reject a data section if it has already been rejected.
+      // Additionally, reject all data sections except for the first one.
+      if (content.rejected || content.name != *GetDataMid()) {
+        session_options->media_description_options.push_back(
+            GetMediaDescriptionOptionsForRejectedData(content.name));
+      } else {
+        session_options->media_description_options.push_back(
+            GetMediaDescriptionOptionsForActiveData(content.name));
+      }
     }
   }
 }
@@ -3331,22 +3373,52 @@
       // If we already have an data m= section, reject this extra one.
       if (*data_index) {
         session_options->media_description_options.push_back(
-            cricket::MediaDescriptionOptions(
-                cricket::MEDIA_TYPE_DATA, content.name,
-                RtpTransceiverDirection::kInactive, true));
+            GetMediaDescriptionOptionsForRejectedData(content.name));
       } else {
         session_options->media_description_options.push_back(
-            cricket::MediaDescriptionOptions(
-                cricket::MEDIA_TYPE_DATA, content.name,
-                // Direction for data sections is meaningless, but legacy
-                // endpoints might expect sendrecv.
-                RtpTransceiverDirection::kSendRecv, false));
+            GetMediaDescriptionOptionsForActiveData(content.name));
         *data_index = session_options->media_description_options.size() - 1;
       }
     }
   }
 }
 
+cricket::MediaDescriptionOptions
+PeerConnection::GetMediaDescriptionOptionsForActiveData(
+    const std::string& mid) const {
+  // Direction for data sections is meaningless, but legacy endpoints might
+  // expect sendrecv.
+  cricket::MediaDescriptionOptions options(cricket::MEDIA_TYPE_DATA, mid,
+                                           RtpTransceiverDirection::kSendRecv,
+                                           /*stopped=*/false);
+  AddRtpDataChannelOptions(rtp_data_channels_, &options);
+  return options;
+}
+
+cricket::MediaDescriptionOptions
+PeerConnection::GetMediaDescriptionOptionsForRejectedData(
+    const std::string& mid) const {
+  cricket::MediaDescriptionOptions options(cricket::MEDIA_TYPE_DATA, mid,
+                                           RtpTransceiverDirection::kInactive,
+                                           /*stopped=*/true);
+  AddRtpDataChannelOptions(rtp_data_channels_, &options);
+  return options;
+}
+
+rtc::Optional<std::string> PeerConnection::GetDataMid() const {
+  switch (data_channel_type_) {
+    case cricket::DCT_RTP:
+      if (!rtp_data_channel_) {
+        return rtc::nullopt;
+      }
+      return rtp_data_channel_->content_name();
+    case cricket::DCT_SCTP:
+      return sctp_content_name_;
+    default:
+      return rtc::nullopt;
+  }
+}
+
 void PeerConnection::RemoveSenders(cricket::MediaType media_type) {
   UpdateLocalSenders(std::vector<cricket::StreamParams>(), media_type);
   UpdateRemoteSendersList(std::vector<cricket::StreamParams>(), false,