Source code
Revision control
Copy as Markdown
Other Tools
/*
* 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