| /* |
| * 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 <cassert> |
| #include <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/strings/str_cat.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/field_trials.h" |
| #include "api/field_trials_view.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 "p2p/base/transport_description.h" |
| #include "pc/sdp_utils.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/time_utils.h" |
| #include "rtc_base/virtual_socket_server.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</*field_trials=*/std::string, |
| /*signal_candidates_from_client=*/bool, |
| /*dtls_role=*/ConnectionRole>> { |
| public: |
| PeerConnectionDataChannelOpenTest() |
| : background_thread_(std::make_unique<Thread>(&vss_)) { |
| RTC_CHECK(background_thread_->Start()); |
| // Delay is set to 50ms so we get a 100ms RTT. |
| vss_.set_delay_mean(/*delay_ms=*/50); |
| vss_.UpdateDelayDistribution(); |
| } |
| |
| scoped_refptr<PeerConnectionTestWrapper> CreatePc( |
| std::unique_ptr<FieldTrialsView> field_trials = nullptr) { |
| auto pc_wrapper = make_ref_counted<PeerConnectionTestWrapper>( |
| "pc", &vss_, background_thread_.get(), background_thread_.get()); |
| pc_wrapper->CreatePc({}, CreateBuiltinAudioEncoderFactory(), |
| CreateBuiltinAudioDecoderFactory(), |
| std::move(field_trials)); |
| return pc_wrapper; |
| } |
| |
| void SignalIceCandidates( |
| scoped_refptr<PeerConnectionTestWrapper> from_pc_wrapper, |
| scoped_refptr<PeerConnectionTestWrapper> to_pc_wrapper) { |
| from_pc_wrapper->SignalOnIceCandidateReady.connect( |
| to_pc_wrapper.get(), &PeerConnectionTestWrapper::AddIceCandidate); |
| } |
| |
| void Negotiate(scoped_refptr<PeerConnectionTestWrapper> local_pc_wrapper, |
| scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper, |
| ConnectionRole remote_role) { |
| std::unique_ptr<SessionDescriptionInterface> offer = |
| CreateOffer(local_pc_wrapper); |
| scoped_refptr<MockSetSessionDescriptionObserver> p1 = |
| SetLocalDescription(local_pc_wrapper, offer.get()); |
| std::unique_ptr<SessionDescriptionInterface> 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<MockSetSessionDescriptionObserver> p2 = |
| SetRemoteDescription(remote_pc_wrapper, modified_offer.get()); |
| EXPECT_TRUE(Await({p1, p2})); |
| std::unique_ptr<SessionDescriptionInterface> 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<webrtc::DataChannelInterface> dc) { |
| return WaitUntil( |
| [&] { |
| return dc->state() == DataChannelInterface::DataState::kOpen; |
| }, |
| IsTrue(), {.timeout = webrtc::TimeDelta::Millis(5000)}) |
| .ok(); |
| } |
| |
| protected: |
| std::unique_ptr<SessionDescriptionInterface> CreateOffer( |
| scoped_refptr<PeerConnectionTestWrapper> pc_wrapper) { |
| auto observer = make_ref_counted<MockCreateSessionDescriptionObserver>(); |
| pc_wrapper->pc()->CreateOffer(observer.get(), {}); |
| EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()), |
| IsRtcOk()); |
| return observer->MoveDescription(); |
| } |
| |
| std::unique_ptr<SessionDescriptionInterface> CreateAnswer( |
| scoped_refptr<PeerConnectionTestWrapper> pc_wrapper) { |
| auto observer = make_ref_counted<MockCreateSessionDescriptionObserver>(); |
| pc_wrapper->pc()->CreateAnswer(observer.get(), {}); |
| EXPECT_THAT(WaitUntil([&] { return observer->called(); }, IsTrue()), |
| IsRtcOk()); |
| return observer->MoveDescription(); |
| } |
| |
| scoped_refptr<MockSetSessionDescriptionObserver> SetLocalDescription( |
| scoped_refptr<PeerConnectionTestWrapper> pc_wrapper, |
| SessionDescriptionInterface* sdp) { |
| auto observer = make_ref_counted<MockSetSessionDescriptionObserver>(); |
| pc_wrapper->pc()->SetLocalDescription( |
| observer.get(), CloneSessionDescription(sdp).release()); |
| return observer; |
| } |
| |
| scoped_refptr<MockSetSessionDescriptionObserver> SetRemoteDescription( |
| scoped_refptr<PeerConnectionTestWrapper> pc_wrapper, |
| SessionDescriptionInterface* sdp) { |
| auto observer = make_ref_counted<MockSetSessionDescriptionObserver>(); |
| pc_wrapper->pc()->SetRemoteDescription( |
| observer.get(), CloneSessionDescription(sdp).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<scoped_refptr<MockSetSessionDescriptionObserver>> observers) { |
| for (auto& observer : observers) { |
| auto result = WaitUntil([&] { return observer->called(); }, IsTrue()); |
| |
| if (!result.ok() || !observer->result()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| VirtualSocketServer vss_; |
| std::unique_ptr<Thread> 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<PeerConnectionTestWrapper> local_pc_wrapper = |
| CreatePc(FieldTrials::CreateNoGlobal(trials)); |
| scoped_refptr<PeerConnectionTestWrapper> remote_pc_wrapper = |
| CreatePc(FieldTrials::CreateNoGlobal(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); |
| uint64_t start_time = TimeNanos(); |
| EXPECT_TRUE(WaitForDataChannelOpen(dc)); |
| uint64_t open_time = TimeNanos(); |
| uint64_t setup_time = open_time - start_time; |
| |
| double setup_time_millis = setup_time / kNumNanosecsPerMillisec; |
| 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/"), |
| 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 |