| // 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 "quic/test_tools/packet_dropping_test_writer.h" | 
 |  | 
 | #include "quic/core/quic_epoll_connection_helper.h" | 
 | #include "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 |