commit 82601c6406a36b04ccea1b381f90d9ec7da01a2d
parent cc1d60c3604fcbe529f2c8d5e3c607dc854793cd
Author: Michael Froman <mfroman@mozilla.com>
Date: Thu, 9 Oct 2025 13:30:09 -0500
Bug 1993083 - Vendor libwebrtc from 8d91ed7f24
Upstream commit: https://webrtc.googlesource.com/src/+/8d91ed7f24ad1138fb9cf663e19f7dac25680829
Add DualPi2 NetworkQueue
DualPi2NetworkQueue is a simplified version of the DualPi2 AQM controller in
https://github.com/L4STeam/linux/. Concepts are described in
https://datatracker.ietf.org/doc/html/rfc9332.
Purpose is to be able to test WebRTC L4S implementation.
Change-Id: I7381aabb7a2708253bd0f230ac1013677bbf5f5f
Bug: webrtc:42225697
Change-Id: I7381aabb7a2708253bd0f230ac1013677bbf5f5f
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/399900
Commit-Queue: Per Kjellander <perkj@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#45139}
Diffstat:
8 files changed, 648 insertions(+), 7 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 /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc
-libwebrtc updated from /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-10-09T18:28:09.753566+00:00.
+libwebrtc updated from /home/mfroman/mozilla/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-10-09T18:29:58.547522+00:00.
# base of lastest vendoring
-5782b72f52
+8d91ed7f24
diff --git a/third_party/libwebrtc/api/test/network_emulation/BUILD.gn b/third_party/libwebrtc/api/test/network_emulation/BUILD.gn
@@ -81,6 +81,8 @@ rtc_library("network_queue") {
visibility = [ "*" ]
sources = [
+ "dual_pi2_network_queue.cc",
+ "dual_pi2_network_queue.h",
"leaky_bucket_network_queue.cc",
"leaky_bucket_network_queue.h",
"network_queue.h",
@@ -90,20 +92,32 @@ rtc_library("network_queue") {
"../..:sequence_checker",
"../..:simulated_network_api",
"../../../rtc_base:checks",
+ "../../../rtc_base:logging",
"../../../rtc_base:macromagic",
+ "../../../rtc_base:random",
+ "../../transport:ecn_marking",
+ "../../units:data_rate",
+ "../../units:data_size",
+ "../../units:time_delta",
"../../units:timestamp",
]
}
rtc_library("network_queue_unittests") {
- sources = [ "leaky_bucket_network_queue_unittest.cc" ]
+ sources = [
+ "dual_pi2_network_queue_unittest.cc",
+ "leaky_bucket_network_queue_unittest.cc",
+ ]
testonly = true
deps = [
":network_queue",
"../..:simulated_network_api",
"../../../test:test_support",
+ "../../transport:ecn_marking",
+ "../../units:data_rate",
"../../units:data_size",
+ "../../units:time_delta",
"../../units:timestamp",
]
}
diff --git a/third_party/libwebrtc/api/test/network_emulation/dual_pi2_network_queue.cc b/third_party/libwebrtc/api/test/network_emulation/dual_pi2_network_queue.cc
@@ -0,0 +1,157 @@
+/*
+ * 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 "api/test/network_emulation/dual_pi2_network_queue.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <optional>
+#include <queue>
+
+#include "api/sequence_checker.h"
+#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 "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+DualPi2NetworkQueue::DualPi2NetworkQueue(const Config& config)
+ : config_(config),
+ step_threshold_(config.link_rate.IsInfinite()
+ ? DataSize::Infinity()
+ : config_.target_delay * config_.link_rate * 2),
+ random_(config.seed),
+ distribution_(0.0, 1.0) {
+ sequence_checker_.Detach();
+}
+
+void DualPi2NetworkQueue::SetMaxPacketCapacity(size_t max_packet_capacity) {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ max_packet_capacity_ = max_packet_capacity;
+ // Hack to allow SetMaxpPacketCapactiy to be called before the queue is being
+ // used on another sequence.
+ sequence_checker_.Detach();
+}
+
+bool DualPi2NetworkQueue::EnqueuePacket(const PacketInFlightInfo& packet_info) {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ UpdateBaseMarkingProbability(packet_info.send_time());
+ if (max_packet_capacity_.has_value() &&
+ l4s_queue_.size() + classic_queue_.size() >= *max_packet_capacity_) {
+ RTC_LOG(LS_WARNING)
+ << "DualPi2NetworkQueue::EnqueuePacket: Dropping packet "
+ "because max packet capacity is reached.";
+ return false;
+ }
+
+ if (packet_info.ecn == EcnMarking::kNotEct ||
+ packet_info.ecn == EcnMarking::kEct0) {
+ bool take_action = ShouldTakeAction(classic_drop_probability());
+ if (!take_action) {
+ total_queued_size_ += packet_info.packet_size();
+ classic_queue_.push(packet_info);
+ return true;
+ }
+ RTC_DLOG(LS_WARNING)
+ << "DualPi2NetworkQueue::EnqueuePacket: Dropping classic packet "
+ << packet_info.packet_id << ". Classic drop probability is "
+ << classic_drop_probability()
+ << " L4S queue size: " << l4s_queue_.size()
+ << " classic queue size: " << classic_queue_.size();
+
+ return false;
+ }
+ RTC_DCHECK(packet_info.ecn == EcnMarking::kEct1 ||
+ packet_info.ecn == EcnMarking::kCe);
+ total_queued_size_ += packet_info.packet_size();
+ bool take_action = ShouldTakeAction(l4s_marking_probability());
+ if (take_action) {
+ PacketInFlightInfo ce_packet_info(packet_info);
+ ce_packet_info.ecn = EcnMarking::kCe;
+ l4s_queue_.push(ce_packet_info);
+ } else {
+ l4s_queue_.push(packet_info);
+ }
+ return true;
+}
+
+std::optional<PacketInFlightInfo> DualPi2NetworkQueue::PeekNextPacket() const {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ if (!l4s_queue_.empty()) {
+ return l4s_queue_.front();
+ }
+ if (!classic_queue_.empty()) {
+ return classic_queue_.front();
+ }
+ return std::nullopt;
+}
+
+std::optional<PacketInFlightInfo> DualPi2NetworkQueue::DequeuePacket(
+ Timestamp time_now) {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ UpdateBaseMarkingProbability(time_now);
+ std::queue<PacketInFlightInfo>& queue =
+ l4s_queue_.empty() ? classic_queue_ : l4s_queue_;
+ if (queue.empty()) {
+ return std::nullopt;
+ }
+
+ PacketInFlightInfo packet_info = queue.front();
+ queue.pop();
+ total_queued_size_ -= packet_info.packet_size();
+ if (packet_info.ecn == EcnMarking::kEct1 &&
+ ShouldTakeAction(l4s_marking_probability())) {
+ packet_info.ecn = EcnMarking::kCe;
+ }
+ return packet_info;
+}
+
+bool DualPi2NetworkQueue::empty() const {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ return classic_queue_.empty() && l4s_queue_.empty();
+}
+
+void DualPi2NetworkQueue::UpdateBaseMarkingProbability(Timestamp time_now) {
+ if (time_now - config_.probability_update_interval <
+ last_probability_update_time_) {
+ return;
+ }
+ last_probability_update_time_ = time_now;
+ TimeDelta sojourn_time =
+ std::max(l4s_queue_delay(time_now), classic_queue_delay(time_now));
+ TimeDelta proportional_update =
+ config_.alpha * (sojourn_time - config_.target_delay);
+ TimeDelta integral_update =
+ config_.beta * (sojourn_time - previous_sojourn_time_);
+ previous_sojourn_time_ = sojourn_time;
+ base_marking_probability_ +=
+ proportional_update.seconds<double>() + integral_update.seconds<double>();
+
+ if (base_marking_probability_ < 0) {
+ base_marking_probability_ = 0;
+ }
+ if (base_marking_probability_ > 1.0) {
+ base_marking_probability_ = 1.0;
+ }
+ RTC_DLOG(LS_VERBOSE) << "base_marking_probability_: "
+ << base_marking_probability_;
+}
+
+bool DualPi2NetworkQueue::ShouldTakeAction(double marking_probability) {
+ if (total_queued_size_ > step_threshold_) {
+ return true;
+ }
+ return distribution_(random_) < marking_probability;
+}
+
+} // namespace webrtc
diff --git a/third_party/libwebrtc/api/test/network_emulation/dual_pi2_network_queue.h b/third_party/libwebrtc/api/test/network_emulation/dual_pi2_network_queue.h
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+#ifndef API_TEST_NETWORK_EMULATION_DUAL_PI2_NETWORK_QUEUE_H_
+#define API_TEST_NETWORK_EMULATION_DUAL_PI2_NETWORK_QUEUE_H_
+
+#include <cstddef>
+#include <memory>
+#include <optional>
+#include <queue>
+#include <random>
+#include <vector>
+
+#include "api/sequence_checker.h"
+#include "api/test/network_emulation/network_queue.h"
+#include "api/test/simulated_network.h"
+#include "api/units/data_rate.h"
+#include "api/units/data_size.h"
+#include "api/units/time_delta.h"
+#include "api/units/timestamp.h"
+
+namespace webrtc {
+
+// DualPi2NetworkQueue is a simplified version of the DualPi2 AQM controller in
+// https://github.com/L4STeam/linux/. Concepts are described in
+// https://datatracker.ietf.org/doc/html/rfc9332.
+// Developed for testing purposes.
+// Note that this implementation does not support the credit-based system
+// (c_protection) from the real implementation and thus a L4S stream can
+// completely starve a classic stream.
+//
+// TODO: bugs.webrtc.org/42225697 - Implement c_protection to better
+// support testing of cross traffic with classic TCP.
+class DualPi2NetworkQueue : public NetworkQueue {
+ public:
+ struct Config {
+ // Target delay for the queue. The queue will try to keep the delay of the
+ // L4S queue below this value.
+ TimeDelta target_delay = TimeDelta::Micros(500);
+ // Link rate puts a cap on how many bytes in total that can be stored in the
+ // queue and still approximately meet the target delay. The cap is
+ // calculated as: 2*target_delay * link_rate and applies to both queues
+ // combined. If more packets than this are enqueued, they will be CE marked
+ // (L4S) or dropped (classic).
+ DataRate link_rate = DataRate::PlusInfinity();
+
+ // These constants are used to calculate the proportional and integral
+ // factors when updating the marking probability.
+ // Values are from the original implementation.
+ double alpha = 0.16;
+ double beta = 3.2;
+ // Coupling factor.
+ int k = 2;
+
+ // How often the base marking probability is updated.
+ TimeDelta probability_update_interval = TimeDelta::Millis(16);
+ int seed = 1;
+ };
+
+ DualPi2NetworkQueue() : DualPi2NetworkQueue(Config()) {}
+ explicit DualPi2NetworkQueue(const Config& config);
+
+ void SetMaxPacketCapacity(size_t max_packet_capacity) override;
+ bool EnqueuePacket(const PacketInFlightInfo& packet_info) override;
+
+ std::optional<PacketInFlightInfo> PeekNextPacket() const override;
+ std::optional<PacketInFlightInfo> DequeuePacket(Timestamp time_now) override;
+ std::vector<PacketInFlightInfo> DequeueDroppedPackets() override {
+ // DualPi2 always tail drop packets.
+ return {};
+ }
+ bool empty() const override;
+
+ // Returns the marking probability of the L4S the l4s queue. Public for
+ // testing.
+ double l4s_marking_probability() const {
+ return base_marking_probability_ * config_.k;
+ }
+ // Returns the drop probability of the classic queue. Public for
+ // testing.
+ double classic_drop_probability() const {
+ return (base_marking_probability_ * base_marking_probability_);
+ }
+
+ private:
+ void UpdateBaseMarkingProbability(Timestamp time_now);
+ bool ShouldTakeAction(double marking_probability);
+ TimeDelta l4s_queue_delay(Timestamp time_now) const {
+ return l4s_queue_.empty() ? TimeDelta::Zero()
+ : time_now - l4s_queue_.front().send_time();
+ }
+
+ TimeDelta classic_queue_delay(Timestamp time_now) const {
+ return classic_queue_.empty()
+ ? TimeDelta::Zero()
+ : time_now - classic_queue_.front().send_time();
+ }
+
+ SequenceChecker sequence_checker_;
+
+ const Config config_;
+ const DataSize step_threshold_;
+
+ std::queue<PacketInFlightInfo> l4s_queue_;
+ std::queue<PacketInFlightInfo> classic_queue_;
+
+ std::mt19937 random_;
+ std::uniform_real_distribution<double> distribution_;
+
+ std::optional<size_t> max_packet_capacity_;
+ DataSize total_queued_size_;
+ double base_marking_probability_ = 0;
+ Timestamp last_probability_update_time_ = Timestamp::MinusInfinity();
+ // The delay of the queue after the last probability update.
+ TimeDelta previous_sojourn_time_ = TimeDelta::Zero();
+};
+
+class DualPi2NetworkQueueFactory : public NetworkQueueFactory {
+ public:
+ explicit DualPi2NetworkQueueFactory(const DualPi2NetworkQueue::Config& config)
+ : config_(config) {}
+
+ std::unique_ptr<NetworkQueue> CreateQueue() override {
+ return std::make_unique<DualPi2NetworkQueue>(config_);
+ }
+
+ private:
+ const DualPi2NetworkQueue::Config config_;
+};
+} // namespace webrtc
+
+#endif // API_TEST_NETWORK_EMULATION_DUAL_PI2_NETWORK_QUEUE_H_
diff --git a/third_party/libwebrtc/api/test/network_emulation/dual_pi2_network_queue_unittest.cc b/third_party/libwebrtc/api/test/network_emulation/dual_pi2_network_queue_unittest.cc
@@ -0,0 +1,281 @@
+/*
+ * 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 "api/test/network_emulation/dual_pi2_network_queue.h"
+
+#include <cstdint>
+#include <limits>
+#include <optional>
+
+#include "api/test/simulated_network.h"
+#include "api/transport/ecn_marking.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 "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+namespace {
+
+using ::testing::AnyOf;
+using ::testing::Field;
+using ::testing::Optional;
+using ::testing::Property;
+
+const DataSize kPacketSize = DataSize::Bytes(1000);
+
+TEST(DualPi2NetworkQueueTest, EnqueuePacket) {
+ DualPi2NetworkQueue queue;
+ Timestamp send_time = Timestamp::Seconds(123);
+ PacketInFlightInfo packet_info(kPacketSize, send_time, /*packet_id=*/1,
+ EcnMarking::kNotEct);
+ EXPECT_TRUE(queue.EnqueuePacket(packet_info));
+}
+
+TEST(DualPi2NetworkQueueTest, PeekNextPacketReturnsNulloptWhenEmpty) {
+ DualPi2NetworkQueue queue;
+ EXPECT_EQ(queue.PeekNextPacket(), std::nullopt);
+}
+
+TEST(DualPi2NetworkQueueTest, PeekNextPacketPrioritizeL4SQueue) {
+ DualPi2NetworkQueue queue;
+ Timestamp send_time = Timestamp::Seconds(123);
+ PacketInFlightInfo packet_info_classic(kPacketSize, send_time,
+ /*packet_id=*/1, EcnMarking::kNotEct);
+ queue.EnqueuePacket(packet_info_classic);
+ PacketInFlightInfo packet_info_l4s_1(kPacketSize, send_time,
+ /*packet_id=*/2, EcnMarking::kEct1);
+ queue.EnqueuePacket(packet_info_l4s_1);
+ PacketInFlightInfo packet_info_l4s_2(kPacketSize, send_time,
+ /*packet_id=*/3, EcnMarking::kEct1);
+ queue.EnqueuePacket(packet_info_l4s_2);
+ std::optional<PacketInFlightInfo> peeked_packet = queue.PeekNextPacket();
+ ASSERT_TRUE(peeked_packet.has_value());
+ EXPECT_EQ(peeked_packet.value().packet_id, 2u);
+}
+
+TEST(DualPi2NetworkQueueTest, DequeuePacketReturnsNulloptWhenEmpty) {
+ DualPi2NetworkQueue queue;
+ EXPECT_EQ(queue.DequeuePacket(Timestamp::Seconds(123)), std::nullopt);
+}
+
+TEST(DualPi2NetworkQueueTest, DequeuePacketPrioritizeL4SQueue) {
+ DualPi2NetworkQueue queue;
+ Timestamp send_time = Timestamp::Seconds(123);
+ PacketInFlightInfo packet_info_classic(kPacketSize, send_time,
+ /*packet_id=*/1, EcnMarking::kNotEct);
+ queue.EnqueuePacket(packet_info_classic);
+ PacketInFlightInfo packet_info_l4s_1(kPacketSize, send_time,
+ /*packet_id=*/2, EcnMarking::kEct1);
+ queue.EnqueuePacket(packet_info_l4s_1);
+ PacketInFlightInfo packet_info_l4s_2(kPacketSize, send_time,
+ /*packet_id=*/3, EcnMarking::kEct1);
+ queue.EnqueuePacket(packet_info_l4s_2);
+ Timestamp dequeue_time = Timestamp::Seconds(123);
+ EXPECT_THAT(
+ queue.DequeuePacket(dequeue_time),
+ Optional(AllOf(Field(&PacketInFlightInfo::packet_id, 2),
+ Field(&PacketInFlightInfo::ecn, EcnMarking::kEct1),
+ Property(&PacketInFlightInfo::send_time, send_time))));
+ EXPECT_THAT(
+ queue.DequeuePacket(dequeue_time),
+ Optional(AllOf(Field(&PacketInFlightInfo::packet_id, 3),
+ Field(&PacketInFlightInfo::ecn, EcnMarking::kEct1),
+ Property(&PacketInFlightInfo::send_time, send_time))));
+ EXPECT_THAT(
+ queue.DequeuePacket(dequeue_time),
+ Optional(AllOf(Field(&PacketInFlightInfo::packet_id, 1),
+ Field(&PacketInFlightInfo::ecn, EcnMarking::kNotEct),
+ Property(&PacketInFlightInfo::send_time, send_time))));
+}
+
+TEST(DualPi2NetworkQueueTest,
+ CeMarkingProbabilityIncreaseIfSojournTimeTooHigh) {
+ DualPi2NetworkQueue queue;
+
+ double marking_probability = 0;
+ Timestamp now = Timestamp::Seconds(123);
+
+ for (int i = 0; i < 4; ++i) {
+ queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i, EcnMarking::kEct1));
+ // Dequeue 1 packet after 17ms, 1ms more than the probability update
+ // interval and more than the target delay.
+ now += TimeDelta::Millis(17);
+ ASSERT_THAT(queue.DequeuePacket(now),
+ Optional(Field(&PacketInFlightInfo::packet_id, i)));
+ EXPECT_GT(queue.l4s_marking_probability(), marking_probability);
+ marking_probability = queue.l4s_marking_probability();
+ EXPECT_GT(marking_probability, 0);
+ EXPECT_LE(marking_probability, std::numeric_limits<uint32_t>::max());
+ }
+}
+
+TEST(DualPi2NetworkQueueTest,
+ CeMarkingProbabilityIncreaseIfSojournTimeTooHighForClassicTraffic) {
+ DualPi2NetworkQueue queue;
+
+ double marking_probability = 0;
+ Timestamp now = Timestamp::Seconds(123);
+
+ for (int i = 0; i < 4; ++i) {
+ queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i, EcnMarking::kEct0));
+ // Dequeue 1 packet after 17ms, 1ms more than the probability update
+ // interval and more than the target delay.
+ now += TimeDelta::Millis(17);
+ ASSERT_THAT(queue.DequeuePacket(now),
+ Optional(Field(&PacketInFlightInfo::packet_id, i)));
+ EXPECT_GT(queue.l4s_marking_probability(), marking_probability);
+ marking_probability = queue.l4s_marking_probability();
+ EXPECT_GT(marking_probability, 0);
+ EXPECT_LE(marking_probability, std::numeric_limits<uint32_t>::max());
+ }
+}
+
+TEST(DualPi2NetworkQueueTest,
+ CeMarkingProbabilityDontIncreaseIfSojournTimeEqualToTarget) {
+ DualPi2NetworkQueue queue;
+ Timestamp now = Timestamp::Seconds(123);
+ int i = 0;
+ double marking_probability_at_equilibrium = -1;
+ while (now < Timestamp::Seconds(123 + 1)) {
+ i = i + 2;
+ queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i, EcnMarking::kEct1));
+ now += TimeDelta::Micros(500);
+ queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i + 1,
+ EcnMarking::kEct1));
+
+ ASSERT_THAT(queue.DequeuePacket(now),
+ Optional(Field(&PacketInFlightInfo::packet_id, i)));
+ now += TimeDelta::Micros(500);
+ ASSERT_THAT(queue.DequeuePacket(now),
+ Optional(Field(&PacketInFlightInfo::packet_id, i + 1)));
+ if (queue.l4s_marking_probability() != 0 &&
+ marking_probability_at_equilibrium == -1) {
+ // Both proportional and integral updates are zero after the second update
+ // since the sojourn time is equal to the target delay.
+ marking_probability_at_equilibrium = queue.l4s_marking_probability();
+ }
+ }
+ EXPECT_EQ(queue.l4s_marking_probability(),
+ marking_probability_at_equilibrium);
+}
+
+TEST(DualPi2NetworkQueueTest, L4SQueueCeMarkIfDelayIsTooHigh) {
+ DualPi2NetworkQueue queue;
+ bool has_seen_ce_marked_packet = false;
+ Timestamp now = Timestamp::Seconds(123);
+ int i = 0;
+ while (now < Timestamp::Seconds(123 + 1)) {
+ now += TimeDelta::Millis(20);
+ // Enqueue 2 L4S packets but only dequeue one. Delay will grow....
+ queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i++,
+ EcnMarking::kEct1));
+ queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i++,
+ EcnMarking::kEct1));
+
+ std::optional<PacketInFlightInfo> dequeued_packet =
+ queue.DequeuePacket(now);
+ ASSERT_TRUE(dequeued_packet.has_value());
+ if (dequeued_packet->ecn == EcnMarking::kCe) {
+ EXPECT_GT(queue.l4s_marking_probability(), 0);
+ has_seen_ce_marked_packet = true;
+ break;
+ }
+ }
+ EXPECT_TRUE(has_seen_ce_marked_packet);
+}
+
+TEST(DualPi2NetworkQueueTest, ClassicQueueDropPacketIfL4SDelayIsTooHigh) {
+ DualPi2NetworkQueue queue;
+ bool has_dropped_classic_packet = false;
+ Timestamp now = Timestamp::Seconds(123);
+ int i = 0;
+ while (now < Timestamp::Seconds(123 + 1)) {
+ now += TimeDelta::Millis(20);
+ // Enqueue 2 L4S packets but only dequeue one. L4S delay will grow....
+ queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i++,
+ EcnMarking::kEct1));
+ queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i++,
+ EcnMarking::kEct1));
+ // Enqueue a classic packet.
+ has_dropped_classic_packet |= queue.EnqueuePacket(
+ PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i++, EcnMarking::kEct0));
+
+ std::optional<PacketInFlightInfo> dequeued_packet =
+ queue.DequeuePacket(now);
+ ASSERT_TRUE(dequeued_packet.has_value());
+ // Dequeued packets are always L4S.
+ EXPECT_THAT(dequeued_packet->ecn,
+ AnyOf(EcnMarking::kEct1, EcnMarking::kCe));
+ }
+ EXPECT_TRUE(has_dropped_classic_packet);
+}
+
+TEST(DualPi2NetworkQueueTest, CeMarksIfStepThresholdIsReached) {
+ DualPi2NetworkQueue::Config config;
+ config.link_rate = DataRate::KilobitsPerSec(100);
+ const DataSize kStepThreshold = config.target_delay * config.link_rate * 2;
+ DualPi2NetworkQueue queue(config);
+ DataSize total_queued_size = DataSize::Zero();
+ Timestamp now = Timestamp::Seconds(123);
+
+ int i = 0;
+ while (total_queued_size < kStepThreshold) {
+ ASSERT_TRUE(queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i++,
+ EcnMarking::kEct1)));
+ total_queued_size += kPacketSize;
+ }
+ std::optional<PacketInFlightInfo> dequeued_packet = queue.DequeuePacket(now);
+ ASSERT_TRUE(dequeued_packet.has_value());
+ EXPECT_EQ(dequeued_packet->ecn, EcnMarking::kCe);
+}
+
+TEST(DualPi2NetworkQueueTest, DropsClassicPacketIfStepThresholdIsReached) {
+ DualPi2NetworkQueue::Config config;
+ config.link_rate = DataRate::KilobitsPerSec(100);
+ const DataSize kStepThreshold = config.target_delay * config.link_rate * 2;
+ DualPi2NetworkQueue queue(config);
+ DataSize total_queued_size = DataSize::Zero();
+ Timestamp now = Timestamp::Seconds(123);
+ int i = 0;
+
+ while (total_queued_size < kStepThreshold) {
+ ASSERT_TRUE(queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i++,
+ EcnMarking::kEct1)));
+ total_queued_size += kPacketSize;
+ }
+
+ EXPECT_FALSE(queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i++,
+ EcnMarking::kEct0)));
+
+ while (total_queued_size < kStepThreshold) {
+ ASSERT_TRUE(queue.EnqueuePacket(PacketInFlightInfo(kPacketSize, now,
+ /*packet_id=*/i++,
+ EcnMarking::kEct1)));
+ total_queued_size += kPacketSize;
+ }
+}
+
+} // namespace
+} // namespace webrtc
diff --git a/third_party/libwebrtc/moz-patch-stack/s0095.patch b/third_party/libwebrtc/moz-patch-stack/s0095.patch
@@ -9,7 +9,7 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/6baf67202c67b27c6
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/api/test/network_emulation/BUILD.gn b/api/test/network_emulation/BUILD.gn
-index de58887b07..665af721e3 100644
+index 681b289c6a..094c962f67 100644
--- a/api/test/network_emulation/BUILD.gn
+++ b/api/test/network_emulation/BUILD.gn
@@ -6,10 +6,10 @@
diff --git a/third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn b/third_party/libwebrtc/test/peer_scenario/tests/BUILD.gn
@@ -30,6 +30,7 @@ if (rtc_include_tests) {
"../../../api:rtp_transceiver_direction",
"../../../api:scoped_refptr",
"../../../api/test/network_emulation",
+ "../../../api/test/network_emulation:network_queue",
"../../../api/transport:ecn_marking",
"../../../api/units:data_rate",
"../../../api/units:time_delta",
diff --git a/third_party/libwebrtc/test/peer_scenario/tests/l4s_test.cc b/third_party/libwebrtc/test/peer_scenario/tests/l4s_test.cc
@@ -17,6 +17,7 @@
#include "api/scoped_refptr.h"
#include "api/stats/rtc_stats_report.h"
#include "api/stats/rtcstats_objects.h"
+#include "api/test/network_emulation/dual_pi2_network_queue.h"
#include "api/test/network_emulation/network_emulation_interfaces.h"
#include "api/transport/ecn_marking.h"
#include "api/units/data_rate.h"
@@ -195,7 +196,7 @@ TEST(L4STest, NegotiateAndUseCcfbIfEnabled) {
EXPECT_EQ(ret_node_feedback_counter.FeedbackAccordingToTransportCc(), 0);
}
-TEST(L4STest, CallerAdaptToLinkCapacityWithoutEcn) {
+TEST(L4STest, CallerAdaptToLinkCapacityOnNetworkWithoutEcn) {
PeerScenario s(*test_info_);
PeerScenarioClient::Config config;
@@ -217,7 +218,56 @@ TEST(L4STest, CallerAdaptToLinkCapacityWithoutEcn) {
auto signaling = s.ConnectSignaling(caller, callee, {caller_to_callee},
{callee_to_caller});
PeerScenarioClient::VideoSendTrackConfig video_conf;
- video_conf.generator.squares_video->framerate = 15;
+ video_conf.generator.squares_video->framerate = 30;
+ video_conf.generator.squares_video->width = 640;
+ video_conf.generator.squares_video->height = 360;
+ caller->CreateVideo("VIDEO_1", video_conf);
+
+ signaling.StartIceSignaling();
+ std::atomic<bool> offer_exchange_done(false);
+ signaling.NegotiateSdp([&](const SessionDescriptionInterface& answer) {
+ offer_exchange_done = true;
+ });
+ s.WaitAndProcess(&offer_exchange_done);
+ s.ProcessMessages(TimeDelta::Seconds(3));
+ DataRate available_bwe =
+ GetAvailableSendBitrate(GetStatsAndProcess(s, caller));
+ EXPECT_GT(available_bwe.kbps(), 450);
+ EXPECT_LT(available_bwe.kbps(), 610);
+}
+
+// Note - this test only test that the
+// caller adapt to the link capacity. It does not test that the caller uses ECN
+// to adapt even though the network can mark packets with CE.
+// TODO: bugs.webrtc.org/42225697 - actually test that the caller adapt to ECN
+// marking.
+TEST(L4STest, CallerAdaptToLinkCapacityOnNetworkWithEcn) {
+ PeerScenario s(*test_info_);
+ PeerScenarioClient::Config config;
+ config.field_trials.Set("WebRTC-RFC8888CongestionControlFeedback", "Enabled");
+
+ PeerScenarioClient* caller = s.CreateClient(config);
+ PeerScenarioClient* callee = s.CreateClient(config);
+
+ DualPi2NetworkQueueFactory dual_pi_factory({});
+ auto caller_to_callee = s.net()
+ ->NodeBuilder()
+ .queue_factory(dual_pi_factory)
+ .capacity(DataRate::KilobitsPerSec(600))
+ .Build()
+ .node;
+ auto callee_to_caller = s.net()->NodeBuilder().Build().node;
+ s.net()->CreateRoute(caller->endpoint(), {caller_to_callee},
+ callee->endpoint());
+ s.net()->CreateRoute(callee->endpoint(), {callee_to_caller},
+ caller->endpoint());
+
+ auto signaling = s.ConnectSignaling(caller, callee, {caller_to_callee},
+ {callee_to_caller});
+ PeerScenarioClient::VideoSendTrackConfig video_conf;
+ video_conf.generator.squares_video->framerate = 30;
+ video_conf.generator.squares_video->width = 640;
+ video_conf.generator.squares_video->height = 360;
caller->CreateVideo("VIDEO_1", video_conf);
signaling.StartIceSignaling();
@@ -229,7 +279,7 @@ TEST(L4STest, CallerAdaptToLinkCapacityWithoutEcn) {
s.ProcessMessages(TimeDelta::Seconds(3));
DataRate available_bwe =
GetAvailableSendBitrate(GetStatsAndProcess(s, caller));
- EXPECT_GT(available_bwe.kbps(), 500);
+ EXPECT_GT(available_bwe.kbps(), 450);
EXPECT_LT(available_bwe.kbps(), 610);
}