Allow receive-only use of datagram transport for data channels.

Adds a field trial and configuration parameter to control whether
datagram transport may be used for data channels in a receive-only
manner.  By default, if use_datagram_transport_for_data_channels is
enabled, PeerConnection will create a datagram transport and offer its
use for outgoing calls as well as accept incoming offers with compatible
datagram transport parameters.

With this change, a receive_only mode is added for datagram transport
data channels.  When receive_only is set, the PeerConnection will not
create or offer datagram transports for outgoing calls, but will accept
incoming calls that offer compatible datagram transport parameters.

Bug: webrtc:9719
Change-Id: I35667bcc408ea4bbc61155898e6d2472dd262711
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/154463
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Commit-Queue: Bjorn Mellem <mellem@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29327}
diff --git a/api/peer_connection_interface.h b/api/peer_connection_interface.h
index f2cc696..a417641 100644
--- a/api/peer_connection_interface.h
+++ b/api/peer_connection_interface.h
@@ -632,6 +632,14 @@
     // of SCTP-DTLS.
     absl::optional<bool> use_datagram_transport_for_data_channels;
 
+    // If true, this PeerConnection will only use datagram transport for data
+    // channels when receiving an incoming offer that includes datagram
+    // transport parameters.  It will not request use of a datagram transport
+    // when it creates the initial, outgoing offer.
+    // This setting only applies when |use_datagram_transport_for_data_channels|
+    // is true.
+    absl::optional<bool> use_datagram_transport_for_data_channels_receive_only;
+
     // Defines advanced optional cryptographic settings related to SRTP and
     // frame encryption for native WebRTC. Setting this will overwrite any
     // settings set in PeerConnectionFactory (which is deprecated).
diff --git a/pc/jsep_transport_controller.cc b/pc/jsep_transport_controller.cc
index 75f5d9d..c9ff0a7 100644
--- a/pc/jsep_transport_controller.cc
+++ b/pc/jsep_transport_controller.cc
@@ -447,7 +447,8 @@
     bool use_media_transport_for_media,
     bool use_media_transport_for_data_channels,
     bool use_datagram_transport,
-    bool use_datagram_transport_for_data_channels) {
+    bool use_datagram_transport_for_data_channels,
+    bool use_datagram_transport_for_data_channels_receive_only) {
   RTC_DCHECK(use_media_transport_for_media ==
                  config_.use_media_transport_for_media ||
              jsep_transports_by_name_.empty())
@@ -466,6 +467,8 @@
   config_.use_datagram_transport = use_datagram_transport;
   config_.use_datagram_transport_for_data_channels =
       use_datagram_transport_for_data_channels;
+  config_.use_datagram_transport_for_data_channels_receive_only =
+      use_datagram_transport_for_data_channels_receive_only;
 }
 
 std::unique_ptr<cricket::IceTransportInternal>
@@ -1795,6 +1798,10 @@
   RTC_DCHECK(!local_desc_ && !remote_desc_)
       << "JsepTransport should exist for every mid once any description is set";
 
+  if (config_.use_datagram_transport_for_data_channels_receive_only) {
+    return absl::nullopt;
+  }
+
   // Need to generate a transport for the offer.
   if (!offer_datagram_transport_) {
     webrtc::MediaTransportSettings settings;
diff --git a/pc/jsep_transport_controller.h b/pc/jsep_transport_controller.h
index 12bcebc..af3c82c 100644
--- a/pc/jsep_transport_controller.h
+++ b/pc/jsep_transport_controller.h
@@ -120,6 +120,11 @@
     // Use datagram transport's implementation of data channels instead of SCTP.
     bool use_datagram_transport_for_data_channels = false;
 
+    // Whether |use_datagram_transport_for_data_channels| applies to outgoing
+    // calls.  If true, |use_datagram_transport_for_data_channels| applies only
+    // to incoming calls.
+    bool use_datagram_transport_for_data_channels_receive_only = false;
+
     // Optional media transport factory (experimental). If provided it will be
     // used to create media_transport (as long as either
     // |use_media_transport_for_media| or
@@ -227,10 +232,12 @@
   // 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,
-                                 bool use_datagram_transport,
-                                 bool use_datagram_transport_for_data_channels);
+  void SetMediaTransportSettings(
+      bool use_media_transport_for_media,
+      bool use_media_transport_for_data_channels,
+      bool use_datagram_transport,
+      bool use_datagram_transport_for_data_channels,
+      bool use_datagram_transport_for_data_channels_receive_only);
 
   // If media transport is present enabled and supported,
   // when this method is called, it creates a media transport and generates its
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 4149081..2679800 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -778,6 +778,7 @@
     bool use_media_transport_for_data_channels;
     absl::optional<bool> use_datagram_transport;
     absl::optional<bool> use_datagram_transport_for_data_channels;
+    absl::optional<bool> use_datagram_transport_for_data_channels_receive_only;
     absl::optional<CryptoOptions> crypto_options;
     bool offer_extmap_allow_mixed;
     std::string turn_logging_id;
@@ -842,6 +843,8 @@
          use_datagram_transport == o.use_datagram_transport &&
          use_datagram_transport_for_data_channels ==
              o.use_datagram_transport_for_data_channels &&
+         use_datagram_transport_for_data_channels_receive_only ==
+             o.use_datagram_transport_for_data_channels_receive_only &&
          crypto_options == o.crypto_options &&
          offer_extmap_allow_mixed == o.offer_extmap_allow_mixed &&
          turn_logging_id == o.turn_logging_id;
@@ -1080,6 +1083,9 @@
       datagram_transport_data_channel_config_.enabled &&
       configuration.use_datagram_transport_for_data_channels.value_or(
           datagram_transport_data_channel_config_.default_value);
+  use_datagram_transport_for_data_channels_receive_only_ =
+      configuration.use_datagram_transport_for_data_channels_receive_only
+          .value_or(datagram_transport_data_channel_config_.receive_only);
   if (use_datagram_transport_ || use_datagram_transport_for_data_channels_ ||
       configuration.use_media_transport ||
       configuration.use_media_transport_for_data_channels) {
@@ -1114,6 +1120,8 @@
     config.use_datagram_transport = use_datagram_transport_;
     config.use_datagram_transport_for_data_channels =
         use_datagram_transport_for_data_channels_;
+    config.use_datagram_transport_for_data_channels_receive_only =
+        use_datagram_transport_for_data_channels_receive_only_;
     config.media_transport_factory = factory_->media_transport_factory();
   }
 
@@ -3573,6 +3581,26 @@
         "after calling SetRemoteDescription.");
   }
 
+  if (local_description() &&
+      configuration.use_datagram_transport_for_data_channels_receive_only !=
+          configuration_
+              .use_datagram_transport_for_data_channels_receive_only) {
+    LOG_AND_RETURN_ERROR(
+        RTCErrorType::INVALID_MODIFICATION,
+        "Can't change use_datagram_transport_for_data_channels_receive_only "
+        "after calling SetLocalDescription.");
+  }
+
+  if (remote_description() &&
+      configuration.use_datagram_transport_for_data_channels_receive_only !=
+          configuration_
+              .use_datagram_transport_for_data_channels_receive_only) {
+    LOG_AND_RETURN_ERROR(
+        RTCErrorType::INVALID_MODIFICATION,
+        "Can't change use_datagram_transport_for_data_channels_receive_only "
+        "after calling SetRemoteDescription.");
+  }
+
   if (configuration.use_media_transport_for_data_channels ||
       configuration.use_media_transport ||
       (configuration.use_datagram_transport &&
@@ -3616,6 +3644,8 @@
   modified_config.use_datagram_transport = configuration.use_datagram_transport;
   modified_config.use_datagram_transport_for_data_channels =
       configuration.use_datagram_transport_for_data_channels;
+  modified_config.use_datagram_transport_for_data_channels_receive_only =
+      configuration.use_datagram_transport_for_data_channels_receive_only;
   modified_config.turn_logging_id = configuration.turn_logging_id;
   if (configuration != modified_config) {
     LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION,
@@ -3688,10 +3718,14 @@
       datagram_transport_data_channel_config_.enabled &&
       modified_config.use_datagram_transport_for_data_channels.value_or(
           datagram_transport_data_channel_config_.default_value);
+  use_datagram_transport_for_data_channels_receive_only_ =
+      modified_config.use_datagram_transport_for_data_channels_receive_only
+          .value_or(datagram_transport_data_channel_config_.receive_only);
   transport_controller_->SetMediaTransportSettings(
       modified_config.use_media_transport,
       modified_config.use_media_transport_for_data_channels,
-      use_datagram_transport_, use_datagram_transport_for_data_channels_);
+      use_datagram_transport_, use_datagram_transport_for_data_channels_,
+      use_datagram_transport_for_data_channels_receive_only_);
 
   if (configuration_.active_reset_srtp_params !=
       modified_config.active_reset_srtp_params) {
diff --git a/pc/peer_connection.h b/pc/peer_connection.h
index 393beed..c783ae9 100644
--- a/pc/peer_connection.h
+++ b/pc/peer_connection.h
@@ -365,8 +365,10 @@
   // Field-trial based configuration for datagram transport data channels.
   struct DatagramTransportDataChannelConfig {
     explicit DatagramTransportDataChannelConfig(const std::string& field_trial)
-        : enabled("enabled", true), default_value("default_value", false) {
-      ParseFieldTrial({&enabled, &default_value}, field_trial);
+        : enabled("enabled", true),
+          default_value("default_value", false),
+          receive_only("receive_only", false) {
+      ParseFieldTrial({&enabled, &default_value, &receive_only}, field_trial);
     }
 
     // Whether datagram transport data channel support is enabled at all.
@@ -382,6 +384,11 @@
     // applications will use the datagram transport by default (but may still
     // explicitly configure themselves not to use it through RTCConfiguration).
     FieldTrialFlag default_value;
+
+    // Whether the datagram transport is enabled in receive-only mode.  If true,
+    // and if the datagram transport is enabled, it will only be used when
+    // receiving incoming calls, not when placing outgoing calls.
+    FieldTrialFlag receive_only;
   };
 
   // Implements MessageHandler.
@@ -1196,7 +1203,8 @@
   const DatagramTransportConfig datagram_transport_config_;
 
   // Field-trial based configuration for datagram transport data channels.
-  const DatagramTransportConfig datagram_transport_data_channel_config_;
+  const DatagramTransportDataChannelConfig
+      datagram_transport_data_channel_config_;
 
   // Final, resolved value for whether datagram transport is in use.
   bool use_datagram_transport_ RTC_GUARDED_BY(signaling_thread()) = false;
@@ -1206,6 +1214,10 @@
   bool use_datagram_transport_for_data_channels_
       RTC_GUARDED_BY(signaling_thread()) = false;
 
+  // Resolved value of whether to use data channels only for incoming calls.
+  bool use_datagram_transport_for_data_channels_receive_only_
+      RTC_GUARDED_BY(signaling_thread()) = false;
+
   // Cache configuration_.use_media_transport so that we can access it from
   // other threads.
   // TODO(bugs.webrtc.org/9987): Caching just this bool and allowing the data
diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc
index 465dca1..6b2d830 100644
--- a/pc/peer_connection_integrationtest.cc
+++ b/pc/peer_connection_integrationtest.cc
@@ -3647,8 +3647,101 @@
   ASSERT_TRUE(ExpectNewFrames(media_expectations));
 }
 
+// Tests that data channels use SCTP instead of datagram transport if datagram
+// transport is configured in receive-only mode on the caller.
+TEST_P(PeerConnectionIntegrationTest,
+       DatagramTransportDataChannelReceiveOnlyOnCallerUsesSctp) {
+  PeerConnectionInterface::RTCConfiguration rtc_config;
+  rtc_config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire;
+  rtc_config.bundle_policy = PeerConnectionInterface::kBundlePolicyMaxBundle;
+  rtc_config.use_datagram_transport_for_data_channels = true;
+  rtc_config.use_datagram_transport_for_data_channels_receive_only = true;
+
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndMediaTransportFactory(
+      rtc_config, rtc_config, loopback_media_transports()->first_factory(),
+      loopback_media_transports()->second_factory()));
+  ConnectFakeSignaling();
+
+  // The caller should offer a data channel using SCTP.
+  caller()->CreateDataChannel();
+  caller()->AddAudioVideoTracks();
+  callee()->AddAudioVideoTracks();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+
+  ASSERT_NE(nullptr, caller()->data_channel());
+  ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout);
+  EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout);
+  EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout);
+
+  // SCTP transports should be present, since they are in use.
+  EXPECT_NE(caller()->pc()->GetSctpTransport(), nullptr);
+  EXPECT_NE(callee()->pc()->GetSctpTransport(), nullptr);
+
+  // Ensure data can be sent in both directions.
+  std::string data = "hello world";
+  caller()->data_channel()->Send(DataBuffer(data));
+  EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(),
+                 kDefaultTimeout);
+  callee()->data_channel()->Send(DataBuffer(data));
+  EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(),
+                 kDefaultTimeout);
+}
+
 #endif  // HAVE_SCTP
 
+// Tests that a callee configured for receive-only use of datagram transport
+// data channels accepts them on incoming calls.
+TEST_P(PeerConnectionIntegrationTest,
+       DatagramTransportDataChannelReceiveOnlyOnCallee) {
+  PeerConnectionInterface::RTCConfiguration offerer_config;
+  offerer_config.rtcp_mux_policy =
+      PeerConnectionInterface::kRtcpMuxPolicyRequire;
+  offerer_config.bundle_policy =
+      PeerConnectionInterface::kBundlePolicyMaxBundle;
+  offerer_config.use_datagram_transport_for_data_channels = true;
+
+  PeerConnectionInterface::RTCConfiguration answerer_config;
+  answerer_config.rtcp_mux_policy =
+      PeerConnectionInterface::kRtcpMuxPolicyRequire;
+  answerer_config.bundle_policy =
+      PeerConnectionInterface::kBundlePolicyMaxBundle;
+  answerer_config.use_datagram_transport_for_data_channels = true;
+  answerer_config.use_datagram_transport_for_data_channels_receive_only = true;
+
+  ASSERT_TRUE(CreatePeerConnectionWrappersWithConfigAndMediaTransportFactory(
+      offerer_config, answerer_config,
+      loopback_media_transports()->first_factory(),
+      loopback_media_transports()->second_factory()));
+  ConnectFakeSignaling();
+
+  caller()->CreateDataChannel();
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_TRUE_WAIT(SignalingStateStable(), kDefaultTimeout);
+
+  // Ensure that the data channel transport is ready.
+  loopback_media_transports()->SetState(webrtc::MediaTransportState::kWritable);
+  loopback_media_transports()->FlushAsyncInvokes();
+
+  ASSERT_NE(nullptr, caller()->data_channel());
+  ASSERT_TRUE_WAIT(callee()->data_channel() != nullptr, kDefaultTimeout);
+  EXPECT_TRUE_WAIT(caller()->data_observer()->IsOpen(), kDefaultTimeout);
+  EXPECT_TRUE_WAIT(callee()->data_observer()->IsOpen(), kDefaultTimeout);
+
+  // SCTP transports should not be present, since datagram transport is used.
+  EXPECT_EQ(caller()->pc()->GetSctpTransport(), nullptr);
+  EXPECT_EQ(callee()->pc()->GetSctpTransport(), nullptr);
+
+  // Ensure data can be sent in both directions.
+  std::string data = "hello world";
+  caller()->data_channel()->Send(DataBuffer(data));
+  EXPECT_EQ_WAIT(data, callee()->data_observer()->last_message(),
+                 kDefaultTimeout);
+  callee()->data_channel()->Send(DataBuffer(data));
+  EXPECT_EQ_WAIT(data, caller()->data_observer()->last_message(),
+                 kDefaultTimeout);
+}
+
 // This test sets up a call between two parties with a datagram transport data
 // channel.
 TEST_P(PeerConnectionIntegrationTest, DatagramTransportDataChannelEndToEnd) {