/* * 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. */ // This file contains tests that verify the correct working of switching // to a different callee between PR-Answer and Answer. #include #include #include #include #include #include "api/data_channel_interface.h" #include "api/jsep.h" #include "api/peer_connection_interface.h" #include "api/rtc_error.h" #include "api/test/rtc_error_matchers.h" #include "p2p/test/test_turn_server.h" #include "pc/test/fake_rtc_certificate_generator.h" #include "pc/test/integration_test_helpers.h" #include "rtc_base/socket_address.h" #include "rtc_base/task_queue_for_test.h" #include "test/gmock.h" #include "test/gtest.h" #include "test/wait_until.h" namespace webrtc { using ::testing::Eq; using ::testing::Field; using ::testing::Gt; using ::testing::HasSubstr; using ::testing::IsTrue; using ::testing::Ne; using ::testing::Not; class PeerConnectionPrAnswerSwitchTest : public PeerConnectionIntegrationBaseTest { public: PeerConnectionPrAnswerSwitchTest() : PeerConnectionIntegrationBaseTest(SdpSemantics::kUnifiedPlan) {} std::unique_ptr SetupCallee2( bool addTurn, bool create_media_engine) { RTCConfiguration config; if (addTurn) { static const SocketAddress turn_server_1_internal_address{"192.0.2.1", 3478}; static const SocketAddress turn_server_1_external_address{"192.0.3.1", 0}; TestTurnServer* turn_server_1 = CreateTurnServer( turn_server_1_internal_address, turn_server_1_external_address); // Bypass permission check on received packets so media can be sent before // the candidate is signaled. SendTask(network_thread(), [turn_server_1] { turn_server_1->set_enable_permission_checks(false); }); PeerConnectionInterface::IceServer ice_server_1; ice_server_1.urls.push_back("turn:192.0.2.1:3478"); ice_server_1.username = "test"; ice_server_1.password = "test"; config.servers.push_back(ice_server_1); config.type = PeerConnectionInterface::kRelay; config.presume_writable_when_fully_relayed = true; } CreatePeerConnectionWrappersWithConfig(config, config, create_media_engine); PeerConnectionDependencies dependencies(nullptr); // Ensure that the key of callee2 is different from the key of // callee1, so that reconnection will trigger an ICE restart. std::unique_ptr cert_generator( new FakeRTCCertificateGenerator()); cert_generator->use_alternate_key(); dependencies.cert_generator = std::move(cert_generator); auto callee2 = CreatePeerConnectionWrapper( "Callee2", nullptr, &config, std::move(dependencies), nullptr, /*reset_encoder_factory=*/false, /*reset_decoder_factory=*/false, create_media_engine); ConnectFakeSignaling(); callee2->set_signaling_message_receiver(caller()); return callee2; } #ifdef WEBRTC_HAVE_SCTP std::unique_ptr SetupCallee2AndDc( bool addTurn) { std::unique_ptr callee2 = SetupCallee2(addTurn, /* create_media_engine= */ false); DataChannelInit dc_init; dc_init.negotiated = true; dc_init.id = 77; caller()->CreateDataChannel("label", &dc_init); callee()->CreateDataChannel("label", &dc_init); callee2->CreateDataChannel("label", &dc_init); return callee2; } #endif // WEBRTC_HAVE_SCTP void WaitConnected(bool prAnswer, PeerConnectionIntegrationWrapper* caller, PeerConnectionIntegrationWrapper* callee) { if (prAnswer) { EXPECT_EQ(caller->pc()->signaling_state(), PeerConnectionInterface::kHaveRemotePrAnswer); EXPECT_EQ(callee->pc()->signaling_state(), PeerConnectionInterface::kHaveLocalPrAnswer); } else { EXPECT_EQ(caller->pc()->signaling_state(), PeerConnectionInterface::kStable); EXPECT_EQ(callee->pc()->signaling_state(), PeerConnectionInterface::kStable); } ASSERT_THAT( WaitUntil([&] { return caller->pc()->peer_connection_state(); }, Eq(PeerConnectionInterface::PeerConnectionState::kConnected)), IsRtcOk()); ASSERT_THAT( WaitUntil([&] { return callee->pc()->peer_connection_state(); }, Eq(PeerConnectionInterface::PeerConnectionState::kConnected)), IsRtcOk()); } #ifdef WEBRTC_HAVE_SCTP void WaitConnectedAndDcOpen(bool prAnswer, PeerConnectionIntegrationWrapper* caller, PeerConnectionIntegrationWrapper* callee) { WaitConnected(prAnswer, caller, callee); ASSERT_THAT(WaitUntil([&] { return caller->data_channel()->state(); }, Eq(DataChannelInterface::kOpen)), IsRtcOk()); ASSERT_THAT(WaitUntil([&] { return callee->data_channel()->state(); }, Eq(DataChannelInterface::kOpen)), IsRtcOk()); } static void SendOnDatachannelWhenConnectedCallback( PeerConnectionIntegrationWrapper* peer, const std::string& data, std::atomic& signal) { if (peer->pc()->peer_connection_state() == PeerConnectionInterface::PeerConnectionState::kConnected && peer->data_channel()->state() == DataChannelInterface::kOpen) { peer->data_channel()->SendAsync(DataBuffer(data), [&](RTCError err) { signal.store(err.ok() ? 1 : -1); }); } } void VerifyReceivedDcMessages(PeerConnectionIntegrationWrapper* peer, const std::string& data, std::atomic& signal) { ASSERT_THAT(WaitUntil([&] { return signal.load(); }, Ne(0)), IsRtcOk()); EXPECT_THAT(WaitUntil([&] { return peer->data_observer()->last_message(); }, Eq(data)), IsRtcOk()); } #endif // WEBRTC_HAVE_SCTP }; #ifdef WEBRTC_HAVE_SCTP TEST_F(PeerConnectionPrAnswerSwitchTest, DtlsRestartOneCalleeAtATime) { // Keep these variables here to make sure they have a wider scope than the PC // objects that may have unfinished async operations (see // `SendOnDatachannelWhenConnectedCallback`) that depend on them when they // (the PC objects), go out of scope. std::atomic caller_sent_on_dc(0); std::atomic callee2_sent_on_dc(0); auto callee2 = SetupCallee2AndDc(/* addTurn= */ false); std::unique_ptr offer; callee()->SetReceivedSdpMunger( [&](std::unique_ptr& sdp) { // Capture offer so that it can be sent to Callee2 too. offer = sdp->Clone(); }); callee()->SetGeneratedSdpMunger( [&](std::unique_ptr& sdp) { // Modify offer to kPrAnswer SetSdpType(sdp, SdpType::kPrAnswer); }); caller()->CreateAndSetAndSignalOffer(); ASSERT_FALSE(HasFailure()); WaitConnectedAndDcOpen(/* prAnswer= */ true, caller(), callee()); ASSERT_FALSE(HasFailure()); caller()->set_connection_change_callback([&](auto new_state) { SendOnDatachannelWhenConnectedCallback(caller(), "KESO", caller_sent_on_dc); }); // Install same cb on both connection_change_callback and // data_observer->set_state_change_callback since they can fire in any order. callee2->set_connection_change_callback([&](auto new_state) { SendOnDatachannelWhenConnectedCallback(callee2.get(), "KENT", callee2_sent_on_dc); }); callee2->data_observer()->set_state_change_callback([&](auto new_state) { SendOnDatachannelWhenConnectedCallback(callee2.get(), "KENT", callee2_sent_on_dc); }); // Now let callee2 get the offer, apply it and send the answer to caller. std::string offer_sdp; EXPECT_TRUE(offer->ToString(&offer_sdp)); callee2->ReceiveSdpMessage(SdpType::kOffer, offer_sdp); WaitConnectedAndDcOpen(/* prAnswer= */ false, caller(), callee2.get()); ASSERT_FALSE(HasFailure()); VerifyReceivedDcMessages(caller(), "KENT", callee2_sent_on_dc); VerifyReceivedDcMessages(callee2.get(), "KESO", caller_sent_on_dc); ASSERT_FALSE(HasFailure()); } #endif // WEBRTC_HAVE_SCTP TEST_F(PeerConnectionPrAnswerSwitchTest, SendMediaNoDataChannel) { std::unique_ptr second_callee = SetupCallee2(/* addTurn= */ false, /* create_media_engine= */ true); std::string saved_offer; caller()->AddAudioVideoTracks(); caller()->SetGeneratedSdpMunger( [&](std::unique_ptr& sdp) { sdp->ToString(&saved_offer); }); callee()->SetGeneratedSdpMunger( [](std::unique_ptr& sdp) { SetSdpType(sdp, SdpType::kPrAnswer); }); caller()->CreateAndSetAndSignalOffer(); ASSERT_THAT(WaitUntil( [&] { return caller()->pc()->signaling_state() == PeerConnectionInterface::kHaveRemotePrAnswer; }, IsTrue()), IsRtcOk()); WaitConnected(/* prAnswer= */ true, caller(), callee()); MediaExpectations media_expectations; media_expectations.CalleeExpectsSomeAudio(); media_expectations.CalleeExpectsSomeVideo(); ASSERT_TRUE(ExpectNewFrames(media_expectations)); // Send original offer to second callee and wait for settlement. second_callee->ReceiveSdpMessage(SdpType::kOffer, saved_offer); EXPECT_THAT( WaitUntil([&] { return caller()->SignalingStateStable(); }, IsTrue()), IsRtcOk()); WaitConnected(/* prAnswer= */ false, caller(), second_callee.get()); ASSERT_FALSE(HasFailure()); } // This test completes, but is disabled because feedback type switching // does not work yet. // TODO: issues.webrtc.org/448848876 - enable when underlying issue fixed. TEST_F(PeerConnectionPrAnswerSwitchTest, DISABLED_MediaWithCcfbFirstThenTwcc) { SetFieldTrials("WebRTC-RFC8888CongestionControlFeedback/Enabled,offer:true/"); SetFieldTrials("Callee2", "WebRTC-RFC8888CongestionControlFeedback/Disabled/"); std::unique_ptr second_callee = SetupCallee2(/* addTurn= */ false, /* create_media_engine= */ true); std::string saved_offer; caller()->AddAudioVideoTracks(); caller()->SetGeneratedSdpMunger( [&](std::unique_ptr& sdp) { sdp->ToString(&saved_offer); }); callee()->SetGeneratedSdpMunger( [](std::unique_ptr& sdp) { SetSdpType(sdp, SdpType::kPrAnswer); }); caller()->CreateAndSetAndSignalOffer(); ASSERT_THAT(WaitUntil( [&] { return caller()->pc()->signaling_state() == PeerConnectionInterface::kHaveRemotePrAnswer; }, IsTrue()), IsRtcOk()); WaitConnected(/* prAnswer= */ true, caller(), callee()); MediaExpectations media_expectations; media_expectations.CalleeExpectsSomeAudio(); media_expectations.CalleeExpectsSomeVideo(); ASSERT_TRUE(ExpectNewFrames(media_expectations)); auto pc_internal = caller()->pc_internal(); EXPECT_THAT( WaitUntil( [&] { return pc_internal->FeedbackAccordingToRfc8888CountForTesting(); }, Gt(0)), IsRtcOk()); // There should be no transport-cc generated. EXPECT_THAT(pc_internal->FeedbackAccordingToTransportCcCountForTesting(), Eq(0)); // The final answer does TWCC. second_callee->ReceiveSdpMessage(SdpType::kOffer, saved_offer); EXPECT_THAT( WaitUntil([&] { return caller()->SignalingStateStable(); }, IsTrue()), IsRtcOk()); WaitConnected(/* prAnswer= */ false, caller(), second_callee.get()); ASSERT_FALSE(HasFailure()); int old_ccfb_count = pc_internal->FeedbackAccordingToRfc8888CountForTesting(); int old_twcc_count = pc_internal->FeedbackAccordingToTransportCcCountForTesting(); EXPECT_THAT( WaitUntil( [&] { return pc_internal->FeedbackAccordingToTransportCcCountForTesting(); }, Gt(old_twcc_count)), IsRtcOk()); // These expects are easier to interpret than the WaitUntil log result. EXPECT_THAT(pc_internal->FeedbackAccordingToTransportCcCountForTesting(), Gt(old_twcc_count)); EXPECT_THAT(pc_internal->FeedbackAccordingToRfc8888CountForTesting(), Eq(old_ccfb_count)); } } // namespace webrtc