/* * Copyright 2025 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 #include #include #include #include #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "api/data_channel_interface.h" #include "api/environment/environment.h" #include "api/jsep.h" #include "api/make_ref_counted.h" #include "api/rtc_error.h" #include "api/scoped_refptr.h" #include "api/test/metrics/global_metrics_logger_and_exporter.h" #include "api/test/metrics/metric.h" #include "api/test/rtc_error_matchers.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "p2p/base/transport_description.h" #include "pc/session_description.h" #include "pc/test/mock_peer_connection_observers.h" #include "pc/test/peer_connection_test_wrapper.h" #include "rtc_base/checks.h" #include "rtc_base/thread.h" #include "rtc_base/virtual_socket_server.h" #include "test/create_test_environment.h" #include "test/create_test_field_trials.h" #include "test/gmock.h" #include "test/gtest.h" #include "test/wait_until.h" using ::testing::IsTrue; using ::testing::Values; using ::webrtc::test::GetGlobalMetricsLogger; using ::webrtc::test::ImprovementDirection; using ::webrtc::test::Unit; namespace webrtc { // All tests in this file require SCTP support. #ifdef WEBRTC_HAVE_SCTP class PeerConnectionDataChannelOpenTest : public ::testing::TestWithParam< std::tuple> { public: PeerConnectionDataChannelOpenTest() : env_(CreateTestEnvironment()), background_thread_(std::make_unique(&vss_)) { RTC_CHECK(background_thread_->Start()); // Delay is set to 50ms so we get a 100ms RTT. vss_.set_delay_mean(/*delay_mean=*/50); vss_.UpdateDelayDistribution(); } scoped_refptr CreatePc( absl::string_view field_trials = "") { auto pc_wrapper = make_ref_counted( "pc", env_, &vss_, background_thread_.get(), background_thread_.get()); pc_wrapper->CreatePc({}, CreateBuiltinAudioEncoderFactory(), CreateBuiltinAudioDecoderFactory(), CreateTestFieldTrialsPtr(field_trials)); return pc_wrapper; } void SignalIceCandidates( scoped_refptr from_pc_wrapper, scoped_refptr to_pc_wrapper) { from_pc_wrapper->SubscribeOnIceCandidateReady( this, [to_pc = to_pc_wrapper.get()](const std::string& arg1, int arg2, const std::string& arg3) { to_pc->AddIceCandidate(arg1, arg2, arg3); }); } void Negotiate(scoped_refptr local_pc_wrapper, scoped_refptr remote_pc_wrapper, ConnectionRole remote_role) { std::unique_ptr offer = CreateOffer(local_pc_wrapper); scoped_refptr p1 = SetLocalDescription(local_pc_wrapper, offer.get()); std::unique_ptr modified_offer = offer->Clone(); // Modify offer role to get desired remote role. if (remote_role == CONNECTIONROLE_PASSIVE) { auto& transport_infos = modified_offer->description()->transport_infos(); ASSERT_TRUE(!transport_infos.empty()); transport_infos[0].description.connection_role = CONNECTIONROLE_ACTIVE; } scoped_refptr p2 = SetRemoteDescription(remote_pc_wrapper, modified_offer.get()); EXPECT_TRUE(Await({p1, p2})); std::unique_ptr answer = CreateAnswer(remote_pc_wrapper); p1 = SetLocalDescription(remote_pc_wrapper, answer.get()); p2 = SetRemoteDescription(local_pc_wrapper, answer.get()); EXPECT_TRUE(Await({p1, p2})); } bool WaitForDataChannelOpen(scoped_refptr dc) { return WaitUntil( [&] { return dc->state() == DataChannelInterface::DataState::kOpen; }, IsTrue(), {.timeout = webrtc::TimeDelta::Millis(5000)}) .ok(); } protected: std::unique_ptr CreateOffer( scoped_refptr pc_wrapper) { auto observer = make_ref_counted(); pc_wrapper->pc()->CreateOffer(observer.get(), {}); EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()), IsRtcOk()); return observer->MoveDescription(); } std::unique_ptr CreateAnswer( scoped_refptr pc_wrapper) { auto observer = make_ref_counted(); pc_wrapper->pc()->CreateAnswer(observer.get(), {}); EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()), IsRtcOk()); return observer->MoveDescription(); } scoped_refptr SetLocalDescription( scoped_refptr pc_wrapper, SessionDescriptionInterface* sdp) { auto observer = make_ref_counted(); pc_wrapper->pc()->SetLocalDescription(observer.get(), sdp->Clone().release()); return observer; } scoped_refptr SetRemoteDescription( scoped_refptr pc_wrapper, SessionDescriptionInterface* sdp) { auto observer = make_ref_counted(); pc_wrapper->pc()->SetRemoteDescription(observer.get(), sdp->Clone().release()); return observer; } // To avoid ICE candidates arriving before the remote endpoint has received // the offer it is important to SetLocalDescription() and // SetRemoteDescription() are kicked off without awaiting in-between. This // helper is used to await multiple observers. bool Await( std::vector> observers) { for (auto& observer : observers) { auto result = WaitUntil([&] { return observer->called(); }, IsTrue()); if (!result.ok() || !observer->result()) { return false; } } return true; } const Environment env_; VirtualSocketServer vss_; std::unique_ptr background_thread_; }; TEST_P(PeerConnectionDataChannelOpenTest, OpenAtCaller) { std::string trials = std::get<0>(GetParam()); bool skip_candidates_from_caller = std::get<1>(GetParam()); ConnectionRole role = std::get<2>(GetParam()); std::string role_string; ASSERT_TRUE(ConnectionRoleToString(role, &role_string)); scoped_refptr local_pc_wrapper = CreatePc(trials); scoped_refptr remote_pc_wrapper = CreatePc(trials); if (!skip_candidates_from_caller) { SignalIceCandidates(local_pc_wrapper, remote_pc_wrapper); } SignalIceCandidates(remote_pc_wrapper, local_pc_wrapper); auto dc = local_pc_wrapper->CreateDataChannel("test", {}); Negotiate(local_pc_wrapper, remote_pc_wrapper, role); Timestamp start_time = env_.clock().CurrentTime(); EXPECT_TRUE(WaitForDataChannelOpen(dc)); Timestamp open_time = env_.clock().CurrentTime(); TimeDelta setup_time = open_time - start_time; double setup_time_millis = setup_time.ms(); std::string test_description = "emulate_server=" + absl::StrCat(skip_candidates_from_caller) + "/dtls_role=" + role_string + "/trials=" + trials; GetGlobalMetricsLogger()->LogSingleValueMetric( "TimeToOpenDataChannel", test_description, setup_time_millis, Unit::kMilliseconds, ImprovementDirection::kSmallerIsBetter); } INSTANTIATE_TEST_SUITE_P( PeerConnectionDataChannelOpenTest, PeerConnectionDataChannelOpenTest, ::testing::Combine( testing::Values( // Field trials to use. // WebRTC 1.0 + DTLS 1.2 "WebRTC-IceHandshakeDtls/Disabled/WebRTC-ForceDtls13/" "Disabled/", // SPED + DTLS 1.2 "WebRTC-IceHandshakeDtls/Enabled/WebRTC-ForceDtls13/" "Disabled/", // WebRTC 1.0 + DTLS 1.3 "WebRTC-IceHandshakeDtls/Disabled/WebRTC-ForceDtls13/" "Enabled/", // SPED + DTLS 1.3 "WebRTC-IceHandshakeDtls/Enabled/WebRTC-ForceDtls13/" "Enabled/", // SPED + DTLS 1.3 + SNAP "WebRTC-IceHandshakeDtls/Enabled/WebRTC-ForceDtls13/" "Enabled/WebRTC-Sctp-Snap/Enabled/"), testing::Bool(), // Whether to skip signaling candidates from // first connection. testing::Values( // Default, other side will send // the DTLS handshake. CONNECTIONROLE_ACTIVE, // Local side will send the DTLS // handshake. CONNECTIONROLE_PASSIVE))); #endif // WEBRTC_HAVE_SCTP } // namespace webrtc