/* * Copyright 2018 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 "call/simulated_network.h" #include #include #include #include "api/units/data_rate.h" #include "api/units/data_size.h" #include "api/units/time_delta.h" #include "rtc_base/checks.h" namespace webrtc { SimulatedNetwork::SimulatedNetwork(SimulatedNetwork::Config config, uint64_t random_seed) : random_(random_seed), bursting_(false) { SetConfig(config); } SimulatedNetwork::~SimulatedNetwork() = default; void SimulatedNetwork::SetConfig(const SimulatedNetwork::Config& config) { rtc::CritScope crit(&config_lock_); if (config_.link_capacity_kbps != config.link_capacity_kbps) { reset_capacity_delay_error_ = true; } config_ = config; // Shallow copy of the struct. double prob_loss = config.loss_percent / 100.0; if (config_.avg_burst_loss_length == -1) { // Uniform loss prob_loss_bursting_ = prob_loss; prob_start_bursting_ = prob_loss; } else { // Lose packets according to a gilbert-elliot model. int avg_burst_loss_length = config.avg_burst_loss_length; int min_avg_burst_loss_length = std::ceil(prob_loss / (1 - prob_loss)); RTC_CHECK_GT(avg_burst_loss_length, min_avg_burst_loss_length) << "For a total packet loss of " << config.loss_percent << "%% then" << " avg_burst_loss_length must be " << min_avg_burst_loss_length + 1 << " or higher."; prob_loss_bursting_ = (1.0 - 1.0 / avg_burst_loss_length); prob_start_bursting_ = prob_loss / (1 - prob_loss) / avg_burst_loss_length; } } void SimulatedNetwork::PauseTransmissionUntil(int64_t until_us) { rtc::CritScope crit(&config_lock_); pause_transmission_until_us_ = until_us; } bool SimulatedNetwork::EnqueuePacket(PacketInFlightInfo packet) { Config config; { rtc::CritScope crit(&config_lock_); config = config_; } rtc::CritScope crit(&process_lock_); if (config.queue_length_packets > 0 && capacity_link_.size() >= config.queue_length_packets) { // Too many packet on the link, drop this one. return false; } int64_t network_start_time_us = packet.send_time_us; { rtc::CritScope crit(&config_lock_); if (reset_capacity_delay_error_) { capacity_delay_error_bytes_ = 0; reset_capacity_delay_error_ = false; } if (pause_transmission_until_us_) { network_start_time_us = std::max(network_start_time_us, *pause_transmission_until_us_); pause_transmission_until_us_.reset(); } } // Delay introduced by the link capacity. TimeDelta capacity_delay = TimeDelta::Zero(); if (config.link_capacity_kbps > 0) { const DataRate link_capacity = DataRate::kbps(config.link_capacity_kbps); int64_t compensated_size = static_cast(packet.size) + capacity_delay_error_bytes_; capacity_delay = DataSize::bytes(compensated_size) / link_capacity; capacity_delay_error_bytes_ += packet.size - (capacity_delay * link_capacity).bytes(); } // Check if there already are packets on the link and change network start // time forward if there is. if (!capacity_link_.empty() && network_start_time_us < capacity_link_.back().arrival_time_us) network_start_time_us = capacity_link_.back().arrival_time_us; int64_t arrival_time_us = network_start_time_us + capacity_delay.us(); capacity_link_.push({packet, arrival_time_us}); return true; } absl::optional SimulatedNetwork::NextDeliveryTimeUs() const { rtc::CritScope crit(&process_lock_); if (!delay_link_.empty()) return delay_link_.begin()->arrival_time_us; return absl::nullopt; } std::vector SimulatedNetwork::DequeueDeliverablePackets( int64_t receive_time_us) { int64_t time_now_us = receive_time_us; Config config; double prob_loss_bursting; double prob_start_bursting; { rtc::CritScope crit(&config_lock_); config = config_; prob_loss_bursting = prob_loss_bursting_; prob_start_bursting = prob_start_bursting_; } { rtc::CritScope crit(&process_lock_); // Check the capacity link first. if (!capacity_link_.empty()) { int64_t last_arrival_time_us = delay_link_.empty() ? -1 : delay_link_.back().arrival_time_us; bool needs_sort = false; while (!capacity_link_.empty() && time_now_us >= capacity_link_.front().arrival_time_us) { // Time to get this packet. PacketInfo packet = std::move(capacity_link_.front()); capacity_link_.pop(); // Drop packets at an average rate of |config_.loss_percent| with // and average loss burst length of |config_.avg_burst_loss_length|. if ((bursting_ && random_.Rand() < prob_loss_bursting) || (!bursting_ && random_.Rand() < prob_start_bursting)) { bursting_ = true; continue; } else { bursting_ = false; } int64_t arrival_time_jitter_us = std::max( random_.Gaussian(config.queue_delay_ms * 1000, config.delay_standard_deviation_ms * 1000), 0.0); // If reordering is not allowed then adjust arrival_time_jitter // to make sure all packets are sent in order. if (!config.allow_reordering && !delay_link_.empty() && packet.arrival_time_us + arrival_time_jitter_us < last_arrival_time_us) { arrival_time_jitter_us = last_arrival_time_us - packet.arrival_time_us; } packet.arrival_time_us += arrival_time_jitter_us; if (packet.arrival_time_us >= last_arrival_time_us) { last_arrival_time_us = packet.arrival_time_us; } else { needs_sort = true; } delay_link_.emplace_back(std::move(packet)); } if (needs_sort) { // Packet(s) arrived out of order, make sure list is sorted. std::sort(delay_link_.begin(), delay_link_.end(), [](const PacketInfo& p1, const PacketInfo& p2) { return p1.arrival_time_us < p2.arrival_time_us; }); } } std::vector packets_to_deliver; // Check the extra delay queue. while (!delay_link_.empty() && time_now_us >= delay_link_.front().arrival_time_us) { PacketInfo packet_info = delay_link_.front(); packets_to_deliver.emplace_back( PacketDeliveryInfo(packet_info.packet, packet_info.arrival_time_us)); delay_link_.pop_front(); } return packets_to_deliver; } } } // namespace webrtc