webrtc/pc/pranswer_switch_integrationtest.cc
Per K 1b01d58547 Reland "Ensure Answer after PrAnswer can change usage from CCFB to TWCC feedback"
This ensure transport wide sequence numbers can be used for congestion control even if initial answer suggested CCFB.

First patch contain original cl, reverted in https://webrtc-review.googlesource.com/c/src/+/423140
Second patch contain fix.

Bug: webrtc:448848876
Change-Id: Iafdb9176bec63ae970073517ad5d5b266768969d
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/423240
Commit-Queue: Per Kjellander <perkj@webrtc.org>
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#46138}
2025-11-05 05:45:35 -08:00

335 lines
13 KiB
C++

/*
* 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 <atomic>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#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/peer_connection.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<PeerConnectionIntegrationWrapper> 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<FakeRTCCertificateGenerator> 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<PeerConnectionIntegrationWrapper> SetupCallee2AndDc(
bool addTurn) {
std::unique_ptr<PeerConnectionIntegrationWrapper> 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<int>& 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<int>& 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<int> caller_sent_on_dc(0);
std::atomic<int> callee2_sent_on_dc(0);
auto callee2 = SetupCallee2AndDc(/* addTurn= */ false);
std::unique_ptr<SessionDescriptionInterface> offer;
callee()->SetReceivedSdpMunger(
[&](std::unique_ptr<SessionDescriptionInterface>& sdp) {
// Capture offer so that it can be sent to Callee2 too.
offer = sdp->Clone();
});
callee()->SetGeneratedSdpMunger(
[&](std::unique_ptr<SessionDescriptionInterface>& 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<PeerConnectionIntegrationWrapper> second_callee =
SetupCallee2(/* addTurn= */ false, /* create_media_engine= */ true);
std::string saved_offer;
caller()->AddAudioVideoTracks();
caller()->SetGeneratedSdpMunger(
[&](std::unique_ptr<SessionDescriptionInterface>& sdp) {
sdp->ToString(&saved_offer);
});
callee()->SetGeneratedSdpMunger(
[](std::unique_ptr<SessionDescriptionInterface>& 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());
}
TEST_F(PeerConnectionPrAnswerSwitchTest, MediaWithCcfbFirstThenTwcc) {
SetFieldTrials("WebRTC-RFC8888CongestionControlFeedback/Enabled,offer:true/");
SetFieldTrials("Callee2",
"WebRTC-RFC8888CongestionControlFeedback/Disabled/");
std::unique_ptr<PeerConnectionIntegrationWrapper> second_callee =
SetupCallee2(/* addTurn= */ false, /* create_media_engine= */ true);
std::string saved_offer;
caller()->AddAudioVideoTracks();
caller()->SetGeneratedSdpMunger(
[&](std::unique_ptr<SessionDescriptionInterface>& sdp) {
sdp->ToString(&saved_offer);
});
callee()->SetGeneratedSdpMunger(
[](std::unique_ptr<SessionDescriptionInterface>& 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));
PeerConnection* caller_pc_internal = caller()->pc_internal();
EXPECT_THAT(WaitUntil(
[&] {
return caller_pc_internal
->FeedbackAccordingToRfc8888CountForTesting();
},
Gt(0)),
IsRtcOk());
// There should be no transport-cc generated.
EXPECT_THAT(
caller_pc_internal->FeedbackAccordingToTransportCcCountForTesting(),
Eq(0));
// The final answer does TWCC and send audio and video.
second_callee->AddAudioVideoTracks();
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 =
caller_pc_internal->FeedbackAccordingToRfc8888CountForTesting();
int old_twcc_count =
caller_pc_internal->FeedbackAccordingToTransportCcCountForTesting();
EXPECT_THAT(WaitUntil(
[&] {
return caller_pc_internal
->FeedbackAccordingToTransportCcCountForTesting();
},
Gt(old_twcc_count)),
IsRtcOk());
// These expects are easier to interpret than the WaitUntil log result.
EXPECT_THAT(
caller_pc_internal->FeedbackAccordingToTransportCcCountForTesting(),
Gt(old_twcc_count));
EXPECT_THAT(caller_pc_internal->FeedbackAccordingToRfc8888CountForTesting(),
Eq(old_ccfb_count));
PeerConnection* second_callee_pc_internal = second_callee->pc_internal();
EXPECT_THAT(WaitUntil(
[&] {
return second_callee_pc_internal
->FeedbackAccordingToTransportCcCountForTesting();
},
Gt(0)),
IsRtcOk());
}
} // namespace webrtc