commit e90c7b8e95a8132f03074de5c181192e583bfbf9 parent a51b13610dd5d9580550cf36c38742766323ab6a Author: Dan Baker <dbaker@mozilla.com> Date: Tue, 2 Dec 2025 01:08:51 -0700 Bug 2000941 - Vendor libwebrtc from ba391e6893 Upstream commit: https://webrtc.googlesource.com/src/+/ba391e6893da350a48a7895a0bff8f033369a729 Introduce FeedbackGenerator for testing Congestion controller using RFC 8888 Change-Id: I02bfc545fff792e4ea73830317e3b37abd1e1424 Bug: webrtc:447037083 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/412060 Commit-Queue: Per Kjellander <perkj@webrtc.org> Reviewed-by: Danil Chapovalov <danilchap@webrtc.org> Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> Cr-Commit-Position: refs/heads/main@{#45749} Diffstat:
10 files changed, 547 insertions(+), 9 deletions(-)
diff --git a/third_party/libwebrtc/README.mozilla.last-vendor b/third_party/libwebrtc/README.mozilla.last-vendor @@ -1,4 +1,4 @@ # ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc -libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-12-02T08:05:38.127499+00:00. +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-12-02T08:08:37.061687+00:00. # base of lastest vendoring -bc7362fb04 +ba391e6893 diff --git a/third_party/libwebrtc/api/test/network_emulation/DEPS b/third_party/libwebrtc/api/test/network_emulation/DEPS @@ -2,6 +2,7 @@ specific_include_rules = { ".*": [ "+rtc_base/socket_address.h", "+rtc_base/ip_address.h", + "+rtc_base/random.h", "+rtc_base/copy_on_write_buffer.h", ], } diff --git a/third_party/libwebrtc/api/test/network_emulation/leaky_bucket_network_queue.cc b/third_party/libwebrtc/api/test/network_emulation/leaky_bucket_network_queue.cc @@ -10,16 +10,31 @@ #include "api/test/network_emulation/leaky_bucket_network_queue.h" +#include <algorithm> #include <cstddef> #include <optional> #include <vector> #include "api/test/simulated_network.h" +#include "api/transport/ecn_marking.h" +#include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "rtc_base/checks.h" +#include "rtc_base/logging.h" namespace webrtc { +LeakyBucketNetworkQueue::LeakyBucketNetworkQueue(const Config& config) + : max_ect1_sojourn_time_(config.max_ect1_sojourn_time), + target_ect1_sojourn_time_(config.target_ect1_sojourn_time), + random_(config.seed) { + RTC_DCHECK_LE(config.target_ect1_sojourn_time, config.max_ect1_sojourn_time); +} + +void LeakyBucketNetworkQueue::SetMaxPacketCapacity(size_t max_capactiy) { + max_packet_capacity_ = max_capactiy; +} + bool LeakyBucketNetworkQueue::EnqueuePacket( const PacketInFlightInfo& packet_info) { if (max_packet_capacity_ <= queue_.size()) { @@ -45,16 +60,31 @@ std::optional<PacketInFlightInfo> LeakyBucketNetworkQueue::DequeuePacket( RTC_DCHECK_LE(queue_.front().send_time(), time_now); PacketInFlightInfo packet_info = queue_.front(); queue_.pop(); + MaybeMarkAsCe(time_now, packet_info); return packet_info; } -void LeakyBucketNetworkQueue::SetMaxPacketCapacity(size_t max_capactiy) { - max_packet_capacity_ = max_capactiy; +void LeakyBucketNetworkQueue::MaybeMarkAsCe(Timestamp time_now, + PacketInFlightInfo& packet_info) { + if (packet_info.ecn != EcnMarking::kEct1 || + target_ect1_sojourn_time_.IsInfinite() || + max_ect1_sojourn_time_.IsInfinite()) { + return; + } + TimeDelta sojourn_time = time_now - packet_info.send_time(); + double p_mark = + std::clamp((sojourn_time - target_ect1_sojourn_time_) / + (max_ect1_sojourn_time_ - target_ect1_sojourn_time_), + 0.0, 1.0); + if (random_.Rand<double>() < p_mark) { + RTC_LOG(LS_VERBOSE) << "Marking packet " << packet_info.packet_id + << " as CE. p_mark: " << p_mark + << " sojourn_time: " << sojourn_time; + packet_info.ecn = EcnMarking::kCe; + } } -bool LeakyBucketNetworkQueue::empty() const { - return queue_.empty(); -} +bool LeakyBucketNetworkQueue::empty() const { return queue_.empty(); } void LeakyBucketNetworkQueue::DropOldestPacket() { dropped_packets_.push_back(queue_.front()); diff --git a/third_party/libwebrtc/api/test/network_emulation/leaky_bucket_network_queue.h b/third_party/libwebrtc/api/test/network_emulation/leaky_bucket_network_queue.h @@ -19,7 +19,9 @@ #include "api/test/network_emulation/network_queue.h" #include "api/test/simulated_network.h" +#include "api/units/time_delta.h" #include "api/units/timestamp.h" +#include "rtc_base/random.h" namespace webrtc { @@ -27,7 +29,18 @@ namespace webrtc { // can be queued. class LeakyBucketNetworkQueue : public NetworkQueue { public: - LeakyBucketNetworkQueue() = default; + struct Config { + int seed = 1; + // If enqueued packets are sent as ECT1 and sojourn time is larger than + // `target_ect1_sojourn_time`, packets will be marked as CE with + // probability (sojourn_time - `target_ect1_sojourn_time`) / + // (`max_ect1_sojourn_time` - `target_ect1_sojourn_time`) + TimeDelta max_ect1_sojourn_time = TimeDelta::PlusInfinity(); + TimeDelta target_ect1_sojourn_time = TimeDelta::PlusInfinity(); + }; + + LeakyBucketNetworkQueue() : LeakyBucketNetworkQueue(Config()) {} + explicit LeakyBucketNetworkQueue(const Config& config); // If `max_capacity` is larger than current queue length, existing packets are // not dropped. But the queue will not accept new packets until queue length // is below `max_capacity`, @@ -42,17 +55,31 @@ class LeakyBucketNetworkQueue : public NetworkQueue { void DropOldestPacket(); private: + void MaybeMarkAsCe(Timestamp time_now, PacketInFlightInfo& packet_info); + size_t max_packet_capacity_ = kMaxPacketCapacity; + const TimeDelta max_ect1_sojourn_time_; + const TimeDelta target_ect1_sojourn_time_; + Random random_; std::queue<PacketInFlightInfo> queue_; std::vector<PacketInFlightInfo> dropped_packets_; }; class LeakyBucketNetworkQueueFactory : public NetworkQueueFactory { public: + LeakyBucketNetworkQueueFactory() + : LeakyBucketNetworkQueueFactory(LeakyBucketNetworkQueue::Config()) {} + explicit LeakyBucketNetworkQueueFactory( + const LeakyBucketNetworkQueue::Config& config) + : config_(config) {} + std::unique_ptr<NetworkQueue> CreateQueue() override { - return std::make_unique<LeakyBucketNetworkQueue>(); + return std::make_unique<LeakyBucketNetworkQueue>(config_); } + + private: + const LeakyBucketNetworkQueue::Config config_; }; } // namespace webrtc diff --git a/third_party/libwebrtc/api/test/network_emulation/leaky_bucket_network_queue_unittest.cc b/third_party/libwebrtc/api/test/network_emulation/leaky_bucket_network_queue_unittest.cc @@ -13,7 +13,9 @@ #include <optional> #include "api/test/simulated_network.h" +#include "api/transport/ecn_marking.h" #include "api/units/data_size.h" +#include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "test/gmock.h" #include "test/gtest.h" @@ -70,5 +72,60 @@ TEST(LeakyBucketNetworkQueueTest, DequeueDoesNotChangePacketInfo) { Property(&PacketInFlightInfo::send_time, packet_info.send_time())))); } +TEST(LeakyBucketNetworkQueueTest, + Ect1PacketMarkedCeIfSoujournEqualOrGreaterThanMax) { + LeakyBucketNetworkQueue queue( + {.max_ect1_sojourn_time = TimeDelta::Millis(10), + .target_ect1_sojourn_time = TimeDelta::Millis(5)}); + + PacketInFlightInfo packet_info(DataSize::Bytes(123), Timestamp::Seconds(123), + /*packet_id=*/1, EcnMarking::kEct1); + queue.EnqueuePacket(packet_info); + + EXPECT_THAT( + queue.DequeuePacket(Timestamp::Seconds(123) + TimeDelta::Millis(10)), + Optional(Field(&PacketInFlightInfo::ecn, EcnMarking::kCe))); + + // Sojourn time greater than max. + queue.EnqueuePacket(packet_info); + EXPECT_THAT( + queue.DequeuePacket(Timestamp::Seconds(123) + TimeDelta::Millis(11)), + Optional(Field(&PacketInFlightInfo::ecn, EcnMarking::kCe))); +} + +TEST(LeakyBucketNetworkQueueTest, Ect0PacketNeverMarkedCe) { + LeakyBucketNetworkQueue queue( + {.max_ect1_sojourn_time = TimeDelta::Millis(10), + .target_ect1_sojourn_time = TimeDelta::Millis(5)}); + + PacketInFlightInfo packet_info(DataSize::Bytes(123), Timestamp::Seconds(123), + /*packet_id=*/1, EcnMarking::kEct0); + queue.EnqueuePacket(packet_info); + + EXPECT_THAT( + queue.DequeuePacket(Timestamp::Seconds(123) + TimeDelta::Millis(10)), + Optional(Field(&PacketInFlightInfo::ecn, EcnMarking::kEct0))); +} + +TEST(LeakyBucketNetworkQueueTest, + Ect1PacketNotMarkedAsCeIfSoujournTimeLessOrEqualTarget) { + LeakyBucketNetworkQueue queue( + {.max_ect1_sojourn_time = TimeDelta::Millis(10), + .target_ect1_sojourn_time = TimeDelta::Millis(5)}); + + PacketInFlightInfo packet_info(DataSize::Bytes(123), Timestamp::Seconds(123), + /*packet_id=*/1, EcnMarking::kEct1); + queue.EnqueuePacket(packet_info); + + EXPECT_THAT( + queue.DequeuePacket(Timestamp::Seconds(123) + TimeDelta::Millis(5)), + Optional(Field(&PacketInFlightInfo::ecn, EcnMarking::kEct1))); + + queue.EnqueuePacket(packet_info); + EXPECT_THAT( + queue.DequeuePacket(Timestamp::Seconds(123) + TimeDelta::Millis(3)), + Optional(Field(&PacketInFlightInfo::ecn, EcnMarking::kEct1))); +} + } // namespace } // namespace webrtc diff --git a/third_party/libwebrtc/modules/congestion_controller/BUILD.gn b/third_party/libwebrtc/modules/congestion_controller/BUILD.gn @@ -72,6 +72,7 @@ if (rtc_include_tests && !build_with_chromium) { "goog_cc:goog_cc_unittests", "pcc:pcc_unittests", "rtp:congestion_controller_unittests", + "scream/test:cc_feedback_generator_unittests", ] } } diff --git a/third_party/libwebrtc/modules/congestion_controller/scream/test/BUILD.gn b/third_party/libwebrtc/modules/congestion_controller/scream/test/BUILD.gn @@ -0,0 +1,52 @@ +# Copyright(c) 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. + +import("../../../../webrtc.gni") + +if (rtc_include_tests) { + rtc_library("cc_feedback_generator") { + testonly = true + sources = [ + "cc_feedback_generator.cc", + "cc_feedback_generator.h", + ] + deps = [ + "../../../../api:simulated_network_api", + "../../../../api/test/network_emulation:network_queue", + "../../../../api/transport:ecn_marking", + "../../../../api/transport:network_control", + "../../../../api/units:data_rate", + "../../../../api/units:data_size", + "../../../../api/units:time_delta", + "../../../../api/units:timestamp", + "../../../../rtc_base:checks", + "../../../../rtc_base:logging", + "../../../../system_wrappers", + "../../../../test:test_support", + "../../../../test/network:simulated_network", + ] + } + + rtc_library("cc_feedback_generator_unittests") { + testonly = true + sources = [ "cc_feedback_generator_unittest.cc" ] + deps = [ + ":cc_feedback_generator", + "../../../../api/transport:ecn_marking", + "../../../../api/transport:network_control", + "../../../../api/units:data_rate", + "../../../../api/units:data_size", + "../../../../api/units:time_delta", + "../../../../api/units:timestamp", + "../../../../system_wrappers", + "../../../../test:test_support", + "../../../../test/network:simulated_network", + "//testing/gtest", + ] + } +} diff --git a/third_party/libwebrtc/modules/congestion_controller/scream/test/cc_feedback_generator.cc b/third_party/libwebrtc/modules/congestion_controller/scream/test/cc_feedback_generator.cc @@ -0,0 +1,168 @@ +/* + * Copyright (c) 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/test/cc_feedback_generator.h" + +#include <memory> +#include <optional> +#include <utility> +#include <vector> + +#include "api/test/simulated_network.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 "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "system_wrappers/include/clock.h" +#include "test/gtest.h" +#include "test/network/simulated_network.h" + +namespace webrtc { + +// static +int CcFeedbackGenerator::CountCeMarks( + const TransportPacketsFeedback& feedback) { + int number_of_ce_marks = 0; + for (const PacketResult& packet : feedback.packet_feedbacks) { + if (packet.ecn == EcnMarking::kCe) { + ++number_of_ce_marks; + } + } + return number_of_ce_marks; +} + +CcFeedbackGenerator::CcFeedbackGenerator(Config config) + : packet_size_(config.packet_size), + time_between_feedback_(config.time_between_feedback), + one_way_delay_(TimeDelta::Millis(config.network_config.queue_delay_ms)), + send_as_ect1_(config.send_as_ect1), + network_(config.network_config, + /*random_seed=*/1, + std::move(config.queue)) { + RTC_CHECK(!config.network_config.allow_reordering) + << "Reordering is not supported"; +} + +TransportPacketsFeedback CcFeedbackGenerator::ProcessUntilNextFeedback( + DataRate send_rate, + SimulatedClock& clock, + TimeDelta max_time) { + Timestamp end_time = clock.CurrentTime() + max_time; + while (clock.CurrentTime() < end_time) { + MaybeSendPackets(clock.CurrentTime(), send_rate); + ProcessNetwork(clock.CurrentTime()); + std::optional<TransportPacketsFeedback> feedback = + MaybeSendFeedback(clock.CurrentTime()); + if (feedback.has_value()) { + return *feedback; + } + clock.AdvanceTime(TimeDelta::Millis(1)); + } + ADD_FAILURE() << "No feedback received after " << max_time; + return TransportPacketsFeedback(); +} + +void CcFeedbackGenerator::MaybeSendPackets(Timestamp time, DataRate send_rate) { + if (last_send_budget_update.IsInfinite()) { + send_budget_ = packet_size_; + } else { + send_budget_ += send_rate * (time - last_send_budget_update); + } + last_send_budget_update = time; + + // This simulator pace out packets with perfect pacing. + while (send_budget_ >= packet_size_) { + send_budget_ -= packet_size_; + PacketInFlightInfo packet_info( + packet_size_, time, + /*packet_id=*/next_packet_id_++, + send_as_ect1_ ? EcnMarking::kEct1 : EcnMarking::kNotEct); + packets_in_flight_.push_back(packet_info); + bool packet_sent = network_.EnqueuePacket(packet_info); + if (!packet_sent) { + RTC_LOG(LS_VERBOSE) << "Packet " << (next_packet_id_ - 1) + << " dropped by queueu "; + } + } +} + +void CcFeedbackGenerator::ProcessNetwork(Timestamp time) { + std::vector<PacketDeliveryInfo> received_packets = + network_.DequeueDeliverablePackets(time.us()); + packets_received_.insert(packets_received_.end(), received_packets.begin(), + received_packets.end()); +} + +std::optional<TransportPacketsFeedback> +CcFeedbackGenerator::MaybeSendFeedback(Timestamp time) { + if (last_feedback_time_.IsFinite() && + time - last_feedback_time_ < time_between_feedback_) { + return std::nullopt; + } + // Time to deliver feedback if there are packets to deliver. + TransportPacketsFeedback feedback; + // Deliver feedback one way delay later than when the packets were + // received. + while (!packets_received_.empty() && + time - Timestamp::Micros(packets_received_.front().receive_time_us) >= + one_way_delay_) { + PacketDeliveryInfo delivery_info = packets_received_.front(); + packets_received_.pop_front(); + + while (packets_in_flight_.front().packet_id != delivery_info.packet_id) { + // Reordering of packets is not supported, so the packet is lost. + PacketResult packet_result; + packet_result.sent_packet.send_time = + packets_in_flight_.front().send_time(); + packet_result.sent_packet.size = packets_in_flight_.front().packet_size(); + packets_in_flight_.pop_front(); + feedback.packet_feedbacks.push_back(packet_result); + } + RTC_CHECK_EQ(packets_in_flight_.front().packet_id, delivery_info.packet_id); + PacketResult packet_result; + packet_result.sent_packet.send_time = + packets_in_flight_.front().send_time(); + packet_result.sent_packet.size = packets_in_flight_.front().packet_size(); + + packet_result.receive_time = + Timestamp::Micros(delivery_info.receive_time_us); + packet_result.ecn = delivery_info.ecn; + packets_in_flight_.pop_front(); + + TimeDelta rtt = one_way_delay_ + (packet_result.receive_time - + packet_result.sent_packet.send_time); + if (smoothed_rtt_.IsInfinite()) { + smoothed_rtt_ = rtt; + } + smoothed_rtt_ = (smoothed_rtt_ * 7 + rtt) / 8; // RFC 6298, alpha = 1/8 + feedback.smoothed_rtt = smoothed_rtt_; + feedback.packet_feedbacks.push_back(packet_result); + } + if (feedback.packet_feedbacks.empty()) { + return std::nullopt; + } + for (const PacketInFlightInfo& in_flight : packets_in_flight_) { + feedback.data_in_flight += in_flight.packet_size(); + } + RTC_LOG(LS_VERBOSE) << "Delivering feedback at time: " << time + << " #packets:" << feedback.packet_feedbacks.size() + << " #ce:" << CountCeMarks(feedback) + << " #lost: " << feedback.LostWithSendInfo().size() + << " data_in_flight: " << feedback.data_in_flight; + + feedback.feedback_time = time; + last_feedback_time_ = time; + return feedback; +} +} // namespace webrtc diff --git a/third_party/libwebrtc/modules/congestion_controller/scream/test/cc_feedback_generator.h b/third_party/libwebrtc/modules/congestion_controller/scream/test/cc_feedback_generator.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 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. + */ + +#ifndef MODULES_CONGESTION_CONTROLLER_SCREAM_TEST_CC_FEEDBACK_GENERATOR_H_ +#define MODULES_CONGESTION_CONTROLLER_SCREAM_TEST_CC_FEEDBACK_GENERATOR_H_ + +#include <cstdint> +#include <deque> +#include <memory> +#include <optional> + +#include "api/test/network_emulation/network_queue.h" +#include "api/test/network_emulation/leaky_bucket_network_queue.h" +#include "api/test/simulated_network.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 "system_wrappers/include/clock.h" +#include "test/network/simulated_network.h" + +namespace webrtc { + +// Simulates sending packets with a given send rate over a simulated network and +// generates TransportPacketsFeedback that is supposed to match +// TransportPacketsFeedback from rtcp::CongestionControlFeedback (RFC8888). +class CcFeedbackGenerator { + public: + struct Config { + SimulatedNetwork::Config network_config; + TimeDelta time_between_feedback = TimeDelta::Millis(25); + bool send_as_ect1 = true; + std::unique_ptr<NetworkQueue> queue = + std::make_unique<LeakyBucketNetworkQueue>( + LeakyBucketNetworkQueue::Config{ + .max_ect1_sojourn_time = TimeDelta::Millis(8), + .target_ect1_sojourn_time = TimeDelta::Millis(4)}); + DataSize packet_size = DataSize::Bytes(1000); + }; + + explicit CcFeedbackGenerator(Config config); + + // Processes the simulation until the next feedback message is received. + // The function will send packets at the given send rate until the next + // feedback message is generated. + TransportPacketsFeedback ProcessUntilNextFeedback( + DataRate send_rate, SimulatedClock& clock, + TimeDelta max_time = TimeDelta::Seconds(1)); + + static int CountCeMarks(const TransportPacketsFeedback& feedback); + + private: + void MaybeSendPackets(Timestamp time, DataRate send_rate); + void ProcessNetwork(Timestamp time); + std::optional<TransportPacketsFeedback> MaybeSendFeedback(Timestamp time); + + const DataSize packet_size_; + const TimeDelta time_between_feedback_; + const TimeDelta one_way_delay_; + const bool send_as_ect1_; + SimulatedNetwork network_; + + int64_t next_packet_id_ = 0; + std::deque<PacketInFlightInfo> packets_in_flight_; + // `packets_received_` are packed that have been received by the remote, but + // feedback has not yet been received by the sender. Feedback is delivered one + // way delay later than when the packets were received. + std::deque<PacketDeliveryInfo> packets_received_; + + Timestamp last_feedback_time_ = Timestamp::MinusInfinity(); + + TimeDelta smoothed_rtt_ = TimeDelta::PlusInfinity(); + + Timestamp last_send_budget_update = Timestamp::MinusInfinity(); + DataSize send_budget_; +}; +} // namespace webrtc + +#endif // MODULES_CONGESTION_CONTROLLER_SCREAM_TEST_CC_FEEDBACK_GENERATOR_H_ diff --git a/third_party/libwebrtc/modules/congestion_controller/scream/test/cc_feedback_generator_unittest.cc b/third_party/libwebrtc/modules/congestion_controller/scream/test/cc_feedback_generator_unittest.cc @@ -0,0 +1,115 @@ +/* + * Copyright (c) 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/test/cc_feedback_generator.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 "system_wrappers/include/clock.h" +#include "test/gtest.h" +#include "test/network/simulated_network.h" + +namespace webrtc { +namespace { + +constexpr DataSize kPacketSize = DataSize::Bytes(1000); + +TEST(CcFeedbackGeneratorTest, BasicFeedback) { + // Link capacity of 1000Kbps means it will take 1000*8/1000 = 8ms to send one + // packet through the narrow section + 25ms fixed one way delay. + // With perfect pacing, smoothed_rtt should be 58ms. + // With a send rate of 500Kbps, where each packet is 1000bytes, one packet is + // sent every 16ms. + // The first feedback packet is expected to be sent as soon as the first + // packet has been delivered and received 25ms later. At that time, there + // should be 3 packets in flight (3*16=48ms). + SimulatedClock clock(Timestamp::Seconds(1234)); + CcFeedbackGenerator feedback_generator( + {.network_config = {.queue_delay_ms = 25, + .link_capacity = DataRate::KilobitsPerSec(1000)}, + .time_between_feedback = TimeDelta::Millis(50)}); + + TransportPacketsFeedback feedback_1 = + feedback_generator.ProcessUntilNextFeedback( + /*send_rate=*/DataRate::KilobitsPerSec(500), clock); + + EXPECT_EQ(feedback_1.feedback_time, clock.CurrentTime()); + EXPECT_EQ(feedback_1.smoothed_rtt, TimeDelta::Millis(58)); + EXPECT_EQ(feedback_1.data_in_flight, 3 * kPacketSize); + for (const PacketResult& packet : feedback_1.packet_feedbacks) { + EXPECT_EQ((packet.receive_time - packet.sent_packet.send_time), + TimeDelta::Millis(25 + 8)); + EXPECT_EQ(packet.ecn, EcnMarking::kEct1); + } + + TransportPacketsFeedback feedback_2 = + feedback_generator.ProcessUntilNextFeedback( + /*send_rate=*/DataRate::KilobitsPerSec(500), clock); + EXPECT_EQ((feedback_2.feedback_time - feedback_1.feedback_time).ms(), 50); +} + +TEST(CcFeedbackGeneratorTest, CeMarksPacketsIfSendRateIsTooHigh) { + SimulatedClock clock(Timestamp::Seconds(1234)); + CcFeedbackGenerator feedback_generator( + {.network_config = {.queue_delay_ms = 25, + .link_capacity = DataRate::KilobitsPerSec(1000)}}); + + int number_of_ce_marks = 0; + for (int i = 0; i < 5; ++i) { + TransportPacketsFeedback feedback = + feedback_generator.ProcessUntilNextFeedback( + /*send_rate=*/DataRate::KilobitsPerSec(1100), clock); + number_of_ce_marks += CcFeedbackGenerator::CountCeMarks(feedback); + } + EXPECT_GE(number_of_ce_marks, 2); +} + +TEST(CcFeedbackGeneratorTest, NoCeMarksIfSendRateIsBelowLinkCapacity) { + SimulatedClock clock(Timestamp::Seconds(1234)); + CcFeedbackGenerator feedback_generator( + {.network_config = {.queue_delay_ms = 25, + .link_capacity = DataRate::KilobitsPerSec(1000)}, + .time_between_feedback = TimeDelta::Millis(25)}); + + int number_of_ce_marks = 0; + for (int i = 0; i < 5; ++i) { + TransportPacketsFeedback feedback = + feedback_generator.ProcessUntilNextFeedback( + /*send_rate=*/DataRate::KilobitsPerSec(1000), clock); + number_of_ce_marks += CcFeedbackGenerator::CountCeMarks(feedback); + } + EXPECT_EQ(number_of_ce_marks, 0); +} + +TEST(CcFeedbackGeneratorTest, DropPacketsIfSendRateIsTooHigh) { + SimulatedClock clock(Timestamp::Seconds(1234)); + SimulatedNetwork::Config network_config = { + .queue_length_packets = 1, // Only one packet is allowed to be queued + .queue_delay_ms = 25, + .link_capacity = DataRate::KilobitsPerSec(1000), + }; + CcFeedbackGenerator feedback_generator( + {.network_config = network_config, .send_as_ect1 = false}); + + int number_of_lost_packets = 0; + for (int i = 0; i < 5; ++i) { + TransportPacketsFeedback feedback = + feedback_generator.ProcessUntilNextFeedback( + /*send_rate=*/DataRate::KilobitsPerSec(1100), clock); + number_of_lost_packets += feedback.LostWithSendInfo().size(); + } + EXPECT_GE(number_of_lost_packets, 2); +} + +} // namespace +} // namespace webrtc