Split QuicEndpoint into QuicEndpoint and QuicEndpointBase.

QuicEndpointBase handles network I/O integration, while QuicEndpoint handles writing test data into the session.  This allows running custom sessions with meaningful semantics using the QUIC simulator.

gfe-relnote: n/a (test-only change)
PiperOrigin-RevId: 276154770
Change-Id: Iff8778578cb484182fd28ea51d263a636651b943
diff --git a/quic/core/congestion_control/bbr2_simulator_test.cc b/quic/core/congestion_control/bbr2_simulator_test.cc
index 75ab6d2..193196f 100644
--- a/quic/core/congestion_control/bbr2_simulator_test.cc
+++ b/quic/core/congestion_control/bbr2_simulator_test.cc
@@ -741,7 +741,7 @@
  protected:
   Bbr2MultiSenderTest() {
     uint64_t first_connection_id = 42;
-    std::vector<simulator::QuicEndpoint*> receiver_endpoint_pointers;
+    std::vector<simulator::QuicEndpointBase*> receiver_endpoint_pointers;
     for (size_t i = 0; i < MultiSenderTopologyParams::kNumLocalLinks; ++i) {
       std::string sender_name = QuicStrCat("Sender", i + 1);
       std::string receiver_name = QuicStrCat("Receiver", i + 1);
diff --git a/quic/test_tools/simulator/quic_endpoint.cc b/quic/test_tools/simulator/quic_endpoint.cc
index bf84b37..9f50435 100644
--- a/quic/test_tools/simulator/quic_endpoint.cc
+++ b/quic/test_tools/simulator/quic_endpoint.cc
@@ -4,6 +4,7 @@
 
 #include "net/third_party/quiche/src/quic/test_tools/simulator/quic_endpoint.h"
 
+#include <memory>
 #include <utility>
 
 #include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h"
@@ -23,87 +24,41 @@
 const QuicByteCount kWriteChunkSize = 128 * 1024;
 const char kStreamDataContents = 'Q';
 
-// Takes a SHA-1 hash of the name and converts it into five 32-bit integers.
-static std::vector<uint32_t> HashNameIntoFive32BitIntegers(std::string name) {
-  const std::string hash = test::Sha1Hash(name);
-
-  std::vector<uint32_t> output;
-  uint32_t current_number = 0;
-  for (size_t i = 0; i < hash.size(); i++) {
-    current_number = (current_number << 8) + hash[i];
-    if (i % 4 == 3) {
-      output.push_back(i);
-      current_number = 0;
-    }
-  }
-
-  return output;
-}
-
-QuicSocketAddress GetAddressFromName(std::string name) {
-  const std::vector<uint32_t> hash = HashNameIntoFive32BitIntegers(name);
-
-  // Generate a random port between 1025 and 65535.
-  const uint16_t port = 1025 + hash[0] % (65535 - 1025 + 1);
-
-  // Generate a random 10.x.x.x address, where x is between 1 and 254.
-  std::string ip_address{"\xa\0\0\0", 4};
-  for (size_t i = 1; i < 4; i++) {
-    ip_address[i] = 1 + hash[i] % 254;
-  }
-  QuicIpAddress host;
-  host.FromPackedString(ip_address.c_str(), ip_address.length());
-  return QuicSocketAddress(host, port);
-}
-
 QuicEndpoint::QuicEndpoint(Simulator* simulator,
                            std::string name,
                            std::string peer_name,
                            Perspective perspective,
                            QuicConnectionId connection_id)
-    : Endpoint(simulator, name),
-      peer_name_(peer_name),
-      writer_(this),
-      nic_tx_queue_(simulator,
-                    QuicStringPrintf("%s (TX Queue)", name.c_str()),
-                    kMaxOutgoingPacketSize * kTxQueueSize),
-      connection_(connection_id,
-                  GetAddressFromName(peer_name),
-                  simulator,
-                  simulator->GetAlarmFactory(),
-                  &writer_,
-                  false,
-                  perspective,
-                  ParsedVersionOfIndex(CurrentSupportedVersions(), 0)),
+    : QuicEndpointBase(simulator, name, peer_name),
       bytes_to_transfer_(0),
       bytes_transferred_(0),
-      write_blocked_count_(0),
       wrong_data_received_(false),
-      drop_next_packet_(false),
       notifier_(nullptr) {
-  nic_tx_queue_.set_listener_interface(this);
-
-  connection_.SetSelfAddress(GetAddressFromName(name));
-  connection_.set_visitor(this);
-  connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
-                           std::make_unique<NullEncrypter>(perspective));
-  connection_.SetEncrypter(ENCRYPTION_INITIAL, nullptr);
-  if (connection_.version().KnowsWhichDecrypterToUse()) {
-    connection_.InstallDecrypter(ENCRYPTION_FORWARD_SECURE,
-                                 std::make_unique<NullDecrypter>(perspective));
-    connection_.RemoveDecrypter(ENCRYPTION_INITIAL);
+  connection_ = std::make_unique<QuicConnection>(
+      connection_id, GetAddressFromName(peer_name), simulator,
+      simulator->GetAlarmFactory(), &writer_, false, perspective,
+      ParsedVersionOfIndex(CurrentSupportedVersions(), 0));
+  connection_->SetSelfAddress(GetAddressFromName(name));
+  connection_->set_visitor(this);
+  connection_->SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+                            std::make_unique<NullEncrypter>(perspective));
+  connection_->SetEncrypter(ENCRYPTION_INITIAL, nullptr);
+  if (connection_->version().KnowsWhichDecrypterToUse()) {
+    connection_->InstallDecrypter(ENCRYPTION_FORWARD_SECURE,
+                                  std::make_unique<NullDecrypter>(perspective));
+    connection_->RemoveDecrypter(ENCRYPTION_INITIAL);
   } else {
-    connection_.SetDecrypter(ENCRYPTION_FORWARD_SECURE,
-                             std::make_unique<NullDecrypter>(perspective));
+    connection_->SetDecrypter(ENCRYPTION_FORWARD_SECURE,
+                              std::make_unique<NullDecrypter>(perspective));
   }
-  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   if (perspective == Perspective::IS_SERVER) {
     // Skip version negotiation.
-    test::QuicConnectionPeer::SetNegotiatedVersion(&connection_);
+    test::QuicConnectionPeer::SetNegotiatedVersion(connection_.get());
   }
-  connection_.SetDataProducer(&producer_);
-  connection_.SetSessionNotifier(this);
-  notifier_ = std::make_unique<test::SimpleSessionNotifier>(&connection_);
+  connection_->SetDataProducer(&producer_);
+  connection_->SetSessionNotifier(this);
+  notifier_ = std::make_unique<test::SimpleSessionNotifier>(connection_.get());
 
   // Configure the connection as if it received a handshake.  This is important
   // primarily because
@@ -120,19 +75,7 @@
       peer_hello, perspective == Perspective::IS_CLIENT ? SERVER : CLIENT,
       &error);
   DCHECK_EQ(error_code, QUIC_NO_ERROR) << "Configuration failed: " << error;
-  connection_.SetFromConfig(config);
-}
-
-QuicEndpoint::~QuicEndpoint() {
-  if (trace_visitor_ != nullptr) {
-    const char* perspective_prefix =
-        connection_.perspective() == Perspective::IS_CLIENT ? "C" : "S";
-
-    std::string identifier =
-        QuicStrCat(perspective_prefix, connection_.connection_id().ToString());
-    QuicRecordTestOutput(identifier,
-                         trace_visitor_->trace()->SerializeAsString());
-  }
+  connection_->SetFromConfig(config);
 }
 
 QuicByteCount QuicEndpoint::bytes_received() const {
@@ -174,48 +117,6 @@
   WriteStreamData();
 }
 
-void QuicEndpoint::DropNextIncomingPacket() {
-  drop_next_packet_ = true;
-}
-
-void QuicEndpoint::RecordTrace() {
-  trace_visitor_ = std::make_unique<QuicTraceVisitor>(&connection_);
-  connection_.set_debug_visitor(trace_visitor_.get());
-}
-
-void QuicEndpoint::AcceptPacket(std::unique_ptr<Packet> packet) {
-  if (packet->destination != name_) {
-    return;
-  }
-  if (drop_next_packet_) {
-    drop_next_packet_ = false;
-    return;
-  }
-
-  QuicReceivedPacket received_packet(packet->contents.data(),
-                                     packet->contents.size(), clock_->Now());
-  connection_.ProcessUdpPacket(connection_.self_address(),
-                               connection_.peer_address(), received_packet);
-}
-
-UnconstrainedPortInterface* QuicEndpoint::GetRxPort() {
-  return this;
-}
-
-void QuicEndpoint::SetTxPort(ConstrainedPortInterface* port) {
-  // Any egress done by the endpoint is actually handled by a queue on an NIC.
-  nic_tx_queue_.set_tx_port(port);
-}
-
-void QuicEndpoint::OnPacketDequeued() {
-  if (writer_.IsWriteBlocked() &&
-      (nic_tx_queue_.capacity() - nic_tx_queue_.bytes_queued()) >=
-          kMaxOutgoingPacketSize) {
-    writer_.SetWritable();
-    connection_.OnCanWrite();
-  }
-}
-
 void QuicEndpoint::OnStreamFrame(const QuicStreamFrame& frame) {
   // Verify that the data received always matches the expected.
   DCHECK(frame.stream_id == kDataStream);
@@ -300,73 +201,6 @@
   return false;
 }
 
-QuicEndpoint::Writer::Writer(QuicEndpoint* endpoint)
-    : endpoint_(endpoint), is_blocked_(false) {}
-
-QuicEndpoint::Writer::~Writer() {}
-
-WriteResult QuicEndpoint::Writer::WritePacket(
-    const char* buffer,
-    size_t buf_len,
-    const QuicIpAddress& /*self_address*/,
-    const QuicSocketAddress& /*peer_address*/,
-    PerPacketOptions* options) {
-  DCHECK(!IsWriteBlocked());
-  DCHECK(options == nullptr);
-  DCHECK(buf_len <= kMaxOutgoingPacketSize);
-
-  // Instead of losing a packet, become write-blocked when the egress queue is
-  // full.
-  if (endpoint_->nic_tx_queue_.packets_queued() > kTxQueueSize) {
-    is_blocked_ = true;
-    endpoint_->write_blocked_count_++;
-    return WriteResult(WRITE_STATUS_BLOCKED, 0);
-  }
-
-  auto packet = std::make_unique<Packet>();
-  packet->source = endpoint_->name();
-  packet->destination = endpoint_->peer_name_;
-  packet->tx_timestamp = endpoint_->clock_->Now();
-
-  packet->contents = std::string(buffer, buf_len);
-  packet->size = buf_len;
-
-  endpoint_->nic_tx_queue_.AcceptPacket(std::move(packet));
-
-  return WriteResult(WRITE_STATUS_OK, buf_len);
-}
-
-bool QuicEndpoint::Writer::IsWriteBlocked() const {
-  return is_blocked_;
-}
-
-void QuicEndpoint::Writer::SetWritable() {
-  is_blocked_ = false;
-}
-
-QuicByteCount QuicEndpoint::Writer::GetMaxPacketSize(
-    const QuicSocketAddress& /*peer_address*/) const {
-  return kMaxOutgoingPacketSize;
-}
-
-bool QuicEndpoint::Writer::SupportsReleaseTime() const {
-  return false;
-}
-
-bool QuicEndpoint::Writer::IsBatchMode() const {
-  return false;
-}
-
-char* QuicEndpoint::Writer::GetNextWriteLocation(
-    const QuicIpAddress& /*self_address*/,
-    const QuicSocketAddress& /*peer_address*/) {
-  return nullptr;
-}
-
-WriteResult QuicEndpoint::Writer::Flush() {
-  return WriteResult(WRITE_STATUS_OK, 0);
-}
-
 WriteStreamDataResult QuicEndpoint::DataProducer::WriteStreamData(
     QuicStreamId /*id*/,
     QuicStreamOffset /*offset*/,
@@ -386,14 +220,14 @@
 
 void QuicEndpoint::WriteStreamData() {
   // Instantiate a flusher which would normally be here due to QuicSession.
-  QuicConnection::ScopedPacketFlusher flusher(&connection_);
+  QuicConnection::ScopedPacketFlusher flusher(connection_.get());
 
   while (bytes_to_transfer_ > 0) {
     // Transfer data in chunks of size at most |kWriteChunkSize|.
     const size_t transmission_size =
         std::min(kWriteChunkSize, bytes_to_transfer_);
 
-    QuicConsumedData consumed_data = connection_.SendStreamData(
+    QuicConsumedData consumed_data = connection_->SendStreamData(
         kDataStream, transmission_size, bytes_transferred_, NO_FIN);
 
     DCHECK(consumed_data.bytes_consumed <= transmission_size);
@@ -405,33 +239,5 @@
   }
 }
 
-QuicEndpointMultiplexer::QuicEndpointMultiplexer(
-    std::string name,
-    const std::vector<QuicEndpoint*>& endpoints)
-    : Endpoint((*endpoints.begin())->simulator(), name) {
-  for (QuicEndpoint* endpoint : endpoints) {
-    mapping_.insert(std::make_pair(endpoint->name(), endpoint));
-  }
-}
-
-QuicEndpointMultiplexer::~QuicEndpointMultiplexer() {}
-
-void QuicEndpointMultiplexer::AcceptPacket(std::unique_ptr<Packet> packet) {
-  auto key_value_pair_it = mapping_.find(packet->destination);
-  if (key_value_pair_it == mapping_.end()) {
-    return;
-  }
-
-  key_value_pair_it->second->GetRxPort()->AcceptPacket(std::move(packet));
-}
-UnconstrainedPortInterface* QuicEndpointMultiplexer::GetRxPort() {
-  return this;
-}
-void QuicEndpointMultiplexer::SetTxPort(ConstrainedPortInterface* port) {
-  for (auto& key_value_pair : mapping_) {
-    key_value_pair.second->SetTxPort(port);
-  }
-}
-
 }  // namespace simulator
 }  // namespace quic
diff --git a/quic/test_tools/simulator/quic_endpoint.h b/quic/test_tools/simulator/quic_endpoint.h
index 43fce53..0b9b54a 100644
--- a/quic/test_tools/simulator/quic_endpoint.h
+++ b/quic/test_tools/simulator/quic_endpoint.h
@@ -16,26 +16,17 @@
 #include "net/third_party/quiche/src/quic/test_tools/simple_session_notifier.h"
 #include "net/third_party/quiche/src/quic/test_tools/simulator/link.h"
 #include "net/third_party/quiche/src/quic/test_tools/simulator/queue.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/quic_endpoint_base.h"
 
 namespace quic {
 namespace simulator {
 
-// Size of the TX queue used by the kernel/NIC.  1000 is the Linux
-// kernel default.
-const QuicByteCount kTxQueueSize = 1000;
-
-// Generate a random local network host-port tuple based on the name of the
-// endpoint.
-QuicSocketAddress GetAddressFromName(std::string name);
-
 // A QUIC connection endpoint.  Wraps around QuicConnection.  In order to
 // initiate a transfer, the caller has to call AddBytesToTransfer().  The data
 // transferred is always the same and is always transferred on a single stream.
 // The endpoint receives all packets addressed to it, and verifies that the data
 // received is what it's supposed to be.
-class QuicEndpoint : public Endpoint,
-                     public UnconstrainedPortInterface,
-                     public Queue::ListenerInterface,
+class QuicEndpoint : public QuicEndpointBase,
                      public QuicConnectionVisitorInterface,
                      public SessionNotifierInterface {
  public:
@@ -44,40 +35,16 @@
                std::string peer_name,
                Perspective perspective,
                QuicConnectionId connection_id);
-  ~QuicEndpoint() override;
 
-  inline QuicConnection* connection() { return &connection_; }
   QuicByteCount bytes_to_transfer() const;
   QuicByteCount bytes_transferred() const;
   QuicByteCount bytes_received() const;
-  inline size_t write_blocked_count() { return write_blocked_count_; }
   inline bool wrong_data_received() const { return wrong_data_received_; }
 
   // Send |bytes| bytes.  Initiates the transfer if one is not already in
   // progress.
   void AddBytesToTransfer(QuicByteCount bytes);
 
-  // Drop the next packet upon receipt.
-  void DropNextIncomingPacket();
-
-  // UnconstrainedPortInterface method.  Called whenever the endpoint receives a
-  // packet.
-  void AcceptPacket(std::unique_ptr<Packet> packet) override;
-
-  // Enables logging of the connection trace at the end of the unit test.
-  void RecordTrace();
-
-  // Begin Endpoint implementation.
-  UnconstrainedPortInterface* GetRxPort() override;
-  void SetTxPort(ConstrainedPortInterface* port) override;
-  // End Endpoint implementation.
-
-  // Actor method.
-  void Act() override {}
-
-  // Queue::ListenerInterface method.
-  void OnPacketDequeued() override;
-
   // Begin QuicConnectionVisitorInterface implementation.
   void OnStreamFrame(const QuicStreamFrame& frame) override;
   void OnCryptoFrame(const QuicCryptoFrame& frame) override;
@@ -134,33 +101,6 @@
   // End SessionNotifierInterface implementation.
 
  private:
-  // A Writer object that writes into the |nic_tx_queue_|.
-  class Writer : public QuicPacketWriter {
-   public:
-    explicit Writer(QuicEndpoint* endpoint);
-    ~Writer() override;
-
-    WriteResult WritePacket(const char* buffer,
-                            size_t buf_len,
-                            const QuicIpAddress& self_address,
-                            const QuicSocketAddress& peer_address,
-                            PerPacketOptions* options) override;
-    bool IsWriteBlocked() const override;
-    void SetWritable() override;
-    QuicByteCount GetMaxPacketSize(
-        const QuicSocketAddress& peer_address) const override;
-    bool SupportsReleaseTime() const override;
-    bool IsBatchMode() const override;
-    char* GetNextWriteLocation(const QuicIpAddress& self_address,
-                               const QuicSocketAddress& peer_address) override;
-    WriteResult Flush() override;
-
-   private:
-    QuicEndpoint* endpoint_;
-
-    bool is_blocked_;
-  };
-
   // The producer outputs the repetition of the same byte.  That sequence is
   // verified by the receiver.
   class DataProducer : public QuicStreamFrameDataProducer {
@@ -175,60 +115,30 @@
                          QuicDataWriter* writer) override;
   };
 
+  std::unique_ptr<QuicConnection> CreateConnection(
+      Simulator* simulator,
+      std::string name,
+      std::string peer_name,
+      Perspective perspective,
+      QuicConnectionId connection_id);
+
   // Write stream data until |bytes_to_transfer_| is zero or the connection is
   // write-blocked.
   void WriteStreamData();
 
-  std::string peer_name_;
-
-  Writer writer_;
   DataProducer producer_;
-  // The queue for the outgoing packets.  In reality, this might be either on
-  // the network card, or in the kernel, but for concreteness we assume it's on
-  // the network card.
-  Queue nic_tx_queue_;
-  QuicConnection connection_;
 
   QuicByteCount bytes_to_transfer_;
   QuicByteCount bytes_transferred_;
 
-  // Counts the number of times the writer became write-blocked.
-  size_t write_blocked_count_;
-
   // Set to true if the endpoint receives stream data different from what it
   // expects.
   bool wrong_data_received_;
 
-  // If true, drop the next packet when receiving it.
-  bool drop_next_packet_;
-
   // Record of received offsets in the data stream.
   QuicIntervalSet<QuicStreamOffset> offsets_received_;
 
   std::unique_ptr<test::SimpleSessionNotifier> notifier_;
-  std::unique_ptr<QuicTraceVisitor> trace_visitor_;
-};
-
-// Multiplexes multiple connections at the same host on the network.
-class QuicEndpointMultiplexer : public Endpoint,
-                                public UnconstrainedPortInterface {
- public:
-  QuicEndpointMultiplexer(std::string name,
-                          const std::vector<QuicEndpoint*>& endpoints);
-  ~QuicEndpointMultiplexer() override;
-
-  // Receives a packet and passes it to the specified endpoint if that endpoint
-  // is one of the endpoints being multiplexed, otherwise ignores the packet.
-  void AcceptPacket(std::unique_ptr<Packet> packet) override;
-  UnconstrainedPortInterface* GetRxPort() override;
-
-  // Sets the egress port for all the endpoints being multiplexed.
-  void SetTxPort(ConstrainedPortInterface* port) override;
-
-  void Act() override {}
-
- private:
-  QuicUnorderedMap<std::string, QuicEndpoint*> mapping_;
 };
 
 }  // namespace simulator
diff --git a/quic/test_tools/simulator/quic_endpoint_base.cc b/quic/test_tools/simulator/quic_endpoint_base.cc
new file mode 100644
index 0000000..537a947
--- /dev/null
+++ b/quic/test_tools/simulator/quic_endpoint_base.cc
@@ -0,0 +1,222 @@
+// Copyright (c) 2012 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/test_tools/simulator/quic_endpoint_base.h"
+
+#include <memory>
+#include <utility>
+
+#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h"
+#include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test_output.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/simulator.h"
+
+namespace quic {
+namespace simulator {
+
+// Takes a SHA-1 hash of the name and converts it into five 32-bit integers.
+static std::vector<uint32_t> HashNameIntoFive32BitIntegers(std::string name) {
+  const std::string hash = test::Sha1Hash(name);
+
+  std::vector<uint32_t> output;
+  uint32_t current_number = 0;
+  for (size_t i = 0; i < hash.size(); i++) {
+    current_number = (current_number << 8) + hash[i];
+    if (i % 4 == 3) {
+      output.push_back(i);
+      current_number = 0;
+    }
+  }
+
+  return output;
+}
+
+QuicSocketAddress GetAddressFromName(std::string name) {
+  const std::vector<uint32_t> hash = HashNameIntoFive32BitIntegers(name);
+
+  // Generate a random port between 1025 and 65535.
+  const uint16_t port = 1025 + hash[0] % (65535 - 1025 + 1);
+
+  // Generate a random 10.x.x.x address, where x is between 1 and 254.
+  std::string ip_address{"\xa\0\0\0", 4};
+  for (size_t i = 1; i < 4; i++) {
+    ip_address[i] = 1 + hash[i] % 254;
+  }
+  QuicIpAddress host;
+  host.FromPackedString(ip_address.c_str(), ip_address.length());
+  return QuicSocketAddress(host, port);
+}
+
+QuicEndpointBase::QuicEndpointBase(Simulator* simulator,
+                                   std::string name,
+                                   std::string peer_name)
+    : Endpoint(simulator, name),
+      peer_name_(peer_name),
+      writer_(this),
+      nic_tx_queue_(simulator,
+                    QuicStringPrintf("%s (TX Queue)", name.c_str()),
+                    kMaxOutgoingPacketSize * kTxQueueSize),
+      connection_(nullptr),
+      write_blocked_count_(0),
+      drop_next_packet_(false) {
+  nic_tx_queue_.set_listener_interface(this);
+}
+
+QuicEndpointBase::~QuicEndpointBase() {
+  if (trace_visitor_ != nullptr) {
+    const char* perspective_prefix =
+        connection_->perspective() == Perspective::IS_CLIENT ? "C" : "S";
+
+    std::string identifier =
+        QuicStrCat(perspective_prefix, connection_->connection_id().ToString());
+    QuicRecordTestOutput(identifier,
+                         trace_visitor_->trace()->SerializeAsString());
+  }
+}
+
+void QuicEndpointBase::DropNextIncomingPacket() {
+  drop_next_packet_ = true;
+}
+
+void QuicEndpointBase::RecordTrace() {
+  trace_visitor_ = std::make_unique<QuicTraceVisitor>(connection_.get());
+  connection_->set_debug_visitor(trace_visitor_.get());
+}
+
+void QuicEndpointBase::AcceptPacket(std::unique_ptr<Packet> packet) {
+  if (packet->destination != name_) {
+    return;
+  }
+  if (drop_next_packet_) {
+    drop_next_packet_ = false;
+    return;
+  }
+
+  QuicReceivedPacket received_packet(packet->contents.data(),
+                                     packet->contents.size(), clock_->Now());
+  connection_->ProcessUdpPacket(connection_->self_address(),
+                                connection_->peer_address(), received_packet);
+}
+
+UnconstrainedPortInterface* QuicEndpointBase::GetRxPort() {
+  return this;
+}
+
+void QuicEndpointBase::SetTxPort(ConstrainedPortInterface* port) {
+  // Any egress done by the endpoint is actually handled by a queue on an NIC.
+  nic_tx_queue_.set_tx_port(port);
+}
+
+void QuicEndpointBase::OnPacketDequeued() {
+  if (writer_.IsWriteBlocked() &&
+      (nic_tx_queue_.capacity() - nic_tx_queue_.bytes_queued()) >=
+          kMaxOutgoingPacketSize) {
+    writer_.SetWritable();
+    connection_->OnCanWrite();
+  }
+}
+
+QuicEndpointBase::Writer::Writer(QuicEndpointBase* endpoint)
+    : endpoint_(endpoint), is_blocked_(false) {}
+
+QuicEndpointBase::Writer::~Writer() {}
+
+WriteResult QuicEndpointBase::Writer::WritePacket(
+    const char* buffer,
+    size_t buf_len,
+    const QuicIpAddress& /*self_address*/,
+    const QuicSocketAddress& /*peer_address*/,
+    PerPacketOptions* options) {
+  DCHECK(!IsWriteBlocked());
+  DCHECK(options == nullptr);
+  DCHECK(buf_len <= kMaxOutgoingPacketSize);
+
+  // Instead of losing a packet, become write-blocked when the egress queue is
+  // full.
+  if (endpoint_->nic_tx_queue_.packets_queued() > kTxQueueSize) {
+    is_blocked_ = true;
+    endpoint_->write_blocked_count_++;
+    return WriteResult(WRITE_STATUS_BLOCKED, 0);
+  }
+
+  auto packet = std::make_unique<Packet>();
+  packet->source = endpoint_->name();
+  packet->destination = endpoint_->peer_name_;
+  packet->tx_timestamp = endpoint_->clock_->Now();
+
+  packet->contents = std::string(buffer, buf_len);
+  packet->size = buf_len;
+
+  endpoint_->nic_tx_queue_.AcceptPacket(std::move(packet));
+
+  return WriteResult(WRITE_STATUS_OK, buf_len);
+}
+
+bool QuicEndpointBase::Writer::IsWriteBlocked() const {
+  return is_blocked_;
+}
+
+void QuicEndpointBase::Writer::SetWritable() {
+  is_blocked_ = false;
+}
+
+QuicByteCount QuicEndpointBase::Writer::GetMaxPacketSize(
+    const QuicSocketAddress& /*peer_address*/) const {
+  return kMaxOutgoingPacketSize;
+}
+
+bool QuicEndpointBase::Writer::SupportsReleaseTime() const {
+  return false;
+}
+
+bool QuicEndpointBase::Writer::IsBatchMode() const {
+  return false;
+}
+
+char* QuicEndpointBase::Writer::GetNextWriteLocation(
+    const QuicIpAddress& /*self_address*/,
+    const QuicSocketAddress& /*peer_address*/) {
+  return nullptr;
+}
+
+WriteResult QuicEndpointBase::Writer::Flush() {
+  return WriteResult(WRITE_STATUS_OK, 0);
+}
+
+QuicEndpointMultiplexer::QuicEndpointMultiplexer(
+    std::string name,
+    const std::vector<QuicEndpointBase*>& endpoints)
+    : Endpoint((*endpoints.begin())->simulator(), name) {
+  for (QuicEndpointBase* endpoint : endpoints) {
+    mapping_.insert(std::make_pair(endpoint->name(), endpoint));
+  }
+}
+
+QuicEndpointMultiplexer::~QuicEndpointMultiplexer() {}
+
+void QuicEndpointMultiplexer::AcceptPacket(std::unique_ptr<Packet> packet) {
+  auto key_value_pair_it = mapping_.find(packet->destination);
+  if (key_value_pair_it == mapping_.end()) {
+    return;
+  }
+
+  key_value_pair_it->second->GetRxPort()->AcceptPacket(std::move(packet));
+}
+UnconstrainedPortInterface* QuicEndpointMultiplexer::GetRxPort() {
+  return this;
+}
+void QuicEndpointMultiplexer::SetTxPort(ConstrainedPortInterface* port) {
+  for (auto& key_value_pair : mapping_) {
+    key_value_pair.second->SetTxPort(port);
+  }
+}
+
+}  // namespace simulator
+}  // namespace quic
diff --git a/quic/test_tools/simulator/quic_endpoint_base.h b/quic/test_tools/simulator/quic_endpoint_base.h
new file mode 100644
index 0000000..ae9f69b
--- /dev/null
+++ b/quic/test_tools/simulator/quic_endpoint_base.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2012 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_TEST_TOOLS_SIMULATOR_QUIC_ENDPOINT_BASE_H_
+#define QUICHE_QUIC_TEST_TOOLS_SIMULATOR_QUIC_ENDPOINT_BASE_H_
+
+#include <memory>
+
+#include "net/third_party/quiche/src/quic/core/crypto/null_decrypter.h"
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
+#include "net/third_party/quiche/src/quic/core/quic_connection.h"
+#include "net/third_party/quiche/src/quic/core/quic_default_packet_writer.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream_frame_data_producer.h"
+#include "net/third_party/quiche/src/quic/core/quic_trace_visitor.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/test_tools/simple_session_notifier.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/link.h"
+#include "net/third_party/quiche/src/quic/test_tools/simulator/queue.h"
+
+namespace quic {
+namespace simulator {
+
+// Size of the TX queue used by the kernel/NIC.  1000 is the Linux
+// kernel default.
+const QuicByteCount kTxQueueSize = 1000;
+
+// Generate a random local network host-port tuple based on the name of the
+// endpoint.
+QuicSocketAddress GetAddressFromName(std::string name);
+
+// A QUIC connection endpoint.  If the specific data transmitted does not matter
+// (e.g. for congestion control purposes), QuicEndpoint is the subclass that
+// transmits dummy data.  If the actual semantics of the connection matter,
+// subclassing QuicEndpointBase is required.
+class QuicEndpointBase : public Endpoint,
+                         public UnconstrainedPortInterface,
+                         public Queue::ListenerInterface {
+ public:
+  // Does not create the connection; the subclass has to create connection by
+  // itself.
+  QuicEndpointBase(Simulator* simulator,
+                   std::string name,
+                   std::string peer_name);
+  ~QuicEndpointBase() override;
+
+  inline QuicConnection* connection() { return connection_.get(); }
+  inline size_t write_blocked_count() { return write_blocked_count_; }
+
+  // Drop the next packet upon receipt.
+  void DropNextIncomingPacket();
+
+  // UnconstrainedPortInterface method.  Called whenever the endpoint receives a
+  // packet.
+  void AcceptPacket(std::unique_ptr<Packet> packet) override;
+
+  // Enables logging of the connection trace at the end of the unit test.
+  void RecordTrace();
+
+  // Begin Endpoint implementation.
+  UnconstrainedPortInterface* GetRxPort() override;
+  void SetTxPort(ConstrainedPortInterface* port) override;
+  // End Endpoint implementation.
+
+  // Actor method.
+  void Act() override {}
+
+  // Queue::ListenerInterface method.
+  void OnPacketDequeued() override;
+
+ protected:
+  // A Writer object that writes into the |nic_tx_queue_|.
+  class Writer : public QuicPacketWriter {
+   public:
+    explicit Writer(QuicEndpointBase* endpoint);
+    ~Writer() override;
+
+    WriteResult WritePacket(const char* buffer,
+                            size_t buf_len,
+                            const QuicIpAddress& self_address,
+                            const QuicSocketAddress& peer_address,
+                            PerPacketOptions* options) override;
+    bool IsWriteBlocked() const override;
+    void SetWritable() override;
+    QuicByteCount GetMaxPacketSize(
+        const QuicSocketAddress& peer_address) const override;
+    bool SupportsReleaseTime() const override;
+    bool IsBatchMode() const override;
+    char* GetNextWriteLocation(const QuicIpAddress& self_address,
+                               const QuicSocketAddress& peer_address) override;
+    WriteResult Flush() override;
+
+   private:
+    QuicEndpointBase* endpoint_;
+
+    bool is_blocked_;
+  };
+
+  // The producer outputs the repetition of the same byte.  That sequence is
+  // verified by the receiver.
+  class DataProducer : public QuicStreamFrameDataProducer {
+   public:
+    WriteStreamDataResult WriteStreamData(QuicStreamId id,
+                                          QuicStreamOffset offset,
+                                          QuicByteCount data_length,
+                                          QuicDataWriter* writer) override;
+    bool WriteCryptoData(EncryptionLevel level,
+                         QuicStreamOffset offset,
+                         QuicByteCount data_length,
+                         QuicDataWriter* writer) override;
+  };
+
+  std::string peer_name_;
+
+  Writer writer_;
+  // The queue for the outgoing packets.  In reality, this might be either on
+  // the network card, or in the kernel, but for concreteness we assume it's on
+  // the network card.
+  Queue nic_tx_queue_;
+  // Created by the subclass.
+  std::unique_ptr<QuicConnection> connection_;
+
+  // Counts the number of times the writer became write-blocked.
+  size_t write_blocked_count_;
+
+  // If true, drop the next packet when receiving it.
+  bool drop_next_packet_;
+
+  std::unique_ptr<QuicTraceVisitor> trace_visitor_;
+};
+
+// Multiplexes multiple connections at the same host on the network.
+class QuicEndpointMultiplexer : public Endpoint,
+                                public UnconstrainedPortInterface {
+ public:
+  QuicEndpointMultiplexer(std::string name,
+                          const std::vector<QuicEndpointBase*>& endpoints);
+  ~QuicEndpointMultiplexer() override;
+
+  // Receives a packet and passes it to the specified endpoint if that endpoint
+  // is one of the endpoints being multiplexed, otherwise ignores the packet.
+  void AcceptPacket(std::unique_ptr<Packet> packet) override;
+  UnconstrainedPortInterface* GetRxPort() override;
+
+  // Sets the egress port for all the endpoints being multiplexed.
+  void SetTxPort(ConstrainedPortInterface* port) override;
+
+  void Act() override {}
+
+ private:
+  QuicUnorderedMap<std::string, QuicEndpointBase*> mapping_;
+};
+
+}  // namespace simulator
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TEST_TOOLS_SIMULATOR_QUIC_ENDPOINT_BASE_H_