| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "quiche/quic/test_tools/packet_dropping_test_writer.h" |
| |
| #include "quiche/quic/platform/api/quic_logging.h" |
| |
| namespace quic { |
| namespace test { |
| |
| // Every dropped packet must be followed by this number of succesfully written |
| // packets. This is to avoid flaky test failures and timeouts, for example, in |
| // case both the client and the server drop every other packet (which is |
| // statistically possible even if drop percentage is less than 50%). |
| const int32_t kMinSuccesfulWritesAfterPacketLoss = 2; |
| |
| // An alarm that is scheduled if a blocked socket is simulated to indicate |
| // it's writable again. |
| class WriteUnblockedAlarm : public QuicAlarm::DelegateWithoutContext { |
| public: |
| explicit WriteUnblockedAlarm(PacketDroppingTestWriter* writer) |
| : writer_(writer) {} |
| |
| void OnAlarm() override { |
| QUIC_DLOG(INFO) << "Unblocking socket."; |
| writer_->OnCanWrite(); |
| } |
| |
| private: |
| PacketDroppingTestWriter* writer_; |
| }; |
| |
| // An alarm that is scheduled every time a new packet is to be written at a |
| // later point. |
| class DelayAlarm : public QuicAlarm::DelegateWithoutContext { |
| public: |
| explicit DelayAlarm(PacketDroppingTestWriter* writer) : writer_(writer) {} |
| |
| void OnAlarm() override { |
| QuicTime new_deadline = writer_->ReleaseOldPackets(); |
| if (new_deadline.IsInitialized()) { |
| writer_->SetDelayAlarm(new_deadline); |
| } |
| } |
| |
| private: |
| PacketDroppingTestWriter* writer_; |
| }; |
| |
| PacketDroppingTestWriter::PacketDroppingTestWriter() |
| : clock_(nullptr), |
| cur_buffer_size_(0), |
| num_calls_to_write_(0), |
| // Do not require any number of successful writes before the first dropped |
| // packet. |
| num_consecutive_succesful_writes_(kMinSuccesfulWritesAfterPacketLoss), |
| fake_packet_loss_percentage_(0), |
| fake_drop_first_n_packets_(0), |
| fake_blocked_socket_percentage_(0), |
| fake_packet_reorder_percentage_(0), |
| fake_packet_delay_(QuicTime::Delta::Zero()), |
| fake_bandwidth_(QuicBandwidth::Zero()), |
| buffer_size_(0) { |
| uint64_t seed = QuicRandom::GetInstance()->RandUint64(); |
| QUIC_LOG(INFO) << "Seeding packet loss with " << seed; |
| simple_random_.set_seed(seed); |
| } |
| |
| PacketDroppingTestWriter::~PacketDroppingTestWriter() { |
| if (write_unblocked_alarm_ != nullptr) { |
| write_unblocked_alarm_->PermanentCancel(); |
| } |
| if (delay_alarm_ != nullptr) { |
| delay_alarm_->PermanentCancel(); |
| } |
| } |
| |
| void PacketDroppingTestWriter::Initialize( |
| QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory, |
| std::unique_ptr<Delegate> on_can_write) { |
| clock_ = helper->GetClock(); |
| write_unblocked_alarm_.reset( |
| alarm_factory->CreateAlarm(new WriteUnblockedAlarm(this))); |
| delay_alarm_.reset(alarm_factory->CreateAlarm(new DelayAlarm(this))); |
| on_can_write_ = std::move(on_can_write); |
| } |
| |
| WriteResult PacketDroppingTestWriter::WritePacket( |
| const char* buffer, size_t buf_len, const QuicIpAddress& self_address, |
| const QuicSocketAddress& peer_address, PerPacketOptions* options) { |
| ++num_calls_to_write_; |
| ReleaseOldPackets(); |
| |
| QuicWriterMutexLock lock(&config_mutex_); |
| if (fake_drop_first_n_packets_ > 0 && |
| num_calls_to_write_ <= |
| static_cast<uint64_t>(fake_drop_first_n_packets_)) { |
| QUIC_DVLOG(1) << "Dropping first " << fake_drop_first_n_packets_ |
| << " packets (packet number " << num_calls_to_write_ << ")"; |
| num_consecutive_succesful_writes_ = 0; |
| return WriteResult(WRITE_STATUS_OK, buf_len); |
| } |
| |
| // Drop every packet at 100%, otherwise always succeed for at least |
| // kMinSuccesfulWritesAfterPacketLoss packets between two dropped ones. |
| if (fake_packet_loss_percentage_ == 100 || |
| (fake_packet_loss_percentage_ > 0 && |
| num_consecutive_succesful_writes_ >= |
| kMinSuccesfulWritesAfterPacketLoss && |
| (simple_random_.RandUint64() % 100 < |
| static_cast<uint64_t>(fake_packet_loss_percentage_)))) { |
| QUIC_DVLOG(1) << "Dropping packet " << num_calls_to_write_; |
| num_consecutive_succesful_writes_ = 0; |
| return WriteResult(WRITE_STATUS_OK, buf_len); |
| } else { |
| ++num_consecutive_succesful_writes_; |
| } |
| |
| if (fake_blocked_socket_percentage_ > 0 && |
| simple_random_.RandUint64() % 100 < |
| static_cast<uint64_t>(fake_blocked_socket_percentage_)) { |
| QUICHE_CHECK(on_can_write_ != nullptr); |
| QUIC_DVLOG(1) << "Blocking socket for packet " << num_calls_to_write_; |
| if (!write_unblocked_alarm_->IsSet()) { |
| // Set the alarm to fire immediately. |
| write_unblocked_alarm_->Set(clock_->ApproximateNow()); |
| } |
| |
| // Dropping this packet on retry could result in PTO timeout, |
| // make sure to avoid this. |
| num_consecutive_succesful_writes_ = 0; |
| |
| return WriteResult(WRITE_STATUS_BLOCKED, EAGAIN); |
| } |
| |
| if (!fake_packet_delay_.IsZero() || !fake_bandwidth_.IsZero()) { |
| if (buffer_size_ > 0 && buf_len + cur_buffer_size_ > buffer_size_) { |
| // Drop packets which do not fit into the buffer. |
| QUIC_DVLOG(1) << "Dropping packet because the buffer is full."; |
| return WriteResult(WRITE_STATUS_OK, buf_len); |
| } |
| |
| // Queue it to be sent. |
| QuicTime send_time = clock_->ApproximateNow() + fake_packet_delay_; |
| if (!fake_bandwidth_.IsZero()) { |
| // Calculate a time the bandwidth limit would impose. |
| QuicTime::Delta bandwidth_delay = QuicTime::Delta::FromMicroseconds( |
| (buf_len * kNumMicrosPerSecond) / fake_bandwidth_.ToBytesPerSecond()); |
| send_time = delayed_packets_.empty() |
| ? send_time + bandwidth_delay |
| : delayed_packets_.back().send_time + bandwidth_delay; |
| } |
| std::unique_ptr<PerPacketOptions> delayed_options; |
| if (options != nullptr) { |
| delayed_options = options->Clone(); |
| } |
| delayed_packets_.push_back( |
| DelayedWrite(buffer, buf_len, self_address, peer_address, |
| std::move(delayed_options), send_time)); |
| cur_buffer_size_ += buf_len; |
| |
| // Set the alarm if it's not yet set. |
| if (!delay_alarm_->IsSet()) { |
| delay_alarm_->Set(send_time); |
| } |
| |
| return WriteResult(WRITE_STATUS_OK, buf_len); |
| } |
| |
| return QuicPacketWriterWrapper::WritePacket(buffer, buf_len, self_address, |
| peer_address, options); |
| } |
| |
| bool PacketDroppingTestWriter::IsWriteBlocked() const { |
| if (write_unblocked_alarm_ != nullptr && write_unblocked_alarm_->IsSet()) { |
| return true; |
| } |
| return QuicPacketWriterWrapper::IsWriteBlocked(); |
| } |
| |
| void PacketDroppingTestWriter::SetWritable() { |
| if (write_unblocked_alarm_ != nullptr && write_unblocked_alarm_->IsSet()) { |
| write_unblocked_alarm_->Cancel(); |
| } |
| QuicPacketWriterWrapper::SetWritable(); |
| } |
| |
| QuicTime PacketDroppingTestWriter::ReleaseNextPacket() { |
| if (delayed_packets_.empty()) { |
| return QuicTime::Zero(); |
| } |
| QuicReaderMutexLock lock(&config_mutex_); |
| auto iter = delayed_packets_.begin(); |
| // Determine if we should re-order. |
| if (delayed_packets_.size() > 1 && fake_packet_reorder_percentage_ > 0 && |
| simple_random_.RandUint64() % 100 < |
| static_cast<uint64_t>(fake_packet_reorder_percentage_)) { |
| QUIC_DLOG(INFO) << "Reordering packets."; |
| ++iter; |
| // Swap the send times when re-ordering packets. |
| delayed_packets_.begin()->send_time = iter->send_time; |
| } |
| |
| QUIC_DVLOG(1) << "Releasing packet. " << (delayed_packets_.size() - 1) |
| << " remaining."; |
| // Grab the next one off the queue and send it. |
| QuicPacketWriterWrapper::WritePacket( |
| iter->buffer.data(), iter->buffer.length(), iter->self_address, |
| iter->peer_address, iter->options.get()); |
| QUICHE_DCHECK_GE(cur_buffer_size_, iter->buffer.length()); |
| cur_buffer_size_ -= iter->buffer.length(); |
| delayed_packets_.erase(iter); |
| |
| // If there are others, find the time for the next to be sent. |
| if (delayed_packets_.empty()) { |
| return QuicTime::Zero(); |
| } |
| return delayed_packets_.begin()->send_time; |
| } |
| |
| QuicTime PacketDroppingTestWriter::ReleaseOldPackets() { |
| while (!delayed_packets_.empty()) { |
| QuicTime next_send_time = delayed_packets_.front().send_time; |
| if (next_send_time > clock_->Now()) { |
| return next_send_time; |
| } |
| ReleaseNextPacket(); |
| } |
| return QuicTime::Zero(); |
| } |
| |
| void PacketDroppingTestWriter::SetDelayAlarm(QuicTime new_deadline) { |
| delay_alarm_->Set(new_deadline); |
| } |
| |
| void PacketDroppingTestWriter::OnCanWrite() { on_can_write_->OnCanWrite(); } |
| |
| PacketDroppingTestWriter::DelayedWrite::DelayedWrite( |
| const char* buffer, size_t buf_len, const QuicIpAddress& self_address, |
| const QuicSocketAddress& peer_address, |
| std::unique_ptr<PerPacketOptions> options, QuicTime send_time) |
| : buffer(buffer, buf_len), |
| self_address(self_address), |
| peer_address(peer_address), |
| options(std::move(options)), |
| send_time(send_time) {} |
| |
| PacketDroppingTestWriter::DelayedWrite::~DelayedWrite() = default; |
| |
| } // namespace test |
| } // namespace quic |