| /* |
| * 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 |