/* * 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 "modules/congestion_controller/scream/scream_v2.h" #include #include "api/environment/environment.h" #include "api/transport/ecn_marking.h" #include "api/transport/network_types.h" #include "api/units/data_rate.h" #include "api/units/data_size.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "modules/congestion_controller/scream/test/cc_feedback_generator.h" #include "rtc_base/logging.h" #include "system_wrappers/include/clock.h" #include "test/create_test_environment.h" #include "test/gtest.h" #include "test/network/simulated_network.h" namespace webrtc { namespace { using ::testing::TestWithParam; constexpr DataSize kPacketSize = DataSize::Bytes(1000); TransportPacketsFeedback CreateFeedback(Timestamp feedback_time, TimeDelta smoothed_rtt, int number_of_ect1_packets, int number_of_packets_in_flight) { int sequence_number = 0; TransportPacketsFeedback feedback; feedback.feedback_time = feedback_time; feedback.smoothed_rtt = smoothed_rtt; Timestamp send_time = feedback_time - smoothed_rtt; feedback.data_in_flight = kPacketSize * number_of_packets_in_flight; for (int i = 0; i < number_of_ect1_packets; ++i) { PacketResult result; result.sent_packet.send_time = send_time; result.sent_packet.size = kPacketSize; result.ecn = EcnMarking::kEct1; result.receive_time = send_time; result.sent_packet.sequence_number = sequence_number++; feedback.packet_feedbacks.push_back(result); } return feedback; } TEST(ScreamV2Test, TargetRateIncreaseToMaxOnUnConstrainedNetwork) { SimulatedClock clock(Timestamp::Seconds(1'234)); Environment env = CreateTestEnvironment({.time = &clock}); ScreamV2 scream(env); const DataRate kMaxDataRate = DataRate::KilobitsPerSec(2000); scream.SetTargetBitrateConstraints(DataRate::Zero(), kMaxDataRate); DataRate send_rate = DataRate::KilobitsPerSec(100); // Configure a feedback generator simulating a network with infinite // capacity but 25ms one way delay. CcFeedbackGenerator feedback_generator( {.network_config = {.queue_delay_ms = 25}}); for (int i = 0; i < 100; ++i) { TransportPacketsFeedback feedback = feedback_generator.ProcessUntilNextFeedback(send_rate, clock); send_rate = scream.OnTransportPacketsFeedback(feedback); } EXPECT_EQ(send_rate, kMaxDataRate); } TEST(ScreamV2Test, ReferenceWindowDoesNotDecreaseAfterLowerSendRateOnUnconstrainedNetwork) { SimulatedClock clock(Timestamp::Seconds(1'234)); Environment env = CreateTestEnvironment({.time = &clock}); ScreamV2 scream(env); const DataRate kMaxDataRate = DataRate::KilobitsPerSec(2000); scream.SetTargetBitrateConstraints(DataRate::Zero(), kMaxDataRate); DataRate send_rate = DataRate::KilobitsPerSec(100); // Configure a feedback generator simulating a network with infinite // capacity but 25ms one way delay. CcFeedbackGenerator feedback_generator( {.network_config = {.queue_delay_ms = 25}}); for (int i = 0; i < 70; ++i) { TransportPacketsFeedback feedback = feedback_generator.ProcessUntilNextFeedback(send_rate, clock); send_rate = scream.OnTransportPacketsFeedback(feedback); } DataSize ref_window = scream.ref_window(); // Half the send rate, but the network is still unconstrained. send_rate = send_rate / 2; for (int i = 0; i < 20; ++i) { TransportPacketsFeedback feedback = feedback_generator.ProcessUntilNextFeedback(send_rate, clock); scream.OnTransportPacketsFeedback(feedback); } // Still the same ref_window. EXPECT_EQ(ref_window, scream.ref_window()); } TEST(ScreamV2Test, ReferenceWindowIncreaseLessPerStepOnLowRtt) { SimulatedClock clock(Timestamp::Seconds(1'234)); Environment env = CreateTestEnvironment({.time = &clock}); ScreamV2 scream_1(env); ScreamV2 scream_2(env); TransportPacketsFeedback feedback = CreateFeedback(clock.CurrentTime(), /*smoothed_rtt=*/ TimeDelta::Millis(10), /*number_of_ect1_packets=*/20, /*number_of_packets_in_flight=*/20); TransportPacketsFeedback high_rtt_feedback = feedback; high_rtt_feedback.smoothed_rtt = TimeDelta::Millis(100); TransportPacketsFeedback low_rtt_feedback = feedback; low_rtt_feedback.smoothed_rtt = TimeDelta::Millis(1); scream_1.OnTransportPacketsFeedback(high_rtt_feedback); scream_2.OnTransportPacketsFeedback(low_rtt_feedback); EXPECT_GT(scream_1.ref_window(), scream_2.ref_window()); } TEST(ScreamV2Test, ReferenceWindowIncreaseLessPerStepIfCeDetected) { SimulatedClock clock(Timestamp::Seconds(1'234)); Environment env = CreateTestEnvironment({.time = &clock}); ScreamV2 scream_1(env); ScreamV2 scream_2(env); TransportPacketsFeedback feedback = CreateFeedback(clock.CurrentTime(), /*smoothed_rtt=*/ TimeDelta::Millis(10), /*number_of_ect1_packets=*/20, /*number_of_packets_in_flight=*/20); TransportPacketsFeedback ce_detected_feedback = feedback; ce_detected_feedback.packet_feedbacks[0].ecn = EcnMarking::kCe; scream_1.OnTransportPacketsFeedback(feedback); scream_2.OnTransportPacketsFeedback(ce_detected_feedback); EXPECT_GT(scream_1.ref_window(), scream_2.ref_window()); } TEST(ScreamV2Test, ReferenceWindowIncreaseToDataInflight) { SimulatedClock clock(Timestamp::Seconds(1'234)); Environment env = CreateTestEnvironment({.time = &clock}); ScreamV2 scream(env); Timestamp start_time = clock.CurrentTime(); TimeDelta feedback_interval = TimeDelta::Millis(25); TransportPacketsFeedback feedback = CreateFeedback(clock.CurrentTime(), /*smoothed_rtt=*/ TimeDelta::Millis(10), /*number_of_ect1_packets=*/20, /*number_of_packets_in_flight=*/10); while (clock.CurrentTime() < start_time + TimeDelta::Seconds(2)) { feedback.feedback_time = clock.CurrentTime(); scream.OnTransportPacketsFeedback(feedback); clock.AdvanceTime(feedback_interval); } // Target rate can increase up to 1.1 * data_in_flight + Max Segment Size( // default 1000 bytes) when no max target rate has been set. EXPECT_EQ(scream.ref_window(), 1.1 * feedback.data_in_flight + DataSize::Bytes(1000)); } TEST(ScreamV2Test, CalculatesL4sAlpha) { SimulatedClock clock(Timestamp::Seconds(1'234)); Environment env = CreateTestEnvironment({.time = &clock}); ScreamV2 scream(env); Timestamp start_time = clock.CurrentTime(); TimeDelta feedback_interval = TimeDelta::Millis(25); TransportPacketsFeedback feedback = CreateFeedback(clock.CurrentTime(), /*smoothed_rtt=*/ TimeDelta::Millis(10), /*number_of_ect1_packets=*/20, /*number_of_packets_in_flight=*/20); // CE mark 20% of packets. for (int i = 0; i < 4; ++i) { feedback.packet_feedbacks[i].ecn = EcnMarking::kCe; } double l4s_alpha = scream.l4s_alpha(); while (clock.CurrentTime() < start_time + TimeDelta::Seconds(2)) { feedback.feedback_time = clock.CurrentTime(); scream.OnTransportPacketsFeedback(feedback); EXPECT_GT(scream.l4s_alpha(), l4s_alpha); clock.AdvanceTime(feedback_interval); } EXPECT_NEAR(scream.l4s_alpha(), 0.2, 0.01); } struct AdaptsToLinkCapacityParams { SimulatedNetwork::Config network_config; bool send_as_ect1 = true; TimeDelta adaption_time; TimeDelta time_to_run_after_adaption_time = TimeDelta::Seconds(5); }; struct AdaptsToLinkCapacityResult { DataRate data_rate_after_adaption; DataRate min_rate_after_adaption; DataRate max_rate_after_adaption; TimeDelta max_smoothed_rtt_after_adaptation = TimeDelta::Zero(); }; AdaptsToLinkCapacityResult RunAdaptToLinkCapacityTest( const AdaptsToLinkCapacityParams& params) { AdaptsToLinkCapacityResult result; const Timestamp kStartTime = Timestamp::Seconds(1'234); SimulatedClock clock(kStartTime); Environment env = CreateTestEnvironment({.time = &clock}); ScreamV2 scream(env); CcFeedbackGenerator feedback_generator( {.network_config = params.network_config, .send_as_ect1 = params.send_as_ect1, .packet_size = DataSize::Bytes(1000)}); DataRate send_rate = DataRate::KilobitsPerSec(100); while (clock.CurrentTime() < kStartTime + params.adaption_time) { TransportPacketsFeedback feedback = feedback_generator.ProcessUntilNextFeedback(send_rate, clock); send_rate = scream.OnTransportPacketsFeedback(feedback); } result.data_rate_after_adaption = send_rate; result.min_rate_after_adaption = send_rate; result.max_rate_after_adaption = send_rate; Timestamp time_after_adaption = clock.CurrentTime(); while (clock.CurrentTime() < time_after_adaption + params.time_to_run_after_adaption_time) { TransportPacketsFeedback feedback = feedback_generator.ProcessUntilNextFeedback(send_rate, clock); send_rate = scream.OnTransportPacketsFeedback(feedback); result.min_rate_after_adaption = std::min(result.min_rate_after_adaption, send_rate); result.max_rate_after_adaption = std::max(result.max_rate_after_adaption, send_rate); result.max_smoothed_rtt_after_adaptation = std::max( result.max_smoothed_rtt_after_adaptation, feedback.smoothed_rtt); } RTC_LOG(LS_INFO) << " rate_after_adaption " << result.data_rate_after_adaption << " max_rate_after_adaption: " << result.max_rate_after_adaption << " min_rate_after_adaption: " << result.min_rate_after_adaption; return result; } TEST(ScreamV2Test, AdaptsToEcnLinkCapacity1Mbps) { AdaptsToLinkCapacityParams params{ .network_config = {.queue_delay_ms = 25, .link_capacity = DataRate::KilobitsPerSec(1000)}, .send_as_ect1 = true, .adaption_time = TimeDelta::Seconds(3)}; AdaptsToLinkCapacityResult result = RunAdaptToLinkCapacityTest(params); EXPECT_LT(result.data_rate_after_adaption, DataRate::KilobitsPerSec(1100)); EXPECT_GT(result.data_rate_after_adaption, DataRate::KilobitsPerSec(650)); EXPECT_LT(result.max_rate_after_adaption, DataRate::KilobitsPerSec(1100)); EXPECT_GT(result.min_rate_after_adaption, DataRate::KilobitsPerSec(650)); EXPECT_LT(result.max_smoothed_rtt_after_adaptation, TimeDelta::Millis(25 * 2 + 25)); } TEST(ScreamV2Test, AdaptsToLossLinkCapacity5Mbps) { AdaptsToLinkCapacityParams params{ .network_config = {.queue_length_packets = 3, .queue_delay_ms = 10, .link_capacity = DataRate::KilobitsPerSec(5000)}, .send_as_ect1 = false, // Adapt only due to loss when queues overflow. .adaption_time = TimeDelta::Seconds(10)}; AdaptsToLinkCapacityResult result = RunAdaptToLinkCapacityTest(params); EXPECT_LT(result.data_rate_after_adaption, DataRate::KilobitsPerSec(5400)); EXPECT_GT(result.data_rate_after_adaption, DataRate::KilobitsPerSec(2500)); EXPECT_LT(result.max_rate_after_adaption, DataRate::KilobitsPerSec(5400)); EXPECT_GT(result.min_rate_after_adaption, DataRate::KilobitsPerSec(2500)); EXPECT_LT(result.max_smoothed_rtt_after_adaptation, TimeDelta::Millis(10 * 2 + 40)); } TEST(ScreamV2Test, AdaptsToDelayLinkCapacity2Mbps) { AdaptsToLinkCapacityParams params{ .network_config = {.queue_delay_ms = 10, .link_capacity = DataRate::KilobitsPerSec(2000)}, .send_as_ect1 = false, // Adapt only due to delay increase. .adaption_time = TimeDelta::Seconds(3)}; AdaptsToLinkCapacityResult result = RunAdaptToLinkCapacityTest(params); EXPECT_LT(result.data_rate_after_adaption, DataRate::KilobitsPerSec(2300)); EXPECT_GT(result.data_rate_after_adaption, DataRate::KilobitsPerSec(1700)); EXPECT_LT(result.max_rate_after_adaption, DataRate::KilobitsPerSec(2300)); EXPECT_GT(result.min_rate_after_adaption, DataRate::KilobitsPerSec(1700)); EXPECT_LT(result.max_smoothed_rtt_after_adaptation, TimeDelta::Millis(10 * 2 + 50 + 10)); } // TODO: bugs.webrtc.org/447037083 - implement support for resetting base delay // if a standing queue has been build up. // https://github.com/EricssonResearch/scream/blob/master/code/ScreamV2Tx.cpp#L1127 TEST(ScreamV2Test, DISABLED_AdaptsToDelayLinkCapacity2MbpsLongRunning) { AdaptsToLinkCapacityParams params{ .network_config = {.queue_delay_ms = 10, .link_capacity = DataRate::KilobitsPerSec(2000)}, .send_as_ect1 = false, // Adapt only due to delay increase. .adaption_time = TimeDelta::Seconds(3), .time_to_run_after_adaption_time = TimeDelta::Minutes(15)}; AdaptsToLinkCapacityResult result = RunAdaptToLinkCapacityTest(params); EXPECT_LT(result.max_rate_after_adaption, DataRate::KilobitsPerSec(2300)); EXPECT_GT(result.min_rate_after_adaption, DataRate::KilobitsPerSec(1700)); EXPECT_LT(result.max_smoothed_rtt_after_adaptation, TimeDelta::Millis(10 * 2 + 50 + 10)); } } // namespace } // namespace webrtc