blob: c8c4ff51cc0b946f354d91594765c360972364c3 [file] [log] [blame] [edit]
/*
* 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