Only create no-op DTLS if media transport is used for both media and data

Currently it's possible that no-op DTLS is created if media transport is only used for data channels.
Changing it so that no-op DTLS is only created when both media & data will flow through media transport.

Bug: webrtc:9719
Change-Id: I87f27fc778ea21b12f2904bad1452d893f66b541
Reviewed-on: https://webrtc-review.googlesource.com/c/119909
Commit-Queue: Peter Slatala <psla@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Anton Sukhanov <sukhanov@webrtc.org>
Reviewed-by: Bjorn Mellem <mellem@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26416}
diff --git a/api/peer_connection_interface.h b/api/peer_connection_interface.h
index 3115e25..a429519 100644
--- a/api/peer_connection_interface.h
+++ b/api/peer_connection_interface.h
@@ -602,9 +602,9 @@
     bool active_reset_srtp_params = false;
 
     // If MediaTransportFactory is provided in PeerConnectionFactory, this flag
-    // informs PeerConnection that it should use the MediaTransportInterface.
-    // It's invalid to set it to |true| if the MediaTransportFactory wasn't
-    // provided.
+    // informs PeerConnection that it should use the MediaTransportInterface for
+    // media (audio/video). It's invalid to set it to |true| if the
+    // MediaTransportFactory wasn't provided.
     bool use_media_transport = false;
 
     // If MediaTransportFactory is provided in PeerConnectionFactory, this flag
diff --git a/pc/jsep_transport_controller.cc b/pc/jsep_transport_controller.cc
index 084cec5..9cecc10 100644
--- a/pc/jsep_transport_controller.cc
+++ b/pc/jsep_transport_controller.cc
@@ -402,13 +402,24 @@
   }
 }
 
-void JsepTransportController::SetMediaTransportFactory(
-    MediaTransportFactory* media_transport_factory) {
-  RTC_DCHECK(media_transport_factory == config_.media_transport_factory ||
+void JsepTransportController::SetMediaTransportSettings(
+    bool use_media_transport_for_media,
+    bool use_media_transport_for_data_channels) {
+  RTC_DCHECK(use_media_transport_for_media ==
+                 config_.use_media_transport_for_media ||
              jsep_transports_by_name_.empty())
-      << "You can only call SetMediaTransportFactory before "
-         "JsepTransportController created its first transport.";
-  config_.media_transport_factory = media_transport_factory;
+      << "You can only change media transport configuration before creating "
+         "the first transport.";
+
+  RTC_DCHECK(use_media_transport_for_data_channels ==
+                 config_.use_media_transport_for_data_channels ||
+             jsep_transports_by_name_.empty())
+      << "You can only change media transport configuration before creating "
+         "the first transport.";
+
+  config_.use_media_transport_for_media = use_media_transport_for_media;
+  config_.use_media_transport_for_data_channels =
+      use_media_transport_for_data_channels;
 }
 
 std::unique_ptr<cricket::IceTransportInternal>
@@ -433,7 +444,12 @@
   RTC_DCHECK(network_thread_->IsCurrent());
 
   std::unique_ptr<cricket::DtlsTransportInternal> dtls;
-  if (is_media_transport_factory_enabled_ && config_.media_transport_factory) {
+  // If media transport is used for both media and data channels,
+  // then we don't need to create DTLS.
+  // Otherwise, DTLS is still created.
+  if (is_media_transport_factory_enabled_ && config_.media_transport_factory &&
+      config_.use_media_transport_for_media &&
+      config_.use_media_transport_for_data_channels) {
     dtls = absl::make_unique<cricket::NoOpDtlsTransport>(
         std::move(ice), config_.crypto_options);
   } else if (config_.external_transport_factory) {
@@ -954,6 +970,11 @@
     return nullptr;
   }
 
+  if (!config_.use_media_transport_for_media &&
+      !config_.use_media_transport_for_data_channels) {
+    return nullptr;
+  }
+
   absl::optional<cricket::CryptoParams> selected_crypto_for_media_transport;
   if (content_info.media_description() &&
       !content_info.media_description()->cryptos().empty()) {
diff --git a/pc/jsep_transport_controller.h b/pc/jsep_transport_controller.h
index 08dea61..f28c10c 100644
--- a/pc/jsep_transport_controller.h
+++ b/pc/jsep_transport_controller.h
@@ -83,10 +83,19 @@
     bool active_reset_srtp_params = false;
     RtcEventLog* event_log = nullptr;
 
+    // Whether media transport is used for media.
+    bool use_media_transport_for_media = false;
+
+    // Whether media transport is used for data channels.
+    bool use_media_transport_for_data_channels = false;
+
     // Optional media transport factory (experimental). If provided it will be
-    // used to create media_transport and will be used to send / receive
-    // audio and video frames instead of RTP. Note that currently
-    // media_transport co-exists with RTP / RTCP transports and uses the same
+    // used to create media_transport (as long as either
+    // |use_media_transport_for_media| or
+    // |use_media_transport_for_data_channels| is set to true). However, whether
+    // it will be used to send / receive audio and video frames instead of RTP
+    // is determined by |use_media_transport_for_media|. Note that currently
+    // media_transport co-exists with RTP / RTCP transports and may use the same
     // underlying ICE transport.
     MediaTransportFactory* media_transport_factory = nullptr;
   };
@@ -173,10 +182,11 @@
   void SetActiveResetSrtpParams(bool active_reset_srtp_params);
 
   // Allows to overwrite the settings from config. You may set or reset the
-  // media transport factory on the jsep transport controller, as long as you
-  // did not call 'GetMediaTransport' or 'MaybeCreateJsepTransport'. Once Jsep
-  // transport is created, you can't change this setting.
-  void SetMediaTransportFactory(MediaTransportFactory* media_transport_factory);
+  // media transport configuration on the jsep transport controller, as long as
+  // you did not call 'GetMediaTransport' or 'MaybeCreateJsepTransport'. Once
+  // Jsep transport is created, you can't change this setting.
+  void SetMediaTransportSettings(bool use_media_transport_for_media,
+                                 bool use_media_transport_for_data_channels);
 
   // All of these signals are fired on the signaling thread.
 
diff --git a/pc/jsep_transport_controller_unittest.cc b/pc/jsep_transport_controller_unittest.cc
index d6a31c8..43ba4ad 100644
--- a/pc/jsep_transport_controller_unittest.cc
+++ b/pc/jsep_transport_controller_unittest.cc
@@ -415,12 +415,48 @@
   EXPECT_EQ(nullptr, transport_controller_->GetMediaTransport(kAudioMid1));
 }
 
+TEST_F(JsepTransportControllerTest,
+       DtlsIsStillCreatedIfMediaTransportIsOnlyUsedForDataChannels) {
+  FakeMediaTransportFactory fake_media_transport_factory;
+  JsepTransportController::Config config;
+
+  config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
+  config.media_transport_factory = &fake_media_transport_factory;
+  config.use_media_transport_for_data_channels = true;
+  CreateJsepTransportController(config);
+  auto description = CreateSessionDescriptionWithBundleGroup();
+  AddCryptoSettings(description.get());
+
+  EXPECT_TRUE(transport_controller_
+                  ->SetLocalDescription(SdpType::kOffer, description.get())
+                  .ok());
+
+  FakeMediaTransport* media_transport = static_cast<FakeMediaTransport*>(
+      transport_controller_->GetMediaTransport(kAudioMid1));
+
+  ASSERT_NE(nullptr, media_transport);
+
+  // After SetLocalDescription, media transport should be created as caller.
+  EXPECT_TRUE(media_transport->is_caller());
+  EXPECT_TRUE(media_transport->pre_shared_key().has_value());
+
+  // Return nullptr for non-existing mids.
+  EXPECT_EQ(nullptr, transport_controller_->GetMediaTransport(kVideoMid2));
+
+  EXPECT_EQ(cricket::ICE_CANDIDATE_COMPONENT_RTP,
+            transport_controller_->GetDtlsTransport(kAudioMid1)->component())
+      << "Media transport for media was not enabled, and so DTLS transport "
+         "should be created.";
+}
+
 TEST_F(JsepTransportControllerTest, GetMediaTransportInCaller) {
   FakeMediaTransportFactory fake_media_transport_factory;
   JsepTransportController::Config config;
 
   config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
   config.media_transport_factory = &fake_media_transport_factory;
+  config.use_media_transport_for_data_channels = true;
+  config.use_media_transport_for_media = true;
   CreateJsepTransportController(config);
   auto description = CreateSessionDescriptionWithBundleGroup();
   AddCryptoSettings(description.get());
@@ -452,6 +488,8 @@
 
   config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
   config.media_transport_factory = &fake_media_transport_factory;
+  config.use_media_transport_for_data_channels = true;
+  config.use_media_transport_for_media = true;
   CreateJsepTransportController(config);
   auto description = CreateSessionDescriptionWithBundleGroup();
   AddCryptoSettings(description.get());
@@ -482,6 +520,7 @@
 
   config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyNegotiate;
   config.media_transport_factory = &fake_media_transport_factory;
+  config.use_media_transport_for_media = true;
   CreateJsepTransportController(config);
   auto description = CreateSessionDescriptionWithoutBundle();
   EXPECT_TRUE(transport_controller_
@@ -512,6 +551,7 @@
 
   config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
   config.media_transport_factory = &fake_media_transport_factory;
+  config.use_media_transport_for_media = true;
   CreateJsepTransportController(config);
   auto description = CreateSessionDescriptionWithoutBundle();
   AddCryptoSettings(description.get());
@@ -821,10 +861,12 @@
 }
 
 TEST_F(JsepTransportControllerTest,
-       SignalConnectionStateConnectedWithMediaTransport) {
+       SignalConnectionStateConnectedWithMediaTransportAndNoDtls) {
   FakeMediaTransportFactory fake_media_transport_factory;
   JsepTransportController::Config config;
   config.media_transport_factory = &fake_media_transport_factory;
+  config.use_media_transport_for_data_channels = true;
+  config.use_media_transport_for_media = true;
   CreateJsepTransportController(config);
 
   // Media Transport is only used with bundle.
@@ -865,10 +907,63 @@
 }
 
 TEST_F(JsepTransportControllerTest,
+       SignalConnectionStateConnectedWithMediaTransport) {
+  FakeMediaTransportFactory fake_media_transport_factory;
+  JsepTransportController::Config config;
+  config.media_transport_factory = &fake_media_transport_factory;
+  config.use_media_transport_for_media = true;
+  CreateJsepTransportController(config);
+
+  // Media Transport is only used with bundle.
+  auto description = CreateSessionDescriptionWithBundleGroup();
+  AddCryptoSettings(description.get());
+  EXPECT_TRUE(transport_controller_
+                  ->SetLocalDescription(SdpType::kOffer, description.get())
+                  .ok());
+
+  auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+      transport_controller_->GetDtlsTransport(kAudioMid1));
+  auto fake_video_dtls = static_cast<FakeDtlsTransport*>(
+      transport_controller_->GetDtlsTransport(kVideoMid1));
+
+  auto fake_audio_ice = static_cast<cricket::FakeIceTransport*>(
+      transport_controller_->GetDtlsTransport(kAudioMid1)->ice_transport());
+  auto fake_video_ice = static_cast<cricket::FakeIceTransport*>(
+      transport_controller_->GetDtlsTransport(kVideoMid1)->ice_transport());
+  fake_audio_ice->SetConnectionCount(2);
+  fake_audio_ice->SetConnectionCount(1);
+  fake_video_ice->SetConnectionCount(2);
+  fake_video_ice->SetConnectionCount(1);
+  fake_audio_ice->SetWritable(true);
+  fake_video_ice->SetWritable(true);
+  fake_audio_dtls->SetWritable(true);
+  fake_video_dtls->SetWritable(true);
+
+  // Still not connected, because we are waiting for media transport.
+  EXPECT_EQ_WAIT(cricket::kIceConnectionConnecting, connection_state_,
+                 kTimeout);
+
+  FakeMediaTransport* media_transport = static_cast<FakeMediaTransport*>(
+      transport_controller_->GetMediaTransport(kAudioMid1));
+
+  media_transport->SetState(webrtc::MediaTransportState::kWritable);
+  EXPECT_EQ_WAIT(cricket::kIceConnectionConnecting, connection_state_,
+                 kTimeout);
+
+  // Still waiting for the second media transport.
+  media_transport = static_cast<FakeMediaTransport*>(
+      transport_controller_->GetMediaTransport(kVideoMid1));
+  media_transport->SetState(webrtc::MediaTransportState::kWritable);
+
+  EXPECT_EQ_WAIT(cricket::kIceConnectionConnected, connection_state_, kTimeout);
+}
+
+TEST_F(JsepTransportControllerTest,
        SignalConnectionStateFailedWhenMediaTransportClosed) {
   FakeMediaTransportFactory fake_media_transport_factory;
   JsepTransportController::Config config;
   config.media_transport_factory = &fake_media_transport_factory;
+  config.use_media_transport_for_media = true;
   CreateJsepTransportController(config);
   auto description = CreateSessionDescriptionWithoutBundle();
   AddCryptoSettings(description.get());
@@ -876,6 +971,11 @@
                   ->SetLocalDescription(SdpType::kOffer, description.get())
                   .ok());
 
+  auto fake_audio_dtls = static_cast<FakeDtlsTransport*>(
+      transport_controller_->GetDtlsTransport(kAudioMid1));
+  auto fake_video_dtls = static_cast<FakeDtlsTransport*>(
+      transport_controller_->GetDtlsTransport(kVideoMid1));
+
   auto fake_audio_ice = static_cast<cricket::FakeIceTransport*>(
       transport_controller_->GetDtlsTransport(kAudioMid1)->ice_transport());
   auto fake_video_ice = static_cast<cricket::FakeIceTransport*>(
@@ -888,6 +988,9 @@
   fake_video_ice->SetConnectionCount(2);
   fake_video_ice->SetConnectionCount(1);
 
+  fake_audio_dtls->SetWritable(true);
+  fake_video_dtls->SetWritable(true);
+
   FakeMediaTransport* media_transport = static_cast<FakeMediaTransport*>(
       transport_controller_->GetMediaTransport(kAudioMid1));
 
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 4cdab68..2b4a48d 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -989,6 +989,9 @@
       return false;
     }
 
+    config.use_media_transport_for_media = configuration.use_media_transport;
+    config.use_media_transport_for_data_channels =
+        configuration.use_media_transport_for_data_channels;
     config.media_transport_factory = factory_->media_transport_factory();
   }
 
@@ -3207,11 +3210,9 @@
   }
 
   transport_controller_->SetIceConfig(ParseIceConfig(modified_config));
-  transport_controller_->SetMediaTransportFactory(
-      modified_config.use_media_transport ||
-              modified_config.use_media_transport_for_data_channels
-          ? factory_->media_transport_factory()
-          : nullptr);
+  transport_controller_->SetMediaTransportSettings(
+      modified_config.use_media_transport,
+      modified_config.use_media_transport_for_data_channels);
 
   if (configuration_.active_reset_srtp_params !=
       modified_config.active_reset_srtp_params) {
@@ -6748,7 +6749,11 @@
     sctp_transport_->SetDtlsTransport(dtls_transport);
   }
 
-  call_->MediaTransportChange(media_transport);
+  if (configuration_.use_media_transport) {
+    // Only pass media transport to call object if media transport is used
+    // for media (and not data channel).
+    call_->MediaTransportChange(media_transport);
+  }
 
   return ret;
 }
diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc
index dd7f1d8..4091006 100644
--- a/pc/peer_connection_integrationtest.cc
+++ b/pc/peer_connection_integrationtest.cc
@@ -3638,6 +3638,28 @@
   EXPECT_GE(first_stats.sent_video_frames, second_stats.received_video_frames);
 }
 
+TEST_P(PeerConnectionIntegrationTest,
+       MediaTransportDataChannelUsesRtpBidirectionalVideo) {
+  PeerConnectionInterface::RTCConfiguration rtc_config;
+  rtc_config.use_media_transport = false;
+  rtc_config.use_media_transport_for_data_channels = true;
+  rtc_config.enable_dtls_srtp = false;  // SDES is required for media transport.
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndMediaTransportFactory(
+      rtc_config, rtc_config, loopback_media_transports()->first_factory(),
+      loopback_media_transports()->second_factory()));
+  ConnectFakeSignaling();
+
+  caller()->AddVideoTrack();
+  callee()->AddVideoTrack();
+  // Start offer/answer exchange and wait for it to complete.
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+
+  MediaExpectations media_expectations;
+  media_expectations.ExpectBidirectionalVideo();
+  ASSERT_TRUE(ExpectNewFrames(media_expectations));
+}
+
 // Test that the ICE connection and gathering states eventually reach
 // "complete".
 TEST_P(PeerConnectionIntegrationTest, IceStatesReachCompletion) {