blob: d7a429d2032912cf5c1003e4fed16e436374da0b [file] [edit]
/*
* 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;
constexpr 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