This CL makes it possible to change transport routes while running a scenario based test. To make this possible in a consistent manner, the scenario test framework is modified to only allow shared transport for all streams between two CallClients. This is what typically is done in practice and it is quite complex to even reason about the implications of using mixed transports for a single call. Bug: webrtc:9718 Change-Id: Ib836928feed98aa2bbbe0295e158157a6518348b Reviewed-on: https://webrtc-review.googlesource.com/c/107200 Commit-Queue: Sebastian Jansson <srte@webrtc.org> Reviewed-by: Björn Terelius <terelius@webrtc.org> Cr-Commit-Position: refs/heads/master@{#25287}
282 lines
10 KiB
C++
282 lines
10 KiB
C++
/*
|
|
* 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 "test/scenario/network_node.h"
|
|
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
#include "rtc_base/numerics/safe_minmax.h"
|
|
|
|
namespace webrtc {
|
|
namespace test {
|
|
namespace {
|
|
SimulatedNetwork::Config CreateSimulationConfig(NetworkNodeConfig config) {
|
|
SimulatedNetwork::Config sim_config;
|
|
sim_config.link_capacity_kbps = config.simulation.bandwidth.kbps_or(0);
|
|
sim_config.loss_percent = config.simulation.loss_rate * 100;
|
|
sim_config.queue_delay_ms = config.simulation.delay.ms();
|
|
sim_config.delay_standard_deviation_ms = config.simulation.delay_std_dev.ms();
|
|
return sim_config;
|
|
}
|
|
} // namespace
|
|
|
|
bool NullReceiver::TryDeliverPacket(rtc::CopyOnWriteBuffer packet,
|
|
uint64_t receiver,
|
|
Timestamp at_time) {
|
|
return true;
|
|
}
|
|
|
|
ActionReceiver::ActionReceiver(std::function<void()> action)
|
|
: action_(action) {}
|
|
|
|
bool ActionReceiver::TryDeliverPacket(rtc::CopyOnWriteBuffer packet,
|
|
uint64_t receiver,
|
|
Timestamp at_time) {
|
|
action_();
|
|
return true;
|
|
}
|
|
|
|
NetworkNode::~NetworkNode() = default;
|
|
|
|
NetworkNode::NetworkNode(NetworkNodeConfig config,
|
|
std::unique_ptr<NetworkBehaviorInterface> behavior)
|
|
: packet_overhead_(config.packet_overhead.bytes()),
|
|
behavior_(std::move(behavior)) {}
|
|
|
|
void NetworkNode::SetRoute(uint64_t receiver, NetworkReceiverInterface* node) {
|
|
rtc::CritScope crit(&crit_sect_);
|
|
routing_[receiver] = node;
|
|
}
|
|
|
|
void NetworkNode::ClearRoute(uint64_t receiver_id) {
|
|
rtc::CritScope crit(&crit_sect_);
|
|
auto it = routing_.find(receiver_id);
|
|
routing_.erase(it);
|
|
}
|
|
|
|
bool NetworkNode::TryDeliverPacket(rtc::CopyOnWriteBuffer packet,
|
|
uint64_t receiver,
|
|
Timestamp at_time) {
|
|
rtc::CritScope crit(&crit_sect_);
|
|
if (routing_.find(receiver) == routing_.end())
|
|
return false;
|
|
uint64_t packet_id = next_packet_id_++;
|
|
bool sent = behavior_->EnqueuePacket(PacketInFlightInfo(
|
|
packet.size() + packet_overhead_, at_time.us(), packet_id));
|
|
if (sent) {
|
|
packets_.emplace_back(StoredPacket{packet, receiver, packet_id, false});
|
|
}
|
|
return sent;
|
|
}
|
|
|
|
void NetworkNode::Process(Timestamp at_time) {
|
|
std::vector<PacketDeliveryInfo> delivery_infos;
|
|
{
|
|
rtc::CritScope crit(&crit_sect_);
|
|
absl::optional<int64_t> delivery_us = behavior_->NextDeliveryTimeUs();
|
|
if (delivery_us && *delivery_us > at_time.us())
|
|
return;
|
|
|
|
delivery_infos = behavior_->DequeueDeliverablePackets(at_time.us());
|
|
}
|
|
for (PacketDeliveryInfo& delivery_info : delivery_infos) {
|
|
StoredPacket* packet = nullptr;
|
|
NetworkReceiverInterface* receiver = nullptr;
|
|
{
|
|
rtc::CritScope crit(&crit_sect_);
|
|
for (StoredPacket& stored_packet : packets_) {
|
|
if (stored_packet.id == delivery_info.packet_id) {
|
|
packet = &stored_packet;
|
|
break;
|
|
}
|
|
}
|
|
RTC_CHECK(packet);
|
|
RTC_DCHECK(!packet->removed);
|
|
receiver = routing_[packet->receiver_id];
|
|
packet->removed = true;
|
|
}
|
|
// We don't want to keep the lock here. Otherwise we would get a deadlock if
|
|
// the receiver tries to push a new packet.
|
|
receiver->TryDeliverPacket(packet->packet_data, packet->receiver_id,
|
|
at_time);
|
|
{
|
|
rtc::CritScope crit(&crit_sect_);
|
|
while (!packets_.empty() && packets_.front().removed) {
|
|
packets_.pop_front();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void NetworkNode::Route(int64_t receiver_id,
|
|
std::vector<NetworkNode*> nodes,
|
|
NetworkReceiverInterface* receiver) {
|
|
RTC_CHECK(!nodes.empty());
|
|
for (size_t i = 0; i + 1 < nodes.size(); ++i)
|
|
nodes[i]->SetRoute(receiver_id, nodes[i + 1]);
|
|
nodes.back()->SetRoute(receiver_id, receiver);
|
|
}
|
|
|
|
void NetworkNode::ClearRoute(int64_t receiver_id,
|
|
std::vector<NetworkNode*> nodes) {
|
|
for (NetworkNode* node : nodes)
|
|
node->ClearRoute(receiver_id);
|
|
}
|
|
|
|
std::unique_ptr<SimulationNode> SimulationNode::Create(
|
|
NetworkNodeConfig config) {
|
|
RTC_DCHECK(config.mode == NetworkNodeConfig::TrafficMode::kSimulation);
|
|
SimulatedNetwork::Config sim_config = CreateSimulationConfig(config);
|
|
auto network = absl::make_unique<SimulatedNetwork>(sim_config);
|
|
SimulatedNetwork* simulation_ptr = network.get();
|
|
return std::unique_ptr<SimulationNode>(
|
|
new SimulationNode(config, std::move(network), simulation_ptr));
|
|
}
|
|
|
|
void SimulationNode::UpdateConfig(
|
|
std::function<void(NetworkNodeConfig*)> modifier) {
|
|
modifier(&config_);
|
|
SimulatedNetwork::Config sim_config = CreateSimulationConfig(config_);
|
|
simulated_network_->SetConfig(sim_config);
|
|
}
|
|
|
|
void SimulationNode::PauseTransmissionUntil(Timestamp until) {
|
|
simulated_network_->PauseTransmissionUntil(until.us());
|
|
}
|
|
|
|
ColumnPrinter SimulationNode::ConfigPrinter() const {
|
|
return ColumnPrinter::Lambda("propagation_delay capacity loss_rate",
|
|
[this](rtc::SimpleStringBuilder& sb) {
|
|
sb.AppendFormat(
|
|
"%.3lf %.0lf %.2lf",
|
|
config_.simulation.delay.seconds<double>(),
|
|
config_.simulation.bandwidth.bps() / 8.0,
|
|
config_.simulation.loss_rate);
|
|
});
|
|
}
|
|
|
|
SimulationNode::SimulationNode(
|
|
NetworkNodeConfig config,
|
|
std::unique_ptr<NetworkBehaviorInterface> behavior,
|
|
SimulatedNetwork* simulation)
|
|
: NetworkNode(config, std::move(behavior)),
|
|
simulated_network_(simulation),
|
|
config_(config) {}
|
|
|
|
NetworkNodeTransport::NetworkNodeTransport(const Clock* sender_clock,
|
|
Call* sender_call)
|
|
: sender_clock_(sender_clock), sender_call_(sender_call) {}
|
|
|
|
NetworkNodeTransport::~NetworkNodeTransport() = default;
|
|
|
|
bool NetworkNodeTransport::SendRtp(const uint8_t* packet,
|
|
size_t length,
|
|
const PacketOptions& options) {
|
|
int64_t send_time_ms = sender_clock_->TimeInMilliseconds();
|
|
rtc::SentPacket sent_packet;
|
|
sent_packet.packet_id = options.packet_id;
|
|
sent_packet.info.included_in_feedback = options.included_in_feedback;
|
|
sent_packet.info.included_in_allocation = options.included_in_allocation;
|
|
sent_packet.send_time_ms = send_time_ms;
|
|
sent_packet.info.packet_size_bytes = length;
|
|
sent_packet.info.packet_type = rtc::PacketType::kData;
|
|
sender_call_->OnSentPacket(sent_packet);
|
|
|
|
Timestamp send_time = Timestamp::ms(send_time_ms);
|
|
rtc::CritScope crit(&crit_sect_);
|
|
if (!send_net_)
|
|
return false;
|
|
rtc::CopyOnWriteBuffer buffer(packet, length,
|
|
length + packet_overhead_.bytes());
|
|
buffer.SetSize(length + packet_overhead_.bytes());
|
|
return send_net_->TryDeliverPacket(buffer, receiver_id_, send_time);
|
|
}
|
|
|
|
bool NetworkNodeTransport::SendRtcp(const uint8_t* packet, size_t length) {
|
|
rtc::CopyOnWriteBuffer buffer(packet, length);
|
|
Timestamp send_time = Timestamp::ms(sender_clock_->TimeInMilliseconds());
|
|
rtc::CritScope crit(&crit_sect_);
|
|
buffer.SetSize(length + packet_overhead_.bytes());
|
|
if (!send_net_)
|
|
return false;
|
|
return send_net_->TryDeliverPacket(buffer, receiver_id_, send_time);
|
|
}
|
|
|
|
void NetworkNodeTransport::Connect(NetworkNode* send_node,
|
|
uint64_t receiver_id,
|
|
DataSize packet_overhead) {
|
|
rtc::CritScope crit(&crit_sect_);
|
|
send_net_ = send_node;
|
|
receiver_id_ = receiver_id;
|
|
packet_overhead_ = packet_overhead;
|
|
|
|
rtc::NetworkRoute route;
|
|
route.connected = true;
|
|
route.local_network_id = receiver_id;
|
|
route.remote_network_id = receiver_id;
|
|
std::string transport_name = "dummy";
|
|
sender_call_->GetTransportControllerSend()->OnNetworkRouteChanged(
|
|
transport_name, route);
|
|
}
|
|
|
|
CrossTrafficSource::CrossTrafficSource(NetworkReceiverInterface* target,
|
|
uint64_t receiver_id,
|
|
CrossTrafficConfig config)
|
|
: target_(target),
|
|
receiver_id_(receiver_id),
|
|
config_(config),
|
|
random_(config.random_seed) {}
|
|
|
|
CrossTrafficSource::~CrossTrafficSource() = default;
|
|
|
|
DataRate CrossTrafficSource::TrafficRate() const {
|
|
return config_.peak_rate * intensity_;
|
|
}
|
|
|
|
void CrossTrafficSource::Process(Timestamp at_time, TimeDelta delta) {
|
|
time_since_update_ += delta;
|
|
if (config_.mode == CrossTrafficConfig::Mode::kRandomWalk) {
|
|
if (time_since_update_ >= config_.random_walk.update_interval) {
|
|
intensity_ += random_.Gaussian(config_.random_walk.bias,
|
|
config_.random_walk.variance) *
|
|
time_since_update_.seconds<double>();
|
|
intensity_ = rtc::SafeClamp(intensity_, 0.0, 1.0);
|
|
time_since_update_ = TimeDelta::Zero();
|
|
}
|
|
} else if (config_.mode == CrossTrafficConfig::Mode::kPulsedPeaks) {
|
|
if (intensity_ == 0 && time_since_update_ >= config_.pulsed.hold_duration) {
|
|
intensity_ = 1;
|
|
time_since_update_ = TimeDelta::Zero();
|
|
} else if (intensity_ == 1 &&
|
|
time_since_update_ >= config_.pulsed.send_duration) {
|
|
intensity_ = 0;
|
|
time_since_update_ = TimeDelta::Zero();
|
|
}
|
|
}
|
|
pending_size_ += TrafficRate() * delta;
|
|
if (pending_size_ > config_.min_packet_size) {
|
|
target_->TryDeliverPacket(rtc::CopyOnWriteBuffer(pending_size_.bytes()),
|
|
receiver_id_, at_time);
|
|
pending_size_ = DataSize::Zero();
|
|
}
|
|
}
|
|
|
|
ColumnPrinter CrossTrafficSource::StatsPrinter() {
|
|
return ColumnPrinter::Lambda("cross_traffic_rate",
|
|
[this](rtc::SimpleStringBuilder& sb) {
|
|
sb.AppendFormat("%.0lf",
|
|
TrafficRate().bps() / 8.0);
|
|
},
|
|
32);
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace webrtc
|