| /* | |
| * 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 <memory> | |
| #include <string> | |
| #include <tuple> | |
| #include <vector> | |
| #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</*field_trials=*/std::string, | |
| /*signal_candidates_from_client=*/bool, | |
| /*dtls_role=*/ConnectionRole>> { | |
| public: | |
| PeerConnectionDataChannelOpenTest() | |
| : env_(CreateTestEnvironment()), | |
| 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_mean=*/50); | |
| vss_.UpdateDelayDistribution(); | |
| } | |
| scoped_refptr<PeerConnectionTestWrapper> CreatePc( | |
| absl::string_view field_trials = "") { | |
| auto pc_wrapper = make_ref_counted<PeerConnectionTestWrapper>( | |
| "pc", env_, &vss_, background_thread_.get(), background_thread_.get()); | |
| pc_wrapper->CreatePc({}, CreateBuiltinAudioEncoderFactory(), | |
| CreateBuiltinAudioDecoderFactory(), | |
| CreateTestFieldTrialsPtr(field_trials)); | |
| return pc_wrapper; | |
| } | |
| void SignalIceCandidates( | |
| scoped_refptr<PeerConnectionTestWrapper> from_pc_wrapper, | |
| scoped_refptr<PeerConnectionTestWrapper> 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<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(), | |
| sdp->Clone().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(), | |
| 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<scoped_refptr<MockSetSessionDescriptionObserver>> 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<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(trials); | |
| scoped_refptr<PeerConnectionTestWrapper> 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<double>(); | |
| 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 |