Create a PacketFilter for random loss and use it in QuartcBidiTest.

This sets up a second benchmark in QuartcBidiTest which runs at 300 kbps, with
200 ms propagation delay, but also includes 2% random packet loss.

Random loss is one of the features required to bring Simulator-based
BidiTestRunner tests in line with higher-level Quartc quality tests.  Those
tests typically use 2-8% random loss, depending on the network profile.

gfe-relnote: n/a (Quartc tests only)
PiperOrigin-RevId: 244062413
Change-Id: Ic7cfc9b7e8c2b5339c8e1a25d7583b8cf156c870
diff --git a/quic/quartc/test/bidi_test_runner.cc b/quic/quartc/test/bidi_test_runner.cc
index ebd6d33..b185db6 100644
--- a/quic/quartc/test/bidi_test_runner.cc
+++ b/quic/quartc/test/bidi_test_runner.cc
@@ -11,22 +11,12 @@
 
 namespace {
 
-bool ContainsSequenceNumbers(const std::vector<ReceivedMessage>& messages,
-                             IdToSequenceNumberMap id_to_sequence_number) {
-  for (const auto& message : messages) {
-    auto it = id_to_sequence_number.find(message.frame.source_id);
-    if (it != id_to_sequence_number.end() &&
-        it->second == message.frame.sequence_number) {
-      id_to_sequence_number.erase(it);
-    }
-  }
-  return id_to_sequence_number.empty();
-}
-
-void LogResults(const std::vector<ReceivedMessage>& messages) {
+void LogResults(const std::vector<ReceivedMessage>& messages,
+                IdToSequenceNumberMap sent_sequence_numbers) {
   QuicTime::Delta max_delay = QuicTime::Delta::Zero();
   QuicTime::Delta total_delay = QuicTime::Delta::Zero();
   QuicByteCount total_throughput = 0;
+  int64_t messages_received = 0;
   for (const auto& message : messages) {
     QuicTime::Delta one_way_delay =
         message.receive_time - message.frame.send_time;
@@ -36,16 +26,28 @@
     max_delay = std::max(max_delay, one_way_delay);
     total_delay = total_delay + one_way_delay;
     total_throughput += message.frame.size;
+    ++messages_received;
   }
+
+  int64_t messages_expected = 0;
+  for (const auto& it : sent_sequence_numbers) {
+    // Sequence numbers start at zero, so add one to the last sequence number
+    // to get the expected number of messages.
+    messages_expected += it.second + 1;
+  }
+
   QuicBandwidth total_bandwidth = QuicBandwidth::FromBytesAndTimeDelta(
       total_throughput,
       messages.back().receive_time - messages.front().receive_time);
+  double fraction_lost =
+      1.0 - static_cast<double>(messages_received) / messages_expected;
   QUIC_LOG(INFO) << "Summary:\n  max_delay (ms)=" << max_delay.ToMilliseconds()
                  << "\n  average_delay (ms)="
                  << total_delay.ToMilliseconds() / messages.size()
                  << "\n  total_throughput (bytes)=" << total_throughput
                  << "\n  total_bandwidth (bps)="
-                 << total_bandwidth.ToBitsPerSecond();
+                 << total_bandwidth.ToBitsPerSecond()
+                 << "\n  fraction_lost=" << fraction_lost;
 }
 
 }  // namespace
@@ -141,30 +143,45 @@
   server_peer_->SetEnabled(false);
   client_peer_->SetEnabled(false);
 
-  IdToSequenceNumberMap sent_by_server = server_peer_->GetLastSequenceNumbers();
-  if (!simulator_->RunUntil([this, &sent_by_server] {
-        return ContainsSequenceNumbers(client_peer_->received_messages(),
-                                       sent_by_server);
-      })) {
-    return false;
-  }
-
-  IdToSequenceNumberMap sent_by_client = client_peer_->GetLastSequenceNumbers();
-  if (!simulator_->RunUntil([this, &sent_by_client] {
-        return ContainsSequenceNumbers(server_peer_->received_messages(),
-                                       sent_by_client);
-      })) {
+  if (!simulator_->RunUntil([this] { return PacketsDrained(); })) {
     return false;
   }
 
   // Compute results.
   QUIC_LOG(INFO) << "Printing client->server results:";
-  LogResults(server_peer_->received_messages());
+  LogResults(server_peer_->received_messages(),
+             client_peer_->GetLastSequenceNumbers());
 
   QUIC_LOG(INFO) << "Printing server->client results:";
-  LogResults(client_peer_->received_messages());
+  LogResults(client_peer_->received_messages(),
+             server_peer_->GetLastSequenceNumbers());
   return true;
 }
 
+bool BidiTestRunner::PacketsDrained() {
+  const ReceivedMessage& last_server_message =
+      server_peer_->received_messages().back();
+  const ReceivedMessage& last_client_message =
+      client_peer_->received_messages().back();
+
+  // Last observed propagation delay on the client -> server path.
+  QuicTime::Delta last_client_server_delay =
+      last_server_message.receive_time - last_server_message.frame.send_time;
+
+  // Last observed propagation delay on the server -> client path.
+  QuicTime::Delta last_server_client_delay =
+      last_client_message.receive_time - last_client_message.frame.send_time;
+
+  // Last observed RTT based on the propagation delays above.
+  QuicTime::Delta last_rtt =
+      last_client_server_delay + last_server_client_delay;
+
+  // If nothing interesting has happened for at least one RTT, then it's
+  // unlikely anything is still in flight.
+  QuicTime now = simulator_->GetClock()->Now();
+  return now - last_server_message.receive_time > last_rtt &&
+         now - last_client_message.receive_time > last_rtt;
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/quartc/test/bidi_test_runner.h b/quic/quartc/test/bidi_test_runner.h
index b76695a..4369e22 100644
--- a/quic/quartc/test/bidi_test_runner.h
+++ b/quic/quartc/test/bidi_test_runner.h
@@ -76,6 +76,9 @@
   virtual bool RunTest(QuicTime::Delta test_duration);
 
  private:
+  // Returns true when no pending packets are believed to be in-flight.
+  bool PacketsDrained();
+
   simulator::Simulator* simulator_;
   QuartcPacketTransport* client_transport_;
   QuartcPacketTransport* server_transport_;
diff --git a/quic/quartc/test/quartc_bidi_test.cc b/quic/quartc/test/quartc_bidi_test.cc
index 5c0e234..db9972c 100644
--- a/quic/quartc/test/quartc_bidi_test.cc
+++ b/quic/quartc/test/quartc_bidi_test.cc
@@ -11,6 +11,7 @@
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
 #include "net/third_party/quiche/src/quic/quartc/simulated_packet_transport.h"
 #include "net/third_party/quiche/src/quic/quartc/test/bidi_test_runner.h"
+#include "net/third_party/quiche/src/quic/quartc/test/random_packet_filter.h"
 #include "net/third_party/quiche/src/quic/test_tools/simulator/link.h"
 #include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h"
 
@@ -24,29 +25,47 @@
 
   void CreateTransports(QuicBandwidth bandwidth,
                         QuicTime::Delta propagation_delay,
-                        QuicByteCount queue_length) {
+                        QuicByteCount queue_length,
+                        int loss_percent) {
     client_transport_ =
         QuicMakeUnique<simulator::SimulatedQuartcPacketTransport>(
             &simulator_, "client_transport", "server_transport", queue_length);
     server_transport_ =
         QuicMakeUnique<simulator::SimulatedQuartcPacketTransport>(
             &simulator_, "server_transport", "client_transport", queue_length);
+    client_filter_ = QuicMakeUnique<simulator::RandomPacketFilter>(
+        &simulator_, "client_filter", client_transport_.get());
+    server_filter_ = QuicMakeUnique<simulator::RandomPacketFilter>(
+        &simulator_, "server_filter", server_transport_.get());
     client_server_link_ = QuicMakeUnique<simulator::SymmetricLink>(
-        client_transport_.get(), server_transport_.get(), bandwidth,
+        client_filter_.get(), server_filter_.get(), bandwidth,
         propagation_delay);
+    client_filter_->set_loss_percent(loss_percent);
+    server_filter_->set_loss_percent(loss_percent);
   }
 
   simulator::Simulator simulator_;
 
   std::unique_ptr<simulator::SimulatedQuartcPacketTransport> client_transport_;
   std::unique_ptr<simulator::SimulatedQuartcPacketTransport> server_transport_;
+  std::unique_ptr<simulator::RandomPacketFilter> client_filter_;
+  std::unique_ptr<simulator::RandomPacketFilter> server_filter_;
   std::unique_ptr<simulator::SymmetricLink> client_server_link_;
 };
 
 TEST_F(QuartcBidiTest, Basic300kbps200ms) {
   CreateTransports(QuicBandwidth::FromKBitsPerSecond(300),
                    QuicTime::Delta::FromMilliseconds(200),
-                   10 * kDefaultMaxPacketSize);
+                   10 * kDefaultMaxPacketSize, /*loss_percent=*/0);
+  BidiTestRunner runner(&simulator_, client_transport_.get(),
+                        server_transport_.get());
+  EXPECT_TRUE(runner.RunTest(QuicTime::Delta::FromSeconds(30)));
+}
+
+TEST_F(QuartcBidiTest, 300kbps200ms2PercentLoss) {
+  CreateTransports(QuicBandwidth::FromKBitsPerSecond(300),
+                   QuicTime::Delta::FromMilliseconds(200),
+                   10 * kDefaultMaxPacketSize, /*loss_percent=*/2);
   BidiTestRunner runner(&simulator_, client_transport_.get(),
                         server_transport_.get());
   EXPECT_TRUE(runner.RunTest(QuicTime::Delta::FromSeconds(30)));
diff --git a/quic/quartc/test/random_packet_filter.cc b/quic/quartc/test/random_packet_filter.cc
new file mode 100644
index 0000000..2fb160e
--- /dev/null
+++ b/quic/quartc/test/random_packet_filter.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2019 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 "net/third_party/quiche/src/quic/quartc/test/random_packet_filter.h"
+
+namespace quic {
+namespace simulator {
+
+RandomPacketFilter::RandomPacketFilter(Simulator* simulator,
+                                       const std::string& name,
+                                       Endpoint* endpoint)
+    : PacketFilter(simulator, name, endpoint), simulator_(simulator) {}
+
+bool RandomPacketFilter::FilterPacket(const Packet& packet) {
+  uint64_t random = simulator_->GetRandomGenerator()->RandUint64();
+  return 100 * static_cast<double>(random) /
+             std::numeric_limits<uint64_t>::max() >=
+         loss_percent_;
+}
+
+}  // namespace simulator
+}  // namespace quic
diff --git a/quic/quartc/test/random_packet_filter.h b/quic/quartc/test/random_packet_filter.h
new file mode 100644
index 0000000..b97d498
--- /dev/null
+++ b/quic/quartc/test/random_packet_filter.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2019 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.
+
+#ifndef QUICHE_QUIC_QUARTC_TEST_RANDOM_PACKET_FILTER_H_
+#define QUICHE_QUIC_QUARTC_TEST_RANDOM_PACKET_FILTER_H_
+
+#include "net/third_party/quiche/src/quic/test_tools/simulator/packet_filter.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/port.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h"
+
+namespace quic {
+namespace simulator {
+
+// Packet filter which randomly drops packets.
+class RandomPacketFilter : public PacketFilter {
+ public:
+  RandomPacketFilter(Simulator* simulator,
+                     const std::string& name,
+                     Endpoint* endpoint);
+
+  void set_loss_percent(double loss_percent) {
+    DCHECK_GE(loss_percent, 0);
+    DCHECK_LE(loss_percent, 100);
+    loss_percent_ = loss_percent;
+  }
+
+ protected:
+  bool FilterPacket(const Packet& packet) override;
+
+ private:
+  Simulator* simulator_;
+  double loss_percent_ = 0;
+};
+
+}  // namespace simulator
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_QUARTC_TEST_RANDOM_PACKET_FILTER_H_