| /* | |
| * 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 "absl/functional/any_invocable.h" | |
| #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, | |
| absl::AnyInvocable<void(const SentPacket&)> sent_packet_cb, | |
| TimeDelta max_time) { | |
| Timestamp end_time = clock.CurrentTime() + max_time; | |
| while (clock.CurrentTime() < end_time) { | |
| std::optional<SentPacket> sent_packet = | |
| MaybeSendPackets(clock.CurrentTime(), send_rate); | |
| ProcessNetwork(clock.CurrentTime()); | |
| if (sent_packet_cb && sent_packet.has_value()) { | |
| sent_packet_cb(*sent_packet); | |
| } | |
| std::optional<TransportPacketsFeedback> feedback = | |
| MaybeSendFeedback(clock.CurrentTime()); | |
| if (feedback.has_value()) { | |
| return *feedback; | |
| } | |
| if (!sent_packet.has_value() && !feedback.has_value()) { | |
| clock.AdvanceTime(TimeDelta::Millis(1)); | |
| } | |
| } | |
| ADD_FAILURE() << "No feedback received after " << max_time; | |
| return TransportPacketsFeedback(); | |
| } | |
| std::optional<SentPacket> 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. | |
| if (send_budget_ < packet_size_) { | |
| return std::nullopt; | |
| } | |
| 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 "; | |
| } | |
| DataSize bytes_in_flight = DataSize::Zero(); | |
| for (const PacketInFlightInfo& in_flight : packets_in_flight_) { | |
| bytes_in_flight += in_flight.packet_size(); | |
| } | |
| SentPacket sent_packet; | |
| sent_packet.send_time = packet_info.send_time(); | |
| sent_packet.size = packet_info.packet_size(); | |
| sent_packet.data_in_flight = bytes_in_flight; | |
| return sent_packet; | |
| } | |
| 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.sequence_number = | |
| packets_in_flight_.front().packet_id; | |
| packet_result.sent_packet.size = packets_in_flight_.front().packet_size(); | |
| packet_result.receive_time = | |
| Timestamp::Micros(delivery_info.receive_time_us); | |
| packet_result.arrival_time_offset = | |
| time - packet_result.receive_time - one_way_delay_; | |
| packet_result.ecn = delivery_info.ecn; | |
| packets_in_flight_.pop_front(); | |
| 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(); | |
| } | |
| feedback.feedback_time = time; | |
| last_feedback_time_ = time; | |
| return feedback; | |
| } | |
| } // namespace webrtc |