Merge changes Ie13868e5,Ie2c1e0ca,Ib91037bf,Ibf204b14,I0b1079c4, ...

* changes:
  Replace a DCHECK with a parse failure in connection ID parsing
  Move drop_response_body from QuicClient to QuicSpdyClientBase to share with chromium
  Extract the FakeProofVerifier from quic_client_bin.cc to facilitate sharing with Chromium.
  Add QuicFramer Probe methods
  Remove .impl() method from QuicSocketAddress and QuicIpAddress.
  Change QuartcClientEndpoint to handle client-side version negotiation.
  In quic_client_bin.cc, print the "Request (succeeded|failed)" messages in stdout instead of stderr.
  Have separate uni- and bi-directional stream limits for IETF QUIC
  Fix a use-after-free in quic_client.
diff --git a/quic/core/crypto/crypto_handshake_message.cc b/quic/core/crypto/crypto_handshake_message.cc
index 1bbed74..579bb37 100644
--- a/quic/core/crypto/crypto_handshake_message.cc
+++ b/quic/core/crypto/crypto_handshake_message.cc
@@ -274,7 +274,8 @@
       case kCFCW:
       case kSFCW:
       case kIRTT:
-      case kMIDS:
+      case kMIUS:
+      case kMIBS:
       case kSCLS:
       case kTCID:
         // uint32_t value
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index a3abb33..e8c8d84 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -218,7 +218,8 @@
 const QuicTag kCLOP = TAG('C', 'L', 'O', 'P');   // Client connection options
 const QuicTag kICSL = TAG('I', 'C', 'S', 'L');   // Idle network timeout
 const QuicTag kSCLS = TAG('S', 'C', 'L', 'S');   // Silently close on timeout
-const QuicTag kMIDS = TAG('M', 'I', 'D', 'S');   // Max incoming dynamic streams
+const QuicTag kMIBS = TAG('M', 'I', 'D', 'S');   // Max incoming bidi streams
+const QuicTag kMIUS = TAG('M', 'I', 'U', 'S');   // Max incoming unidi streams
 const QuicTag kIRTT = TAG('I', 'R', 'T', 'T');   // Estimated initial RTT in us.
 const QuicTag kSNI  = TAG('S', 'N', 'I', '\0');  // Server name
                                                  // indication
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 2675428..209bc81 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -1389,7 +1389,7 @@
   // Set a limit on maximum number of incoming dynamic streams.
   // Make sure the limit is respected.
   const uint32_t kServerMaxIncomingDynamicStreams = 1;
-  server_config_.SetMaxIncomingDynamicStreamsToSend(
+  server_config_.SetMaxIncomingBidirectionalStreamsToSend(
       kServerMaxIncomingDynamicStreams);
   ASSERT_TRUE(Initialize());
   if (GetParam().negotiated_version.transport_version == QUIC_VERSION_99) {
@@ -1430,10 +1430,15 @@
   // Each endpoint can set max incoming dynamic streams independently.
   const uint32_t kClientMaxIncomingDynamicStreams = 2;
   const uint32_t kServerMaxIncomingDynamicStreams = 1;
-  client_config_.SetMaxIncomingDynamicStreamsToSend(
+  client_config_.SetMaxIncomingBidirectionalStreamsToSend(
       kClientMaxIncomingDynamicStreams);
-  server_config_.SetMaxIncomingDynamicStreamsToSend(
+  server_config_.SetMaxIncomingBidirectionalStreamsToSend(
       kServerMaxIncomingDynamicStreams);
+  client_config_.SetMaxIncomingUnidirectionalStreamsToSend(
+      kClientMaxIncomingDynamicStreams);
+  server_config_.SetMaxIncomingUnidirectionalStreamsToSend(
+      kServerMaxIncomingDynamicStreams);
+
   ASSERT_TRUE(Initialize());
   EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
 
@@ -2739,8 +2744,10 @@
   const size_t kNumMaxStreams = 10;
 
   EndToEndTestServerPush() : EndToEndTest() {
-    client_config_.SetMaxIncomingDynamicStreamsToSend(kNumMaxStreams);
-    server_config_.SetMaxIncomingDynamicStreamsToSend(kNumMaxStreams);
+    client_config_.SetMaxIncomingBidirectionalStreamsToSend(kNumMaxStreams);
+    server_config_.SetMaxIncomingBidirectionalStreamsToSend(kNumMaxStreams);
+    client_config_.SetMaxIncomingUnidirectionalStreamsToSend(kNumMaxStreams);
+    server_config_.SetMaxIncomingUnidirectionalStreamsToSend(kNumMaxStreams);
     support_server_push_ = true;
   }
 
diff --git a/quic/core/http/quic_server_session_base_test.cc b/quic/core/http/quic_server_session_base_test.cc
index 0527f0b..b532601 100644
--- a/quic/core/http/quic_server_session_base_test.cc
+++ b/quic/core/http/quic_server_session_base_test.cc
@@ -131,9 +131,12 @@
                        TlsServerHandshaker::CreateSslCtx()),
         compressed_certs_cache_(
             QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) {
-    config_.SetMaxIncomingDynamicStreamsToSend(kMaxStreamsForTest);
-    QuicConfigPeer::SetReceivedMaxIncomingDynamicStreams(&config_,
-                                                         kMaxStreamsForTest);
+    config_.SetMaxIncomingBidirectionalStreamsToSend(kMaxStreamsForTest);
+    config_.SetMaxIncomingUnidirectionalStreamsToSend(kMaxStreamsForTest);
+    QuicConfigPeer::SetReceivedMaxIncomingBidirectionalStreams(
+        &config_, kMaxStreamsForTest);
+    QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
+        &config_, kMaxStreamsForTest);
     config_.SetInitialStreamFlowControlWindowToSend(
         kInitialStreamFlowControlWindowForTest);
     config_.SetInitialSessionFlowControlWindowToSend(
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index 8460c7d..599f939 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -153,7 +153,15 @@
     QuicCryptoClientStream* stream = static_cast<QuicCryptoClientStream*>(
         session_->GetMutableCryptoStream());
     QuicConfig config = DefaultQuicConfig();
-    config.SetMaxIncomingDynamicStreamsToSend(server_max_incoming_streams);
+    if (connection_->transport_version() == QUIC_VERSION_99) {
+      config.SetMaxIncomingUnidirectionalStreamsToSend(
+          server_max_incoming_streams);
+      config.SetMaxIncomingBidirectionalStreamsToSend(
+          server_max_incoming_streams);
+    } else {
+      config.SetMaxIncomingBidirectionalStreamsToSend(
+          server_max_incoming_streams);
+    }
     crypto_test_utils::HandshakeWithFakeServer(
         &config, &helper_, &alarm_factory_, connection_, stream);
   }
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 26ec153..c7ce057 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -69,7 +69,7 @@
         kInitialStreamFlowControlWindowForTest);
     session()->config()->SetInitialSessionFlowControlWindowToSend(
         kInitialSessionFlowControlWindowForTest);
-    session()->config()->ToHandshakeMessage(&msg);
+    session()->config()->ToHandshakeMessage(&msg, transport_version());
     const QuicErrorCode error =
         session()->config()->ProcessPeerHello(msg, CLIENT, &error_details);
     EXPECT_EQ(QUIC_NO_ERROR, error);
@@ -565,7 +565,11 @@
 TEST_P(QuicSpdySessionTestServer, ManyAvailableStreams) {
   // When max_open_streams_ is 200, should be able to create 200 streams
   // out-of-order, that is, creating the one with the largest stream ID first.
-  QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, 200);
+  if (IsVersion99()) {
+    QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(&session_, 200);
+  } else {
+    QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, 200);
+  }
   QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
   // Create one stream.
   session_.GetOrCreateDynamicStream(stream_id);
@@ -1179,7 +1183,7 @@
     QuicStreamOffset offset = crypto_stream->stream_bytes_written();
     QuicConfig config;
     CryptoHandshakeMessage crypto_message;
-    config.ToHandshakeMessage(&crypto_message);
+    config.ToHandshakeMessage(&crypto_message, transport_version());
     crypto_stream->SendHandshakeMessage(crypto_message);
     char buf[1000];
     QuicDataWriter writer(1000, buf, NETWORK_BYTE_ORDER);
@@ -1500,7 +1504,12 @@
   // with a FIN or RST then we send an RST to refuse streams for versions other
   // than version 99. In version 99 the connection gets closed.
   const QuicStreamId kMaxStreams = 5;
-  QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+  if (IsVersion99()) {
+    QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(&session_,
+                                                            kMaxStreams);
+  } else {
+    QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+  }
   // GetNth assumes that both the crypto and header streams have been
   // open, but the stream id manager, using GetFirstBidirectional... only
   // assumes that the crypto stream is open. This means that GetNth...(0)
@@ -1573,7 +1582,12 @@
   }
   EXPECT_CALL(*connection_, OnStreamReset(_, QUIC_REFUSED_STREAM)).Times(0);
   const QuicStreamId kMaxStreams = 5;
-  QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+  if (IsVersion99()) {
+    QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(&session_,
+                                                            kMaxStreams);
+  } else {
+    QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+  }
 
   // Create kMaxStreams + 1 data streams, and mark them draining.
   const QuicStreamId kFirstStreamId = GetNthClientInitiatedBidirectionalId(0);
diff --git a/quic/core/quic_config.cc b/quic/core/quic_config.cc
index 4880c6d..0914809 100644
--- a/quic/core/quic_config.cc
+++ b/quic/core/quic_config.cc
@@ -404,7 +404,7 @@
       client_connection_options_(kCLOP, PRESENCE_OPTIONAL),
       idle_network_timeout_seconds_(kICSL, PRESENCE_REQUIRED),
       silent_close_(kSCLS, PRESENCE_OPTIONAL),
-      max_incoming_dynamic_streams_(kMIDS, PRESENCE_REQUIRED),
+      max_incoming_bidirectional_streams_(kMIBS, PRESENCE_REQUIRED),
       bytes_for_connection_id_(kTCID, PRESENCE_OPTIONAL),
       initial_round_trip_time_us_(kIRTT, PRESENCE_OPTIONAL),
       initial_stream_flow_control_window_bytes_(kSFCW, PRESENCE_OPTIONAL),
@@ -412,7 +412,8 @@
       connection_migration_disabled_(kNCMR, PRESENCE_OPTIONAL),
       alternate_server_address_(kASAD, PRESENCE_OPTIONAL),
       support_max_header_list_size_(kSMHL, PRESENCE_OPTIONAL),
-      stateless_reset_token_(kSRST, PRESENCE_OPTIONAL) {
+      stateless_reset_token_(kSRST, PRESENCE_OPTIONAL),
+      max_incoming_unidirectional_streams_(kMIUS, PRESENCE_OPTIONAL) {
   SetDefaults();
 }
 
@@ -505,21 +506,38 @@
   return silent_close_.GetUint32() > 0;
 }
 
-void QuicConfig::SetMaxIncomingDynamicStreamsToSend(
-    uint32_t max_incoming_dynamic_streams) {
-  max_incoming_dynamic_streams_.SetSendValue(max_incoming_dynamic_streams);
+void QuicConfig::SetMaxIncomingBidirectionalStreamsToSend(
+    uint32_t max_streams) {
+  max_incoming_bidirectional_streams_.SetSendValue(max_streams);
 }
 
-uint32_t QuicConfig::GetMaxIncomingDynamicStreamsToSend() {
-  return max_incoming_dynamic_streams_.GetSendValue();
+uint32_t QuicConfig::GetMaxIncomingBidirectionalStreamsToSend() {
+  return max_incoming_bidirectional_streams_.GetSendValue();
 }
 
-bool QuicConfig::HasReceivedMaxIncomingDynamicStreams() {
-  return max_incoming_dynamic_streams_.HasReceivedValue();
+bool QuicConfig::HasReceivedMaxIncomingBidirectionalStreams() {
+  return max_incoming_bidirectional_streams_.HasReceivedValue();
 }
 
-uint32_t QuicConfig::ReceivedMaxIncomingDynamicStreams() {
-  return max_incoming_dynamic_streams_.GetReceivedValue();
+uint32_t QuicConfig::ReceivedMaxIncomingBidirectionalStreams() {
+  return max_incoming_bidirectional_streams_.GetReceivedValue();
+}
+
+void QuicConfig::SetMaxIncomingUnidirectionalStreamsToSend(
+    uint32_t max_streams) {
+  max_incoming_unidirectional_streams_.SetSendValue(max_streams);
+}
+
+uint32_t QuicConfig::GetMaxIncomingUnidirectionalStreamsToSend() {
+  return max_incoming_unidirectional_streams_.GetSendValue();
+}
+
+bool QuicConfig::HasReceivedMaxIncomingUnidirectionalStreams() {
+  return max_incoming_unidirectional_streams_.HasReceivedValue();
+}
+
+uint32_t QuicConfig::ReceivedMaxIncomingUnidirectionalStreams() {
+  return max_incoming_unidirectional_streams_.GetReceivedValue();
 }
 
 bool QuicConfig::HasSetBytesForConnectionIdToSend() const {
@@ -664,7 +682,8 @@
   idle_network_timeout_seconds_.set(kMaximumIdleTimeoutSecs,
                                     kDefaultIdleTimeoutSecs);
   silent_close_.set(1, 0);
-  SetMaxIncomingDynamicStreamsToSend(kDefaultMaxStreamsPerConnection);
+  SetMaxIncomingBidirectionalStreamsToSend(kDefaultMaxStreamsPerConnection);
+  SetMaxIncomingUnidirectionalStreamsToSend(kDefaultMaxStreamsPerConnection);
   max_time_before_crypto_handshake_ =
       QuicTime::Delta::FromSeconds(kMaxTimeForCryptoHandshakeSecs);
   max_idle_time_before_crypto_handshake_ =
@@ -676,10 +695,18 @@
   SetSupportMaxHeaderListSize();
 }
 
-void QuicConfig::ToHandshakeMessage(CryptoHandshakeMessage* out) const {
+void QuicConfig::ToHandshakeMessage(
+    CryptoHandshakeMessage* out,
+    QuicTransportVersion transport_version) const {
   idle_network_timeout_seconds_.ToHandshakeMessage(out);
   silent_close_.ToHandshakeMessage(out);
-  max_incoming_dynamic_streams_.ToHandshakeMessage(out);
+  // Do not need a version check here, max...bi... will encode
+  // as "MIDS" -- the max initial dynamic streams tag -- if
+  // doing some version other than IETF QUIC/V99.
+  max_incoming_bidirectional_streams_.ToHandshakeMessage(out);
+  if (transport_version == QUIC_VERSION_99) {
+    max_incoming_unidirectional_streams_.ToHandshakeMessage(out);
+  }
   bytes_for_connection_id_.ToHandshakeMessage(out);
   initial_round_trip_time_us_.ToHandshakeMessage(out);
   initial_stream_flow_control_window_bytes_.ToHandshakeMessage(out);
@@ -707,7 +734,11 @@
         silent_close_.ProcessPeerHello(peer_hello, hello_type, error_details);
   }
   if (error == QUIC_NO_ERROR) {
-    error = max_incoming_dynamic_streams_.ProcessPeerHello(
+    error = max_incoming_bidirectional_streams_.ProcessPeerHello(
+        peer_hello, hello_type, error_details);
+  }
+  if (error == QUIC_NO_ERROR) {
+    error = max_incoming_unidirectional_streams_.ProcessPeerHello(
         peer_hello, hello_type, error_details);
   }
   if (error == QUIC_NO_ERROR) {
@@ -771,9 +802,9 @@
   params->initial_max_stream_data_uni.set_value(
       initial_stream_flow_control_window_bytes_.GetSendValue());
   params->initial_max_streams_bidi.set_value(
-      max_incoming_dynamic_streams_.GetSendValue());
+      max_incoming_bidirectional_streams_.GetSendValue());
   params->initial_max_streams_uni.set_value(
-      max_incoming_dynamic_streams_.GetSendValue());
+      max_incoming_unidirectional_streams_.GetSendValue());
   params->max_ack_delay.set_value(kDefaultDelayedAckTimeMs);
   params->disable_migration =
       connection_migration_disabled_.HasSendValue() &&
@@ -846,10 +877,12 @@
   initial_session_flow_control_window_bytes_.SetReceivedValue(
       std::min<uint64_t>(params.initial_max_data.value(),
                          std::numeric_limits<uint32_t>::max()));
-
-  max_incoming_dynamic_streams_.SetReceivedValue(
+  max_incoming_bidirectional_streams_.SetReceivedValue(
       std::min<uint64_t>(params.initial_max_streams_bidi.value(),
                          std::numeric_limits<uint32_t>::max()));
+  max_incoming_unidirectional_streams_.SetReceivedValue(
+      std::min<uint64_t>(params.initial_max_streams_uni.value(),
+                         std::numeric_limits<uint32_t>::max()));
 
   initial_stream_flow_control_window_bytes_.SetReceivedValue(
       std::min<uint64_t>(params.initial_max_stream_data_bidi_local.value(),
diff --git a/quic/core/quic_config.h b/quic/core/quic_config.h
index 8ad161d..ae15b6a 100644
--- a/quic/core/quic_config.h
+++ b/quic/core/quic_config.h
@@ -305,14 +305,24 @@
 
   bool SilentClose() const;
 
-  void SetMaxIncomingDynamicStreamsToSend(
-      uint32_t max_incoming_dynamic_streams);
+  // Configuration for the Google QUIC and IETF QUIC stream ID managers. Note
+  // that the naming is a bit  weird; it is from the perspective of the node
+  // generating (sending) the configuration and, thus, The "incoming" counts are
+  // the number of streams that the node sending the configuration is willing to
+  // accept and therefore the number that the node receiving the confguration
+  // can create .. the number of outbound streams that may be intiated..
+  // There are two sets, one for unidirectional streams and one for
+  // bidirectional. The bidirectional set also covers Google-QUICs
+  // dynamic stream count (which are bidirectional streams).
+  void SetMaxIncomingBidirectionalStreamsToSend(uint32_t max_streams);
+  uint32_t GetMaxIncomingBidirectionalStreamsToSend();
+  bool HasReceivedMaxIncomingBidirectionalStreams();
+  uint32_t ReceivedMaxIncomingBidirectionalStreams();
 
-  uint32_t GetMaxIncomingDynamicStreamsToSend();
-
-  bool HasReceivedMaxIncomingDynamicStreams();
-
-  uint32_t ReceivedMaxIncomingDynamicStreams();
+  void SetMaxIncomingUnidirectionalStreamsToSend(uint32_t max_streams);
+  uint32_t GetMaxIncomingUnidirectionalStreamsToSend();
+  bool HasReceivedMaxIncomingUnidirectionalStreams();
+  uint32_t ReceivedMaxIncomingUnidirectionalStreams();
 
   void set_max_time_before_crypto_handshake(
       QuicTime::Delta max_time_before_crypto_handshake) {
@@ -412,7 +422,8 @@
 
   // ToHandshakeMessage serialises the settings in this object as a series of
   // tags /value pairs and adds them to |out|.
-  void ToHandshakeMessage(CryptoHandshakeMessage* out) const;
+  void ToHandshakeMessage(CryptoHandshakeMessage* out,
+                          QuicTransportVersion transport_version) const;
 
   // Calls ProcessPeerHello on each negotiable parameter. On failure returns
   // the corresponding QuicErrorCode and sets detailed error in |error_details|.
@@ -456,8 +467,10 @@
   QuicNegotiableUint32 idle_network_timeout_seconds_;
   // Whether to use silent close.  Defaults to 0 (false) and is otherwise true.
   QuicNegotiableUint32 silent_close_;
-  // Maximum number of incoming dynamic streams that the connection can support.
-  QuicFixedUint32 max_incoming_dynamic_streams_;
+  // Maximum number of incoming dynamic streams that a Google QUIC connection
+  // can support or the maximum number of incoming bidirectional streams that
+  // an IETF QUIC connection can support.
+  QuicFixedUint32 max_incoming_bidirectional_streams_;
   // The number of bytes required for the connection ID.
   QuicFixedUint32 bytes_for_connection_id_;
   // Initial round trip time estimate in microseconds.
@@ -484,6 +497,10 @@
   // be created. This allows for CHLOs that are larger than a single
   // packet to be processed.
   QuicTagVector create_session_tag_indicators_;
+
+  // Maximum number of incoming unidirectional streams that the connection can
+  // support.
+  QuicFixedUint32 max_incoming_unidirectional_streams_;
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_config_test.cc b/quic/core/quic_config_test.cc
index c18c520..84e89c1 100644
--- a/quic/core/quic_config_test.cc
+++ b/quic/core/quic_config_test.cc
@@ -21,12 +21,17 @@
 namespace test {
 namespace {
 
-class QuicConfigTest : public QuicTest {
+class QuicConfigTest : public QuicTestWithParam<QuicTransportVersion> {
  protected:
   QuicConfig config_;
 };
 
-TEST_F(QuicConfigTest, ToHandshakeMessage) {
+// Run all tests with all versions of QUIC.
+INSTANTIATE_TEST_SUITE_P(QuicConfigTests,
+                         QuicConfigTest,
+                         ::testing::ValuesIn(AllSupportedTransportVersions()));
+
+TEST_P(QuicConfigTest, ToHandshakeMessage) {
   config_.SetInitialStreamFlowControlWindowToSend(
       kInitialStreamFlowControlWindowForTest);
   config_.SetInitialSessionFlowControlWindowToSend(
@@ -34,7 +39,7 @@
   config_.SetIdleNetworkTimeout(QuicTime::Delta::FromSeconds(5),
                                 QuicTime::Delta::FromSeconds(2));
   CryptoHandshakeMessage msg;
-  config_.ToHandshakeMessage(&msg);
+  config_.ToHandshakeMessage(&msg, GetParam());
 
   uint32_t value;
   QuicErrorCode error = msg.GetUint32(kICSL, &value);
@@ -50,7 +55,7 @@
   EXPECT_EQ(kInitialSessionFlowControlWindowForTest, value);
 }
 
-TEST_F(QuicConfigTest, ProcessClientHello) {
+TEST_P(QuicConfigTest, ProcessClientHello) {
   QuicConfig client_config;
   QuicTagVector cgst;
   cgst.push_back(kQBIC);
@@ -66,7 +71,7 @@
   copt.push_back(kTBBR);
   client_config.SetConnectionOptionsToSend(copt);
   CryptoHandshakeMessage msg;
-  client_config.ToHandshakeMessage(&msg);
+  client_config.ToHandshakeMessage(&msg, GetParam());
 
   std::string error_details;
   QuicTagVector initial_received_options;
@@ -96,7 +101,7 @@
             2 * kInitialSessionFlowControlWindowForTest);
 }
 
-TEST_F(QuicConfigTest, ProcessServerHello) {
+TEST_P(QuicConfigTest, ProcessServerHello) {
   QuicIpAddress host;
   host.FromString("127.0.3.1");
   const QuicSocketAddress kTestServerAddress = QuicSocketAddress(host, 1234);
@@ -115,7 +120,7 @@
   server_config.SetAlternateServerAddressToSend(kTestServerAddress);
   server_config.SetStatelessResetTokenToSend(kTestResetToken);
   CryptoHandshakeMessage msg;
-  server_config.ToHandshakeMessage(&msg);
+  server_config.ToHandshakeMessage(&msg, GetParam());
   std::string error_details;
   const QuicErrorCode error =
       config_.ProcessPeerHello(msg, SERVER, &error_details);
@@ -134,13 +139,13 @@
   EXPECT_EQ(kTestResetToken, config_.ReceivedStatelessResetToken());
 }
 
-TEST_F(QuicConfigTest, MissingOptionalValuesInCHLO) {
+TEST_P(QuicConfigTest, MissingOptionalValuesInCHLO) {
   CryptoHandshakeMessage msg;
   msg.SetValue(kICSL, 1);
 
   // Set all REQUIRED tags.
   msg.SetValue(kICSL, 1);
-  msg.SetValue(kMIDS, 1);
+  msg.SetValue(kMIBS, 1);
 
   // No error, as rest are optional.
   std::string error_details;
@@ -150,12 +155,12 @@
   EXPECT_TRUE(config_.negotiated());
 }
 
-TEST_F(QuicConfigTest, MissingOptionalValuesInSHLO) {
+TEST_P(QuicConfigTest, MissingOptionalValuesInSHLO) {
   CryptoHandshakeMessage msg;
 
   // Set all REQUIRED tags.
   msg.SetValue(kICSL, 1);
-  msg.SetValue(kMIDS, 1);
+  msg.SetValue(kMIBS, 1);
 
   // No error, as rest are optional.
   std::string error_details;
@@ -165,7 +170,7 @@
   EXPECT_TRUE(config_.negotiated());
 }
 
-TEST_F(QuicConfigTest, MissingValueInCHLO) {
+TEST_P(QuicConfigTest, MissingValueInCHLO) {
   // Server receives CHLO with missing kICSL.
   CryptoHandshakeMessage msg;
   std::string error_details;
@@ -174,7 +179,7 @@
   EXPECT_EQ(QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND, error);
 }
 
-TEST_F(QuicConfigTest, MissingValueInSHLO) {
+TEST_P(QuicConfigTest, MissingValueInSHLO) {
   // Client receives SHLO with missing kICSL.
   CryptoHandshakeMessage msg;
   std::string error_details;
@@ -183,21 +188,21 @@
   EXPECT_EQ(QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND, error);
 }
 
-TEST_F(QuicConfigTest, OutOfBoundSHLO) {
+TEST_P(QuicConfigTest, OutOfBoundSHLO) {
   QuicConfig server_config;
   server_config.SetIdleNetworkTimeout(
       QuicTime::Delta::FromSeconds(2 * kMaximumIdleTimeoutSecs),
       QuicTime::Delta::FromSeconds(2 * kMaximumIdleTimeoutSecs));
 
   CryptoHandshakeMessage msg;
-  server_config.ToHandshakeMessage(&msg);
+  server_config.ToHandshakeMessage(&msg, GetParam());
   std::string error_details;
   const QuicErrorCode error =
       config_.ProcessPeerHello(msg, SERVER, &error_details);
   EXPECT_EQ(QUIC_INVALID_NEGOTIATED_VALUE, error);
 }
 
-TEST_F(QuicConfigTest, InvalidFlowControlWindow) {
+TEST_P(QuicConfigTest, InvalidFlowControlWindow) {
   // QuicConfig should not accept an invalid flow control window to send to the
   // peer: the receive window must be at least the default of 16 Kb.
   QuicConfig config;
@@ -210,7 +215,7 @@
             config.GetInitialStreamFlowControlWindowToSend());
 }
 
-TEST_F(QuicConfigTest, HasClientSentConnectionOption) {
+TEST_P(QuicConfigTest, HasClientSentConnectionOption) {
   QuicConfig client_config;
   QuicTagVector copt;
   copt.push_back(kTBBR);
@@ -219,7 +224,7 @@
       kTBBR, Perspective::IS_CLIENT));
 
   CryptoHandshakeMessage msg;
-  client_config.ToHandshakeMessage(&msg);
+  client_config.ToHandshakeMessage(&msg, GetParam());
 
   std::string error_details;
   const QuicErrorCode error =
@@ -233,14 +238,14 @@
       config_.HasClientSentConnectionOption(kTBBR, Perspective::IS_SERVER));
 }
 
-TEST_F(QuicConfigTest, DontSendClientConnectionOptions) {
+TEST_P(QuicConfigTest, DontSendClientConnectionOptions) {
   QuicConfig client_config;
   QuicTagVector copt;
   copt.push_back(kTBBR);
   client_config.SetClientConnectionOptions(copt);
 
   CryptoHandshakeMessage msg;
-  client_config.ToHandshakeMessage(&msg);
+  client_config.ToHandshakeMessage(&msg, GetParam());
 
   std::string error_details;
   const QuicErrorCode error =
@@ -251,7 +256,7 @@
   EXPECT_FALSE(config_.HasReceivedConnectionOptions());
 }
 
-TEST_F(QuicConfigTest, HasClientRequestedIndependentOption) {
+TEST_P(QuicConfigTest, HasClientRequestedIndependentOption) {
   QuicConfig client_config;
   QuicTagVector client_opt;
   client_opt.push_back(kRENO);
@@ -267,7 +272,7 @@
       kTBBR, Perspective::IS_CLIENT));
 
   CryptoHandshakeMessage msg;
-  client_config.ToHandshakeMessage(&msg);
+  client_config.ToHandshakeMessage(&msg, GetParam());
 
   std::string error_details;
   const QuicErrorCode error =
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 0bae7d5..d80b55f 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -4988,7 +4988,7 @@
   client_config.SetIdleNetworkTimeout(
       QuicTime::Delta::FromSeconds(kDefaultIdleTimeoutSecs),
       QuicTime::Delta::FromSeconds(kDefaultIdleTimeoutSecs));
-  client_config.ToHandshakeMessage(&msg);
+  client_config.ToHandshakeMessage(&msg, connection_.transport_version());
   const QuicErrorCode error =
       config.ProcessPeerHello(msg, CLIENT, &error_details);
   EXPECT_EQ(QUIC_NO_ERROR, error);
@@ -5055,7 +5055,7 @@
   client_config.SetIdleNetworkTimeout(
       QuicTime::Delta::FromSeconds(kDefaultIdleTimeoutSecs),
       QuicTime::Delta::FromSeconds(kDefaultIdleTimeoutSecs));
-  client_config.ToHandshakeMessage(&msg);
+  client_config.ToHandshakeMessage(&msg, connection_.transport_version());
   const QuicErrorCode error =
       config.ProcessPeerHello(msg, CLIENT, &error_details);
   EXPECT_EQ(QUIC_NO_ERROR, error);
@@ -5111,7 +5111,7 @@
   client_config.SetIdleNetworkTimeout(
       QuicTime::Delta::FromSeconds(kDefaultIdleTimeoutSecs),
       QuicTime::Delta::FromSeconds(kDefaultIdleTimeoutSecs));
-  client_config.ToHandshakeMessage(&msg);
+  client_config.ToHandshakeMessage(&msg, connection_.transport_version());
   const QuicErrorCode error =
       config.ProcessPeerHello(msg, CLIENT, &error_details);
   EXPECT_EQ(QUIC_NO_ERROR, error);
diff --git a/quic/core/quic_crypto_client_handshaker.cc b/quic/core/quic_crypto_client_handshaker.cc
index 57d8c28..b0c26be 100644
--- a/quic/core/quic_crypto_client_handshaker.cc
+++ b/quic/core/quic_crypto_client_handshaker.cc
@@ -263,7 +263,7 @@
   DCHECK(session()->config() != nullptr);
   // Send all the options, regardless of whether we're sending an
   // inchoate or subsequent hello.
-  session()->config()->ToHandshakeMessage(&out);
+  session()->config()->ToHandshakeMessage(&out, session()->transport_version());
 
   if (!cached->IsComplete(session()->connection()->clock()->WallNow())) {
     crypto_config_->FillInchoateClientHello(
diff --git a/quic/core/quic_crypto_server_handshaker.cc b/quic/core/quic_crypto_server_handshaker.cc
index 9e46d6d..025e391 100644
--- a/quic/core/quic_crypto_server_handshaker.cc
+++ b/quic/core/quic_crypto_server_handshaker.cc
@@ -208,7 +208,7 @@
 
   session()->OnConfigNegotiated();
 
-  config->ToHandshakeMessage(reply.get());
+  config->ToHandshakeMessage(reply.get(), session()->transport_version());
 
   // Receiving a full CHLO implies the client is prepared to decrypt with
   // the new server write key.  We can start to encrypt with the new server
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index 9633053..b686068 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -2699,8 +2699,13 @@
 
   if (!GetQuicRestartFlag(quic_do_not_override_connection_id)) {
     if (header->source_connection_id_included == CONNECTION_ID_PRESENT) {
+      DCHECK_EQ(Perspective::IS_CLIENT, perspective_);
+      DCHECK_EQ(IETF_QUIC_LONG_HEADER_PACKET, header->form);
+      if (!header->destination_connection_id.IsEmpty()) {
+        set_detailed_error("Client connection ID not supported yet.");
+        return false;
+      }
       // Set destination connection ID to source connection ID.
-      DCHECK_EQ(EmptyQuicConnectionId(), header->destination_connection_id);
       header->destination_connection_id = header->source_connection_id;
     } else if (header->destination_connection_id_included ==
                CONNECTION_ID_ABSENT) {
@@ -6104,5 +6109,162 @@
   return QUIC_NO_ERROR;
 }
 
+// static
+bool QuicFramer::WriteClientVersionNegotiationProbePacket(
+    char* packet_bytes,
+    QuicByteCount packet_length,
+    const char* destination_connection_id_bytes,
+    uint8_t destination_connection_id_length) {
+  if (packet_bytes == nullptr) {
+    QUIC_BUG << "Invalid packet_bytes";
+    return false;
+  }
+  if (packet_length < kMinPacketSizeForVersionNegotiation ||
+      packet_length > 65535) {
+    QUIC_BUG << "Invalid packet_length";
+    return false;
+  }
+  if (destination_connection_id_length > kQuicMaxConnectionIdLength ||
+      (destination_connection_id_length > 0 &&
+       destination_connection_id_length < 4)) {
+    QUIC_BUG << "Invalid connection_id_length";
+    return false;
+  }
+  // clang-format off
+  static const unsigned char packet_start_bytes[] = {
+    // IETF long header with fixed bit set, type initial, all-0 encrypted bits.
+    0xc0,
+    // Version, part of the IETF space reserved for negotiation.
+    // This intentionally differs from QuicVersionReservedForNegotiation()
+    // to allow differentiating them over the wire.
+    0xca, 0xba, 0xda, 0xba,
+  };
+  // clang-format on
+  static_assert(sizeof(packet_start_bytes) == 5, "bad packet_start_bytes size");
+  QuicDataWriter writer(packet_length, packet_bytes);
+  if (!writer.WriteBytes(packet_start_bytes, sizeof(packet_start_bytes))) {
+    QUIC_BUG << "Failed to write packet start";
+    return false;
+  }
+
+  QuicConnectionId destination_connection_id(destination_connection_id_bytes,
+                                             destination_connection_id_length);
+  if (!AppendIetfConnectionIds(/*version_flag=*/true, destination_connection_id,
+                               EmptyQuicConnectionId(), &writer)) {
+    QUIC_BUG << "Failed to write connection IDs";
+    return false;
+  }
+  // Add 8 bytes of zeroes followed by 8 bytes of ones to ensure that this does
+  // not parse with any known version. The zeroes make sure that packet numbers,
+  // retry token lengths and payload lengths are parsed as zero, and if the
+  // zeroes are treated as padding frames, 0xff is known to not parse as a
+  // valid frame type.
+  if (!writer.WriteUInt64(0) ||
+      !writer.WriteUInt64(std::numeric_limits<uint64_t>::max())) {
+    QUIC_BUG << "Failed to write 18 bytes";
+    return false;
+  }
+  // Make sure the polite greeting below is padded to a 16-byte boundary to
+  // make it easier to read in tcpdump.
+  while (writer.length() % 16 != 0) {
+    if (!writer.WriteUInt8(0)) {
+      QUIC_BUG << "Failed to write padding byte";
+      return false;
+    }
+  }
+  // Add a polite greeting in case a human sees this in tcpdump.
+  static const char polite_greeting[] =
+      "This packet only exists to trigger IETF QUIC version negotiation. "
+      "Please respond with a Version Negotiation packet indicating what "
+      "versions you support. Thank you and have a nice day.";
+  if (!writer.WriteBytes(polite_greeting, sizeof(polite_greeting))) {
+    QUIC_BUG << "Failed to write polite greeting";
+    return false;
+  }
+  // Fill the rest of the packet with zeroes.
+  writer.WritePadding();
+  DCHECK_EQ(0u, writer.remaining());
+  return true;
+}
+
+// static
+bool QuicFramer::ParseServerVersionNegotiationProbeResponse(
+    const char* packet_bytes,
+    QuicByteCount packet_length,
+    char* source_connection_id_bytes,
+    uint8_t* source_connection_id_length_out,
+    std::string* detailed_error) {
+  if (detailed_error == nullptr) {
+    QUIC_BUG << "Invalid error_details";
+    return false;
+  }
+  *detailed_error = "";
+  if (packet_bytes == nullptr) {
+    *detailed_error = "Invalid packet_bytes";
+    return false;
+  }
+  if (packet_length < 6) {
+    *detailed_error = "Invalid packet_length";
+    return false;
+  }
+  if (source_connection_id_bytes == nullptr) {
+    *detailed_error = "Invalid source_connection_id_bytes";
+    return false;
+  }
+  if (source_connection_id_length_out == nullptr) {
+    *detailed_error = "Invalid source_connection_id_length_out";
+    return false;
+  }
+  QuicDataReader reader(packet_bytes, packet_length);
+  uint8_t type_byte = 0;
+  if (!reader.ReadUInt8(&type_byte)) {
+    *detailed_error = "Failed to read type byte";
+    return false;
+  }
+  if ((type_byte & 0x80) == 0) {
+    *detailed_error = "Packet does not have long header";
+    return false;
+  }
+  uint32_t version = 0;
+  if (!reader.ReadUInt32(&version)) {
+    *detailed_error = "Failed to read version";
+    return false;
+  }
+  if (version != 0) {
+    *detailed_error = "Packet is not a version negotiation packet";
+    return false;
+  }
+  uint8_t expected_connection_id_length = 0,
+          destination_connection_id_length = 0, source_connection_id_length = 0;
+  if (!ProcessAndValidateIetfConnectionIdLength(
+          &reader, UnsupportedQuicVersion(),
+          /*should_update_expected_connection_id_length=*/true,
+          &expected_connection_id_length, &destination_connection_id_length,
+          &source_connection_id_length, detailed_error)) {
+    return false;
+  }
+  if (destination_connection_id_length != 0) {
+    *detailed_error = "Received unexpected destination connection ID length";
+    return false;
+  }
+  QuicConnectionId destination_connection_id, source_connection_id;
+  if (!reader.ReadConnectionId(&destination_connection_id,
+                               destination_connection_id_length)) {
+    *detailed_error = "Failed to read destination connection ID";
+    return false;
+  }
+  if (!reader.ReadConnectionId(&source_connection_id,
+                               source_connection_id_length)) {
+    *detailed_error = "Failed to read source connection ID";
+    return false;
+  }
+
+  memcpy(source_connection_id_bytes, source_connection_id.data(),
+         source_connection_id_length);
+  *source_connection_id_length_out = source_connection_id_length;
+
+  return true;
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 }  // namespace quic
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h
index bded585..ab0e8d7 100644
--- a/quic/core/quic_framer.h
+++ b/quic/core/quic_framer.h
@@ -588,6 +588,38 @@
 
   void EnableMultiplePacketNumberSpacesSupport();
 
+  // Writes an array of bytes that, if sent as a UDP datagram, will trigger
+  // IETF QUIC Version Negotiation on servers. The bytes will be written to
+  // |packet_bytes|, which must point to |packet_length| bytes of memory.
+  // |packet_length| must be in the range [1200, 65535].
+  // |destination_connection_id_bytes| will be sent as the destination
+  // connection ID, and must point to |destination_connection_id_length| bytes
+  // of memory. |destination_connection_id_length| must be either 0 or in the
+  // range [4,18]. When targeting Google servers, it is recommended to use a
+  // |destination_connection_id_length| of 8.
+  static bool WriteClientVersionNegotiationProbePacket(
+      char* packet_bytes,
+      QuicByteCount packet_length,
+      const char* destination_connection_id_bytes,
+      uint8_t destination_connection_id_length);
+
+  // Parses a packet which a QUIC server sent in response to a packet sent by
+  // WriteClientVersionNegotiationProbePacket. |packet_bytes| must point to
+  // |packet_length| bytes in memory which represent the response.
+  // |packet_length| must be greater or equal to 6. This method will fill in
+  // |source_connection_id_bytes| which must point to at least 18 bytes in
+  // memory. |source_connection_id_length_out| will contain the length of the
+  // received source connection ID, which on success will match the contents of
+  // the destination connection ID passed in to
+  // WriteClientVersionNegotiationProbePacket. In the case of a failure,
+  // |detailed_error| will be filled in with an explanation of what failed.
+  static bool ParseServerVersionNegotiationProbeResponse(
+      const char* packet_bytes,
+      QuicByteCount packet_length,
+      char* source_connection_id_bytes,
+      uint8_t* source_connection_id_length_out,
+      std::string* detailed_error);
+
  private:
   friend class test::QuicFramerPeer;
 
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index e83fd1b..347df84 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -13603,6 +13603,147 @@
   CheckFramingBoundaries(packet, QUIC_INVALID_PACKET_HEADER);
 }
 
+TEST_P(QuicFramerTest, WriteClientVersionNegotiationProbePacket) {
+  // clang-format off
+  static const char expected_packet[1200] = {
+    // IETF long header with fixed bit set, type initial, all-0 encrypted bits.
+    0xc0,
+    // Version, part of the IETF space reserved for negotiation.
+    0xca, 0xba, 0xda, 0xba,
+    // Destination connection ID length 8, source connection ID length 0.
+    0x50,
+    // 8-byte destination connection ID.
+    0x56, 0x4e, 0x20, 0x70, 0x6c, 0x7a, 0x20, 0x21,
+    // 8 bytes of zeroes followed by 8 bytes of ones to ensure that this does
+    // not parse with any known version.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    // 2 bytes of zeroes to pad to 16 byte boundary.
+    0x00, 0x00,
+    // A polite greeting in case a human sees this in tcpdump.
+    0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x61, 0x63,
+    0x6b, 0x65, 0x74, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+    0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x20,
+    0x74, 0x6f, 0x20, 0x74, 0x72, 0x69, 0x67, 0x67,
+    0x65, 0x72, 0x20, 0x49, 0x45, 0x54, 0x46, 0x20,
+    0x51, 0x55, 0x49, 0x43, 0x20, 0x76, 0x65, 0x72,
+    0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x65, 0x67,
+    0x6f, 0x74, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+    0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65,
+    0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64,
+    0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x20,
+    0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20,
+    0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74,
+    0x69, 0x6f, 0x6e, 0x20, 0x70, 0x61, 0x63, 0x6b,
+    0x65, 0x74, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63,
+    0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x77, 0x68,
+    0x61, 0x74, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69,
+    0x6f, 0x6e, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20,
+    0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e,
+    0x20, 0x54, 0x68, 0x61, 0x6e, 0x6b, 0x20, 0x79,
+    0x6f, 0x75, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x68,
+    0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x6e, 0x69,
+    0x63, 0x65, 0x20, 0x64, 0x61, 0x79, 0x2e, 0x00,
+  };
+  // clang-format on
+  char packet[1200];
+  char destination_connection_id_bytes[] = {0x56, 0x4e, 0x20, 0x70,
+                                            0x6c, 0x7a, 0x20, 0x21};
+  EXPECT_TRUE(QuicFramer::WriteClientVersionNegotiationProbePacket(
+      packet, sizeof(packet), destination_connection_id_bytes,
+      sizeof(destination_connection_id_bytes)));
+  test::CompareCharArraysWithHexError("constructed packet", expected_packet,
+                                      sizeof(expected_packet), packet,
+                                      sizeof(packet));
+  QuicEncryptedPacket encrypted(reinterpret_cast<const char*>(packet),
+                                sizeof(packet), false);
+  // Make sure we fail to parse this packet for the version under test.
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+  if (framer_.transport_version() <= QUIC_VERSION_43) {
+    // We can only parse the connection ID with an IETF parser.
+    return;
+  }
+  ASSERT_TRUE(visitor_.header_.get());
+  QuicConnectionId probe_payload_connection_id(
+      reinterpret_cast<const char*>(destination_connection_id_bytes),
+      sizeof(destination_connection_id_bytes));
+  EXPECT_EQ(probe_payload_connection_id,
+            visitor_.header_.get()->destination_connection_id);
+}
+
+TEST_P(QuicFramerTest, ParseServerVersionNegotiationProbeResponse) {
+  // clang-format off
+  const char packet[] = {
+    // IETF long header with fixed bit set, type initial, all-0 encrypted bits.
+    0xc0,
+    // Version of 0, indicating version negotiation.
+    0x00, 0x00, 0x00, 0x00,
+    // Destination connection ID length 0, source connection ID length 8.
+    0x05,
+    // 8-byte source connection ID.
+    0x56, 0x4e, 0x20, 0x70, 0x6c, 0x7a, 0x20, 0x21,
+    // A few supported versions.
+    0xaa, 0xaa, 0xaa, 0xaa,
+    QUIC_VERSION_BYTES,
+  };
+  // clang-format on
+  char probe_payload_bytes[] = {0x56, 0x4e, 0x20, 0x70, 0x6c, 0x7a, 0x20, 0x21};
+  char parsed_probe_payload_bytes[kQuicMaxConnectionIdLength] = {};
+  uint8_t parsed_probe_payload_length = 0;
+  std::string parse_detailed_error = "";
+  EXPECT_TRUE(QuicFramer::ParseServerVersionNegotiationProbeResponse(
+      reinterpret_cast<const char*>(packet), sizeof(packet),
+      reinterpret_cast<char*>(parsed_probe_payload_bytes),
+      &parsed_probe_payload_length, &parse_detailed_error));
+  EXPECT_EQ("", parse_detailed_error);
+  test::CompareCharArraysWithHexError(
+      "parsed probe", probe_payload_bytes, sizeof(probe_payload_bytes),
+      parsed_probe_payload_bytes, parsed_probe_payload_length);
+}
+
+TEST_P(QuicFramerTest, ClientConnectionIdNotSupportedYet) {
+  if (GetQuicRestartFlag(quic_do_not_override_connection_id)) {
+    // This check is currently only performed when this flag is disabled.
+    return;
+  }
+  if (framer_.transport_version() <= QUIC_VERSION_43) {
+    // This test requires an IETF long header.
+    return;
+  }
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+  const unsigned char type_byte =
+      framer_.transport_version() == QUIC_VERSION_44 ? 0xFC : 0xD3;
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (long header with packet type ZERO_RTT_PROTECTED and
+    // 4-byte packet number)
+    type_byte,
+    // version
+    QUIC_VERSION_BYTES,
+    // destination connection ID length
+    0x50,
+    // destination connection ID
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // long header packet length
+    0x05,
+    // packet number
+    0x12, 0x34, 0x56, 0x00,
+    // padding frame
+    0x00,
+  };
+  // clang-format on
+  EXPECT_FALSE(framer_.ProcessPacket(
+      QuicEncryptedPacket(AsChars(packet), QUIC_ARRAYSIZE(packet), false)));
+  EXPECT_EQ(QUIC_INVALID_PACKET_HEADER, framer_.error());
+  if (!QuicUtils::VariableLengthConnectionIdAllowedForVersion(
+          framer_.transport_version())) {
+    EXPECT_EQ("Invalid ConnectionId length.", framer_.detailed_error());
+  } else {
+    EXPECT_EQ("Client connection ID not supported yet.",
+              framer_.detailed_error());
+  }
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 0545fa6..d56689f 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -55,10 +55,13 @@
       config_(config),
       stream_id_manager_(this,
                          kDefaultMaxStreamsPerConnection,
-                         config_.GetMaxIncomingDynamicStreamsToSend()),
-      v99_streamid_manager_(this,
-                            kDefaultMaxStreamsPerConnection,
-                            config_.GetMaxIncomingDynamicStreamsToSend()),
+                         config_.GetMaxIncomingBidirectionalStreamsToSend()),
+      v99_streamid_manager_(
+          this,
+          kDefaultMaxStreamsPerConnection,
+          kDefaultMaxStreamsPerConnection,
+          config_.GetMaxIncomingBidirectionalStreamsToSend(),
+          config_.GetMaxIncomingUnidirectionalStreamsToSend()),
       num_dynamic_incoming_streams_(0),
       num_draining_incoming_streams_(0),
       num_outgoing_static_streams_(0),
@@ -973,23 +976,33 @@
 void QuicSession::OnConfigNegotiated() {
   connection_->SetFromConfig(config_);
 
-  uint32_t max_streams = 0;
-  if (config_.HasReceivedMaxIncomingDynamicStreams()) {
-    max_streams = config_.ReceivedMaxIncomingDynamicStreams();
-  }
-  QUIC_DVLOG(1) << "Setting max_open_outgoing_streams_ to " << max_streams;
   if (connection_->transport_version() == QUIC_VERSION_99) {
-    // TODO: When transport negotiation knows about bi- and uni- directional
-    // streams, this should be modified to indicate which one to the manager.
-    // Currently, BOTH are set to the same value.
-    // TODO(fkastenholz): AdjustMax is cognizant of the number of static streams
-    // and sets the maximum to be max_streams + number_of_statics. This should
-    // eventually be removed from IETF QUIC. -- Replace the call with
-    // ConfigureMaxOpen...
-    v99_streamid_manager_.AdjustMaxOpenOutgoingStreams(max_streams);
+    uint32_t max_streams = 0;
+    if (config_.HasReceivedMaxIncomingBidirectionalStreams()) {
+      max_streams = config_.ReceivedMaxIncomingBidirectionalStreams();
+    }
+    QUIC_DVLOG(1) << "Setting Bidirectional outgoing_max_streams_ to "
+                  << max_streams;
+    v99_streamid_manager_.AdjustMaxOpenOutgoingBidirectionalStreams(
+        max_streams);
+
+    max_streams = 0;
+    if (config_.HasReceivedMaxIncomingUnidirectionalStreams()) {
+      max_streams = config_.ReceivedMaxIncomingUnidirectionalStreams();
+    }
+    QUIC_DVLOG(1) << "Setting Unidirectional outgoing_max_streams_ to "
+                  << max_streams;
+    v99_streamid_manager_.AdjustMaxOpenOutgoingUnidirectionalStreams(
+        max_streams);
   } else {
+    uint32_t max_streams = 0;
+    if (config_.HasReceivedMaxIncomingBidirectionalStreams()) {
+      max_streams = config_.ReceivedMaxIncomingBidirectionalStreams();
+    }
+    QUIC_DVLOG(1) << "Setting max_open_outgoing_streams_ to " << max_streams;
     stream_id_manager_.set_max_open_outgoing_streams(max_streams);
   }
+
   if (perspective() == Perspective::IS_SERVER) {
     if (config_.HasReceivedConnectionOptions()) {
       // The following variations change the initial receive flow control
@@ -1014,17 +1027,19 @@
     config_.SetStatelessResetTokenToSend(GetStatelessResetToken());
   }
 
-  // A small number of additional incoming streams beyond the limit should be
-  // allowed. This helps avoid early connection termination when FIN/RSTs for
-  // old streams are lost or arrive out of order.
-  // Use a minimum number of additional streams, or a percentage increase,
-  // whichever is larger.
-  uint32_t max_incoming_streams_to_send =
-      config_.GetMaxIncomingDynamicStreamsToSend();
   if (connection_->transport_version() == QUIC_VERSION_99) {
-    v99_streamid_manager_.SetMaxOpenIncomingStreams(
-        max_incoming_streams_to_send);
+    v99_streamid_manager_.SetMaxOpenIncomingBidirectionalStreams(
+        config_.GetMaxIncomingBidirectionalStreamsToSend());
+    v99_streamid_manager_.SetMaxOpenIncomingUnidirectionalStreams(
+        config_.GetMaxIncomingUnidirectionalStreamsToSend());
   } else {
+    // A small number of additional incoming streams beyond the limit should be
+    // allowed. This helps avoid early connection termination when FIN/RSTs for
+    // old streams are lost or arrive out of order.
+    // Use a minimum number of additional streams, or a percentage increase,
+    // whichever is larger.
+    uint32_t max_incoming_streams_to_send =
+        config_.GetMaxIncomingBidirectionalStreamsToSend();
     uint32_t max_incoming_streams =
         std::max(max_incoming_streams_to_send + kMaxStreamsMinimumIncrement,
                  static_cast<uint32_t>(max_incoming_streams_to_send *
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index f16477c..7da8ec3 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -419,6 +419,10 @@
   static void RecordConnectionCloseAtServer(QuicErrorCode error,
                                             ConnectionCloseSource source);
 
+  inline QuicTransportVersion transport_version() const {
+    return connection_->transport_version();
+  }
+
  protected:
   using StaticStreamMap = QuicSmallMap<QuicStreamId, QuicStream*, 2>;
 
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 8f418c0..95bb6d4 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -69,7 +69,8 @@
         kInitialStreamFlowControlWindowForTest);
     session()->config()->SetInitialSessionFlowControlWindowToSend(
         kInitialSessionFlowControlWindowForTest);
-    session()->config()->ToHandshakeMessage(&msg);
+    session()->config()->ToHandshakeMessage(
+        &msg, session()->connection()->transport_version());
     const QuicErrorCode error =
         session()->config()->ProcessPeerHello(msg, CLIENT, &error_details);
     EXPECT_EQ(QUIC_NO_ERROR, error);
@@ -707,31 +708,86 @@
 TEST_P(QuicSessionTestServer, ManyAvailableBidirectionalStreams) {
   // When max_open_streams_ is 200, should be able to create 200 streams
   // out-of-order, that is, creating the one with the largest stream ID first.
-  QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, 200);
+  if (transport_version() == QUIC_VERSION_99) {
+    QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(&session_, 200);
+    // Smaller limit on unidirectional streams to help detect crossed wires.
+    QuicSessionPeer::SetMaxOpenIncomingUnidirectionalStreams(&session_, 50);
+  } else {
+    QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, 200);
+  }
+  // Create a stream at the start of the range.
   QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
-  // Create one stream.
   EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(stream_id));
-  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
 
   // Create the largest stream ID of a threatened total of 200 streams.
   // GetNth... starts at 0, so for 200 streams, get the 199th.
+  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
   EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(
                          GetNthClientInitiatedBidirectionalId(199)));
+
+  if (transport_version() == QUIC_VERSION_99) {
+    // If IETF QUIC, check to make sure that creating bidirectional
+    // streams does not mess up the unidirectional streams.
+    stream_id = GetNthClientInitiatedUnidirectionalId(0);
+    EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(stream_id));
+    // Now try to get the last possible unidirectional stream.
+    EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(
+                           GetNthClientInitiatedUnidirectionalId(49)));
+    // and this should fail because it exceeds the unidirectional limit
+    // (but not the bi-)
+    EXPECT_CALL(
+        *connection_,
+        CloseConnection(QUIC_INVALID_STREAM_ID,
+                        "Stream id 798 would exceed stream count limit 50",
+                        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET
+
+                        ))
+        .Times(1);
+    EXPECT_EQ(nullptr, session_.GetOrCreateDynamicStream(
+                           GetNthClientInitiatedUnidirectionalId(199)));
+  }
 }
 
 TEST_P(QuicSessionTestServer, ManyAvailableUnidirectionalStreams) {
   // When max_open_streams_ is 200, should be able to create 200 streams
   // out-of-order, that is, creating the one with the largest stream ID first.
-  QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, 200);
-  QuicStreamId stream_id = GetNthClientInitiatedUnidirectionalId(0);
+  if (transport_version() == QUIC_VERSION_99) {
+    QuicSessionPeer::SetMaxOpenIncomingUnidirectionalStreams(&session_, 200);
+    // Smaller limit on unidirectional streams to help detect crossed wires.
+    QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(&session_, 50);
+  } else {
+    QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, 200);
+  }
   // Create one stream.
+  QuicStreamId stream_id = GetNthClientInitiatedUnidirectionalId(0);
   EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(stream_id));
-  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
 
   // Create the largest stream ID of a threatened total of 200 streams.
   // GetNth... starts at 0, so for 200 streams, get the 199th.
+  EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
   EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(
                          GetNthClientInitiatedUnidirectionalId(199)));
+  if (transport_version() == QUIC_VERSION_99) {
+    // If IETF QUIC, check to make sure that creating unidirectional
+    // streams does not mess up the bidirectional streams.
+    stream_id = GetNthClientInitiatedBidirectionalId(0);
+    EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(stream_id));
+    // Now try to get the last possible bidirectional stream.
+    EXPECT_NE(nullptr, session_.GetOrCreateDynamicStream(
+                           GetNthClientInitiatedBidirectionalId(49)));
+    // and this should fail because it exceeds the bnidirectional limit
+    // (but not the uni-)
+    EXPECT_CALL(
+        *connection_,
+        CloseConnection(QUIC_INVALID_STREAM_ID,
+                        "Stream id 800 would exceed stream count limit 51",
+                        ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET
+
+                        ))
+        .Times(1);
+    EXPECT_EQ(nullptr, session_.GetOrCreateDynamicStream(
+                           GetNthClientInitiatedBidirectionalId(199)));
+  }
 }
 
 TEST_P(QuicSessionTestServer, DebugDFatalIfMarkingClosedStreamWriteBlocked) {
@@ -1304,7 +1360,7 @@
     QuicStreamOffset offset = crypto_stream->stream_bytes_written();
     QuicConfig config;
     CryptoHandshakeMessage crypto_message;
-    config.ToHandshakeMessage(&crypto_message);
+    config.ToHandshakeMessage(&crypto_message, transport_version());
     crypto_stream->SendHandshakeMessage(crypto_message);
     char buf[1000];
     QuicDataWriter writer(1000, buf, NETWORK_BYTE_ORDER);
@@ -1528,7 +1584,12 @@
   // with a FIN or RST then we send an RST to refuse streams. For V99 the
   // connection is closed.
   const QuicStreamId kMaxStreams = 5;
-  QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+  if (transport_version() == QUIC_VERSION_99) {
+    QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(&session_,
+                                                            kMaxStreams);
+  } else {
+    QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+  }
   const QuicStreamId kFirstStreamId = GetNthClientInitiatedBidirectionalId(0);
   const QuicStreamId kFinalStreamId =
       GetNthClientInitiatedBidirectionalId(kMaxStreams);
@@ -1653,7 +1714,12 @@
   }
   EXPECT_CALL(*connection_, OnStreamReset(_, QUIC_REFUSED_STREAM)).Times(0);
   const QuicStreamId kMaxStreams = 5;
-  QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+  if (transport_version() == QUIC_VERSION_99) {
+    QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(&session_,
+                                                            kMaxStreams);
+  } else {
+    QuicSessionPeer::SetMaxOpenIncomingStreams(&session_, kMaxStreams);
+  }
 
   // Create kMaxStreams + 1 data streams, and mark them draining.
   const QuicStreamId kFirstStreamId = GetNthClientInitiatedBidirectionalId(0);
diff --git a/quic/core/quic_stream_id_manager.cc b/quic/core/quic_stream_id_manager.cc
index f68401a..94a8487 100644
--- a/quic/core/quic_stream_id_manager.cc
+++ b/quic/core/quic_stream_id_manager.cc
@@ -130,12 +130,13 @@
   outgoing_max_streams_ = std::min(
       static_cast<QuicStreamCount>(max_open_streams),
       QuicUtils::GetMaxStreamCount(unidirectional_, session_->perspective()));
-
   return true;
 }
 
 void QuicStreamIdManager::SetMaxOpenOutgoingStreams(size_t max_open_streams) {
   QUIC_BUG_IF(!using_default_max_streams_);
+  // TODO(fkastenholz): when static streams are removed from I-Quic, this
+  // should be revised to invoke ConfigureMaxOpen...
   AdjustMaxOpenOutgoingStreams(max_open_streams);
 }
 
@@ -143,8 +144,8 @@
 // including static streams. If the new stream limit wraps, will peg
 // the limit at the implementation max.
 // TODO(fkastenholz): AdjustMax is cognizant of the number of static streams and
-// sets the maximum to be max_streams + number_of_statics. This should
-// eventually be removed from IETF QUIC.
+// sets the maximum to be max_streams + number_of_statics. This should be
+// removed from IETF QUIC when static streams are gone.
 void QuicStreamIdManager::AdjustMaxOpenOutgoingStreams(
     size_t max_open_streams) {
   if ((outgoing_static_stream_count_ + max_open_streams) < max_open_streams) {
diff --git a/quic/core/uber_quic_stream_id_manager.cc b/quic/core/uber_quic_stream_id_manager.cc
index e2261a0..75f7c6a 100644
--- a/quic/core/uber_quic_stream_id_manager.cc
+++ b/quic/core/uber_quic_stream_id_manager.cc
@@ -11,17 +11,19 @@
 
 UberQuicStreamIdManager::UberQuicStreamIdManager(
     QuicSession* session,
-    QuicStreamCount max_open_outgoing_streams,
-    QuicStreamCount max_open_incoming_streams)
+    QuicStreamCount max_open_outgoing_bidirectional_streams,
+    QuicStreamCount max_open_outgoing_unidirectional_streams,
+    QuicStreamCount max_open_incoming_bidirectional_streams,
+    QuicStreamCount max_open_incoming_unidirectional_streams)
     : bidirectional_stream_id_manager_(session,
                                        /*unidirectional=*/false,
-                                       max_open_outgoing_streams,
-                                       max_open_incoming_streams),
-      unidirectional_stream_id_manager_(session,
-                                        /*unidirectional=*/true,
-                                        max_open_outgoing_streams,
-                                        max_open_incoming_streams) {}
-
+                                       max_open_outgoing_bidirectional_streams,
+                                       max_open_incoming_bidirectional_streams),
+      unidirectional_stream_id_manager_(
+          session,
+          /*unidirectional=*/true,
+          max_open_outgoing_unidirectional_streams,
+          max_open_incoming_unidirectional_streams) {}
 void UberQuicStreamIdManager::RegisterStaticStream(QuicStreamId id) {
   if (QuicUtils::IsBidirectionalStreamId(id)) {
     bidirectional_stream_id_manager_.RegisterStaticStream(id);
@@ -30,35 +32,42 @@
   unidirectional_stream_id_manager_.RegisterStaticStream(id);
 }
 
-void UberQuicStreamIdManager::ConfigureMaxOpenOutgoingStreams(
+void UberQuicStreamIdManager::AdjustMaxOpenOutgoingUnidirectionalStreams(
     size_t max_streams) {
-  // TODO(fkastenholz): When transport configuration negotiation knows uni- vs
-  // bi- directionality, this method needs modifying to select the correct
-  // manager to configure.
-  bidirectional_stream_id_manager_.ConfigureMaxOpenOutgoingStreams(max_streams);
-  unidirectional_stream_id_manager_.ConfigureMaxOpenOutgoingStreams(
-      max_streams);
+  unidirectional_stream_id_manager_.AdjustMaxOpenOutgoingStreams(max_streams);
+}
+void UberQuicStreamIdManager::AdjustMaxOpenOutgoingBidirectionalStreams(
+    size_t max_streams) {
+  bidirectional_stream_id_manager_.AdjustMaxOpenOutgoingStreams(max_streams);
 }
 
-void UberQuicStreamIdManager::AdjustMaxOpenOutgoingStreams(size_t max_streams) {
-  // TODO(fkastenholz): When transport configuration negotiation knows uni- vs
-  // bi- directionality, this method needs modifying to select the correct
-  // manager to configure.
-  bidirectional_stream_id_manager_.AdjustMaxOpenOutgoingStreams(max_streams);
-  unidirectional_stream_id_manager_.AdjustMaxOpenOutgoingStreams(max_streams);
+void UberQuicStreamIdManager::ConfigureMaxOpenOutgoingBidirectionalStreams(
+    size_t max_streams) {
+  bidirectional_stream_id_manager_.ConfigureMaxOpenOutgoingStreams(max_streams);
+}
+void UberQuicStreamIdManager::ConfigureMaxOpenOutgoingUnidirectionalStreams(
+    size_t max_streams) {
+  unidirectional_stream_id_manager_.ConfigureMaxOpenOutgoingStreams(
+      max_streams);
 }
 
 // TODO(fkastenholz): SetMax is cognizant of the number of static streams and
 // sets the maximum to be max_streams + number_of_statics. This should
 // eventually be removed from IETF QUIC.
-void UberQuicStreamIdManager::SetMaxOpenOutgoingStreams(
+void UberQuicStreamIdManager::SetMaxOpenOutgoingBidirectionalStreams(
     size_t max_open_streams) {
   bidirectional_stream_id_manager_.SetMaxOpenOutgoingStreams(max_open_streams);
+}
+void UberQuicStreamIdManager::SetMaxOpenOutgoingUnidirectionalStreams(
+    size_t max_open_streams) {
   unidirectional_stream_id_manager_.SetMaxOpenOutgoingStreams(max_open_streams);
 }
-void UberQuicStreamIdManager::SetMaxOpenIncomingStreams(
+void UberQuicStreamIdManager::SetMaxOpenIncomingBidirectionalStreams(
     size_t max_open_streams) {
   bidirectional_stream_id_manager_.SetMaxOpenIncomingStreams(max_open_streams);
+}
+void UberQuicStreamIdManager::SetMaxOpenIncomingUnidirectionalStreams(
+    size_t max_open_streams) {
   unidirectional_stream_id_manager_.SetMaxOpenIncomingStreams(max_open_streams);
 }
 
diff --git a/quic/core/uber_quic_stream_id_manager.h b/quic/core/uber_quic_stream_id_manager.h
index 41fa12b..288f0c0 100644
--- a/quic/core/uber_quic_stream_id_manager.h
+++ b/quic/core/uber_quic_stream_id_manager.h
@@ -20,9 +20,12 @@
 // unidirectional stream IDs, respectively.
 class QUIC_EXPORT_PRIVATE UberQuicStreamIdManager {
  public:
-  UberQuicStreamIdManager(QuicSession* session,
-                          QuicStreamCount max_open_outgoing_streams,
-                          QuicStreamCount max_open_incoming_streams);
+  UberQuicStreamIdManager(
+      QuicSession* session,
+      QuicStreamCount max_open_outgoing_bidirectional_streams,
+      QuicStreamCount max_open_outgoing_unidirectional_streams,
+      QuicStreamCount max_open_incoming_bidirectional_streams,
+      QuicStreamCount max_open_incoming_unidirectional_streams);
 
   // Called when a stream with |stream_id| is registered as a static stream.
   void RegisterStaticStream(QuicStreamId id);
@@ -30,21 +33,26 @@
   // Sets the maximum outgoing stream count as a result of doing the transport
   // configuration negotiation. Forces the limit to max_streams, regardless of
   // static streams.
-  void ConfigureMaxOpenOutgoingStreams(size_t max_streams);
+  void ConfigureMaxOpenOutgoingBidirectionalStreams(size_t max_streams);
+  void ConfigureMaxOpenOutgoingUnidirectionalStreams(size_t max_streams);
 
   // Sets the limits to max_open_streams + number of static streams
   // in existence. SetMaxOpenOutgoingStreams will QUIC_BUG if it is called
-  // after getting the first MAX_STREAMS frame.
+  // after getting the first MAX_STREAMS frame or the transport configuration
+  // was done.
   // TODO(fkastenholz): SetMax is cognizant of the number of static streams and
   // sets the maximum to be max_streams + number_of_statics. This should
   // eventually be removed from IETF QUIC.
-  void SetMaxOpenOutgoingStreams(size_t max_open_streams);
-  void SetMaxOpenIncomingStreams(size_t max_open_streams);
+  void SetMaxOpenOutgoingBidirectionalStreams(size_t max_open_streams);
+  void SetMaxOpenOutgoingUnidirectionalStreams(size_t max_open_streams);
+  void SetMaxOpenIncomingBidirectionalStreams(size_t max_open_streams);
+  void SetMaxOpenIncomingUnidirectionalStreams(size_t max_open_streams);
 
   // Sets the outgoing stream count to the number of static streams + max
   // outgoing streams.  Unlike SetMaxOpenOutgoingStreams, this method will
   // not QUIC_BUG if called after getting  the first MAX_STREAMS frame.
-  void AdjustMaxOpenOutgoingStreams(size_t max_streams);
+  void AdjustMaxOpenOutgoingBidirectionalStreams(size_t max_streams);
+  void AdjustMaxOpenOutgoingUnidirectionalStreams(size_t max_streams);
 
   // Returns true if next outgoing bidirectional stream ID can be allocated.
   bool CanOpenNextOutgoingBidirectionalStream();
@@ -58,7 +66,7 @@
   // Returns the next outgoing unidirectional stream id.
   QuicStreamId GetNextOutgoingUnidirectionalStreamId();
 
-  // Returns true if allow to open the incoming |id|.
+  // Returns true if the incoming |id| is within the limit.
   bool MaybeIncreaseLargestPeerStreamId(QuicStreamId id);
 
   // Called when |id| is released.
diff --git a/quic/core/uber_quic_stream_id_manager_test.cc b/quic/core/uber_quic_stream_id_manager_test.cc
index ed08d8f..b9420bf 100644
--- a/quic/core/uber_quic_stream_id_manager_test.cc
+++ b/quic/core/uber_quic_stream_id_manager_test.cc
@@ -60,6 +60,28 @@
            kV99StreamIdIncrement * n;
   }
 
+  // TODO(fkastenholz): Existing tests can use these helper functions.
+  QuicStreamId GetNthPeerInitiatedBidirectionalStreamId(int n) {
+    return ((GetParam() == Perspective::IS_SERVER)
+                ? GetNthClientInitiatedBidirectionalId(n)
+                : GetNthServerInitiatedBidirectionalId(n));
+  }
+  QuicStreamId GetNthPeerInitiatedUnidirectionalStreamId(int n) {
+    return ((GetParam() == Perspective::IS_SERVER)
+                ? GetNthClientInitiatedUnidirectionalId(n)
+                : GetNthServerInitiatedUnidirectionalId(n));
+  }
+  QuicStreamId GetNthSelfInitiatedBidirectionalStreamId(int n) {
+    return ((GetParam() == Perspective::IS_CLIENT)
+                ? GetNthClientInitiatedBidirectionalId(n)
+                : GetNthServerInitiatedBidirectionalId(n));
+  }
+  QuicStreamId GetNthSelfInitiatedUnidirectionalStreamId(int n) {
+    return ((GetParam() == Perspective::IS_CLIENT)
+                ? GetNthClientInitiatedUnidirectionalId(n)
+                : GetNthServerInitiatedUnidirectionalId(n));
+  }
+
   QuicStreamId StreamCountToId(QuicStreamCount stream_count,
                                Perspective perspective,
                                bool bidirectional) {
@@ -129,17 +151,39 @@
 
 TEST_P(UberQuicStreamIdManagerTest, SetMaxOpenOutgoingStreams) {
   const size_t kNumMaxOutgoingStream = 123;
-  manager_->SetMaxOpenOutgoingStreams(kNumMaxOutgoingStream);
+  // Set the uni- and bi- directional limits to different values to ensure
+  // that they are managed separately.
+  manager_->SetMaxOpenOutgoingBidirectionalStreams(kNumMaxOutgoingStream);
+  manager_->SetMaxOpenOutgoingUnidirectionalStreams(kNumMaxOutgoingStream + 1);
   EXPECT_EQ(kNumMaxOutgoingStream,
             manager_->max_allowed_outgoing_bidirectional_streams());
-  EXPECT_EQ(kNumMaxOutgoingStream,
+  EXPECT_EQ(kNumMaxOutgoingStream + 1,
             manager_->max_allowed_outgoing_unidirectional_streams());
+  // Check that, for each directionality, we can open the correct number of
+  // streams.
+  int i = kNumMaxOutgoingStream;
+  while (i) {
+    EXPECT_TRUE(manager_->CanOpenNextOutgoingBidirectionalStream());
+    manager_->GetNextOutgoingBidirectionalStreamId();
+    EXPECT_TRUE(manager_->CanOpenNextOutgoingUnidirectionalStream());
+    manager_->GetNextOutgoingUnidirectionalStreamId();
+    i--;
+  }
+  // One more unidirectional
+  EXPECT_TRUE(manager_->CanOpenNextOutgoingUnidirectionalStream());
+  manager_->GetNextOutgoingUnidirectionalStreamId();
+
+  // Both should be exhausted...
+  EXPECT_FALSE(manager_->CanOpenNextOutgoingUnidirectionalStream());
+  EXPECT_FALSE(manager_->CanOpenNextOutgoingBidirectionalStream());
 }
 
 TEST_P(UberQuicStreamIdManagerTest, SetMaxOpenIncomingStreams) {
   const size_t kNumMaxIncomingStreams = 456;
-  manager_->SetMaxOpenIncomingStreams(kNumMaxIncomingStreams);
-  EXPECT_EQ(kNumMaxIncomingStreams,
+  manager_->SetMaxOpenIncomingUnidirectionalStreams(kNumMaxIncomingStreams);
+  // Do +1 for bidirectional to ensure that uni- and bi- get properly set.
+  manager_->SetMaxOpenIncomingBidirectionalStreams(kNumMaxIncomingStreams + 1);
+  EXPECT_EQ(kNumMaxIncomingStreams + 1,
             manager_->GetMaxAllowdIncomingBidirectionalStreams());
   EXPECT_EQ(kNumMaxIncomingStreams,
             manager_->GetMaxAllowdIncomingUnidirectionalStreams());
@@ -147,6 +191,24 @@
             manager_->advertised_max_allowed_incoming_bidirectional_streams());
   EXPECT_EQ(manager_->actual_max_allowed_incoming_unidirectional_streams(),
             manager_->advertised_max_allowed_incoming_unidirectional_streams());
+  // Make sure that we can create kNumMaxIncomingStreams incoming unidirectional
+  // streams and kNumMaxIncomingStreams+1 incoming bidirectional streams.
+  size_t i;
+  for (i = 0; i < kNumMaxIncomingStreams; i++) {
+    EXPECT_TRUE(manager_->MaybeIncreaseLargestPeerStreamId(
+        GetNthPeerInitiatedUnidirectionalStreamId(i)));
+    EXPECT_TRUE(manager_->MaybeIncreaseLargestPeerStreamId(
+        GetNthPeerInitiatedBidirectionalStreamId(i)));
+  }
+  // Should be able to open the next bidirectional stream
+  EXPECT_TRUE(manager_->MaybeIncreaseLargestPeerStreamId(
+      GetNthPeerInitiatedBidirectionalStreamId(i)));
+
+  // We should have exhausted the counts, the next streams should fail
+  EXPECT_FALSE(manager_->MaybeIncreaseLargestPeerStreamId(
+      GetNthPeerInitiatedUnidirectionalStreamId(i)));
+  EXPECT_FALSE(manager_->MaybeIncreaseLargestPeerStreamId(
+      GetNthPeerInitiatedBidirectionalStreamId(i + 1)));
 }
 
 TEST_P(UberQuicStreamIdManagerTest, GetNextOutgoingStreamId) {
@@ -339,6 +401,44 @@
   }
 }
 
+TEST_P(UberQuicStreamIdManagerTest, SetMaxOpenOutgoingStreamsPlusFrame) {
+  const size_t kNumMaxOutgoingStream = 123;
+  // Set the uni- and bi- directional limits to different values to ensure
+  // that they are managed separately.
+  manager_->SetMaxOpenOutgoingBidirectionalStreams(kNumMaxOutgoingStream);
+  manager_->SetMaxOpenOutgoingUnidirectionalStreams(kNumMaxOutgoingStream + 1);
+  EXPECT_EQ(kNumMaxOutgoingStream,
+            manager_->max_allowed_outgoing_bidirectional_streams());
+  EXPECT_EQ(kNumMaxOutgoingStream + 1,
+            manager_->max_allowed_outgoing_unidirectional_streams());
+  // Check that, for each directionality, we can open the correct number of
+  // streams.
+  int i = kNumMaxOutgoingStream;
+  while (i) {
+    EXPECT_TRUE(manager_->CanOpenNextOutgoingBidirectionalStream());
+    manager_->GetNextOutgoingBidirectionalStreamId();
+    EXPECT_TRUE(manager_->CanOpenNextOutgoingUnidirectionalStream());
+    manager_->GetNextOutgoingUnidirectionalStreamId();
+    i--;
+  }
+  // One more unidirectional
+  EXPECT_TRUE(manager_->CanOpenNextOutgoingUnidirectionalStream());
+  manager_->GetNextOutgoingUnidirectionalStreamId();
+
+  // Both should be exhausted...
+  EXPECT_FALSE(manager_->CanOpenNextOutgoingUnidirectionalStream());
+  EXPECT_FALSE(manager_->CanOpenNextOutgoingBidirectionalStream());
+
+  // Now cons a MAX STREAMS frame for unidirectional streams to raise
+  // the limit.
+  QuicMaxStreamsFrame frame(1, kNumMaxOutgoingStream + 10,
+                            /*unidirectional=*/true);
+  manager_->OnMaxStreamsFrame(frame);
+  // We now should be able to get another uni- stream, but not a bi.
+  EXPECT_TRUE(manager_->CanOpenNextOutgoingUnidirectionalStream());
+  EXPECT_FALSE(manager_->CanOpenNextOutgoingBidirectionalStream());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/platform/api/quic_ip_address.h b/quic/platform/api/quic_ip_address.h
index 856efbe..6d507b4 100644
--- a/quic/platform/api/quic_ip_address.h
+++ b/quic/platform/api/quic_ip_address.h
@@ -76,8 +76,6 @@
   in_addr GetIPv4() const;
   in6_addr GetIPv6() const;
 
-  const QuicIpAddressImpl& impl() const { return impl_; }
-
  private:
   QuicIpAddressImpl impl_;
 };
diff --git a/quic/platform/api/quic_socket_address.cc b/quic/platform/api/quic_socket_address.cc
index dacd6ac..e0bb35c 100644
--- a/quic/platform/api/quic_socket_address.cc
+++ b/quic/platform/api/quic_socket_address.cc
@@ -9,7 +9,7 @@
 namespace quic {
 
 QuicSocketAddress::QuicSocketAddress(QuicIpAddress address, uint16_t port)
-    : impl_(address.impl(), port) {}
+    : impl_(address, port) {}
 
 QuicSocketAddress::QuicSocketAddress(const struct sockaddr_storage& saddr)
     : impl_(saddr) {}
diff --git a/quic/platform/api/quic_socket_address.h b/quic/platform/api/quic_socket_address.h
index 57689a7..85e61d5 100644
--- a/quic/platform/api/quic_socket_address.h
+++ b/quic/platform/api/quic_socket_address.h
@@ -39,7 +39,6 @@
   QuicIpAddress host() const;
   uint16_t port() const;
   sockaddr_storage generic_address() const;
-  const QuicSocketAddressImpl& impl() const { return impl_; }
 
  private:
   QuicSocketAddressImpl impl_;
diff --git a/quic/quartc/quartc_endpoint.cc b/quic/quartc/quartc_endpoint.cc
index fe87ca1..7676c59 100644
--- a/quic/quartc/quartc_endpoint.cc
+++ b/quic/quartc/quartc_endpoint.cc
@@ -67,17 +67,72 @@
 
 void QuartcClientEndpoint::Connect(QuartcPacketTransport* packet_transport) {
   packet_transport_ = packet_transport;
+  // For the first attempt to connect, use any version that the client supports.
+  current_versions_ = version_manager_->GetSupportedVersions();
   create_session_alarm_->Set(clock_->Now());
 }
 
 void QuartcClientEndpoint::OnCreateSessionAlarm() {
   session_ = CreateQuartcClientSession(
       config_, clock_, alarm_factory_, connection_helper_.get(),
-      version_manager_->GetSupportedVersions(), serialized_server_config_,
-      packet_transport_);
+      current_versions_, serialized_server_config_, packet_transport_);
+  session_->SetDelegate(this);
   delegate_->OnSessionCreated(session_.get());
 }
 
+void QuartcClientEndpoint::OnCryptoHandshakeComplete() {
+  delegate_->OnCryptoHandshakeComplete();
+}
+
+void QuartcClientEndpoint::OnConnectionWritable() {
+  delegate_->OnConnectionWritable();
+}
+
+void QuartcClientEndpoint::OnIncomingStream(QuartcStream* stream) {
+  delegate_->OnIncomingStream(stream);
+}
+
+void QuartcClientEndpoint::OnCongestionControlChange(
+    QuicBandwidth bandwidth_estimate,
+    QuicBandwidth pacing_rate,
+    QuicTime::Delta latest_rtt) {
+  delegate_->OnCongestionControlChange(bandwidth_estimate, pacing_rate,
+                                       latest_rtt);
+}
+
+void QuartcClientEndpoint::OnConnectionClosed(QuicErrorCode error_code,
+                                              const std::string& error_details,
+                                              ConnectionCloseSource source) {
+  // First, see if we can restart the session with a mutually-supported version.
+  if (error_code == QUIC_INVALID_VERSION && session_ &&
+      session_->connection() &&
+      !session_->connection()->server_supported_versions().empty()) {
+    for (const auto& client_version :
+         version_manager_->GetSupportedVersions()) {
+      if (QuicContainsValue(session_->connection()->server_supported_versions(),
+                            client_version)) {
+        // Found a mutually-supported version.  Reconnect using that version.
+        current_versions_.clear();
+        current_versions_.push_back(client_version);
+        create_session_alarm_->Set(clock_->Now());
+        return;
+      }
+    }
+  }
+
+  // Permanent version negotiation errors are forwarded to the |delegate_|,
+  // along with all other errors.
+  delegate_->OnConnectionClosed(error_code, error_details, source);
+}
+
+void QuartcClientEndpoint::OnMessageReceived(QuicStringPiece message) {
+  delegate_->OnMessageReceived(message);
+}
+
+void QuartcClientEndpoint::OnMessageSent(int64_t datagram_id) {
+  delegate_->OnMessageSent(datagram_id);
+}
+
 QuartcServerEndpoint::QuartcServerEndpoint(
     QuicAlarmFactory* alarm_factory,
     const QuicClock* clock,
@@ -115,6 +170,7 @@
 }
 
 void QuartcServerEndpoint::OnSessionCreated(QuartcSession* session) {
+  session->SetDelegate(delegate_);
   delegate_->OnSessionCreated(session);
 }
 
diff --git a/quic/quartc/quartc_endpoint.h b/quic/quartc/quartc_endpoint.h
index 1169403..6c7644c 100644
--- a/quic/quartc/quartc_endpoint.h
+++ b/quic/quartc/quartc_endpoint.h
@@ -10,6 +10,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h"
 #include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_clock.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
 #include "net/third_party/quiche/src/quic/quartc/quartc_connection_helper.h"
 #include "net/third_party/quiche/src/quic/quartc/quartc_crypto_helpers.h"
 #include "net/third_party/quiche/src/quic/quartc/quartc_dispatcher.h"
@@ -29,22 +30,23 @@
 // Endpoint (client or server) in a peer-to-peer Quartc connection.
 class QuartcEndpoint {
  public:
-  class Delegate {
+  class Delegate : public QuartcSession::Delegate {
    public:
     virtual ~Delegate() = default;
 
     // Called when an endpoint creates a new session, before any packets are
     // processed or sent.  The callee should perform any additional
-    // configuration required, such as setting a session delegate, before
+    // configuration required, such as setting up congestion control, before
     // returning.  |session| is owned by the endpoint, but remains safe to use
-    // until another call to |OnSessionCreated| occurs, at which point previous
-    // session is destroyed.
+    // until another call to |OnSessionCreated| or |OnConnectionClosed| occurs,
+    // at which point previous session may be destroyed.
+    //
+    // Callees must not change the |session|'s delegate.  The Endpoint itself
+    // manages the delegate and will forward calls.
+    //
+    // New calls to |OnSessionCreated| will only occur prior to
+    // |OnConnectionWritable|, during initial connection negotiation.
     virtual void OnSessionCreated(QuartcSession* session) = 0;
-
-    // Called if the endpoint fails to establish a session after a call to
-    // Connect.  (The most likely cause is a network idle timeout.)
-    virtual void OnConnectError(QuicErrorCode error,
-                                const std::string& error_details) = 0;
   };
 
   virtual ~QuartcEndpoint() = default;
@@ -58,7 +60,8 @@
 // Implementation of QuartcEndpoint which immediately (but asynchronously)
 // creates a session by scheduling a QuicAlarm.  Only suitable for use with the
 // client perspective.
-class QuartcClientEndpoint : public QuartcEndpoint {
+class QuartcClientEndpoint : public QuartcEndpoint,
+                             public QuartcSession::Delegate {
  public:
   // |alarm_factory|, |clock|, and |delegate| are owned by the caller and must
   // outlive the endpoint.
@@ -66,13 +69,26 @@
       QuicAlarmFactory* alarm_factory,
       const QuicClock* clock,
       QuicRandom* random,
-      Delegate* delegate,
+      QuartcEndpoint::Delegate* delegate,
       const QuartcSessionConfig& config,
       QuicStringPiece serialized_server_config,
       std::unique_ptr<QuicVersionManager> version_manager = nullptr);
 
   void Connect(QuartcPacketTransport* packet_transport) override;
 
+  // QuartcSession::Delegate overrides.
+  void OnCryptoHandshakeComplete() override;
+  void OnConnectionWritable() override;
+  void OnIncomingStream(QuartcStream* stream) override;
+  void OnCongestionControlChange(QuicBandwidth bandwidth_estimate,
+                                 QuicBandwidth pacing_rate,
+                                 QuicTime::Delta latest_rtt) override;
+  void OnConnectionClosed(QuicErrorCode error_code,
+                          const std::string& error_details,
+                          ConnectionCloseSource source) override;
+  void OnMessageReceived(QuicStringPiece message) override;
+  void OnMessageSent(int64_t datagram_id) override;
+
  private:
   friend class CreateSessionDelegate;
   class CreateSessionDelegate : public QuicAlarm::Delegate {
@@ -104,6 +120,18 @@
   // Version manager.  May be injected to control version negotiation in tests.
   std::unique_ptr<QuicVersionManager> version_manager_;
 
+  // Versions to be used when the next session is created.  The session will
+  // choose one of these versions for its connection attempt.
+  //
+  // If the connection does not succeed, the client session MAY try again using
+  // another version from this list, or it MAY simply fail with a
+  // QUIC_INVALID_VERSION error.  The latter occurs when it is not possible to
+  // upgrade a connection in-place (for example, if the way stream ids are
+  // allocated changes between versions).  This failure mode is handled by
+  // narrowing |current_versions_| to one of that is mutually-supported and
+  // reconnecting (with a new session).
+  ParsedQuicVersionVector current_versions_;
+
   // Alarm for creating sessions asynchronously.  The alarm is set when
   // Connect() is called.  When it fires, the endpoint creates a session and
   // calls the delegate.
diff --git a/quic/quartc/quartc_endpoint_test.cc b/quic/quartc/quartc_endpoint_test.cc
index 567ccdd..5f8c25d 100644
--- a/quic/quartc/quartc_endpoint_test.cc
+++ b/quic/quartc/quartc_endpoint_test.cc
@@ -30,18 +30,16 @@
                             &server_transport_,
                             QuicBandwidth::FromKBitsPerSecond(10000),
                             QuicTime::Delta::FromMilliseconds(1)),
-        server_session_delegate_(&server_stream_delegate_,
-                                 simulator_.GetClock()),
-        server_endpoint_delegate_(&server_session_delegate_),
+        server_endpoint_delegate_(&server_stream_delegate_,
+                                  simulator_.GetClock()),
         server_endpoint_(QuicMakeUnique<QuartcServerEndpoint>(
             simulator_.GetAlarmFactory(),
             simulator_.GetClock(),
             simulator_.GetRandomGenerator(),
             &server_endpoint_delegate_,
             QuartcSessionConfig())),
-        client_session_delegate_(&client_stream_delegate_,
-                                 simulator_.GetClock()),
-        client_endpoint_delegate_(&client_session_delegate_),
+        client_endpoint_delegate_(&client_stream_delegate_,
+                                  simulator_.GetClock()),
         client_endpoint_(QuicMakeUnique<QuartcClientEndpoint>(
             simulator_.GetAlarmFactory(),
             simulator_.GetClock(),
@@ -57,13 +55,11 @@
   simulator::SymmetricLink client_server_link_;
 
   FakeQuartcStreamDelegate server_stream_delegate_;
-  FakeQuartcSessionDelegate server_session_delegate_;
   FakeQuartcEndpointDelegate server_endpoint_delegate_;
 
   std::unique_ptr<QuartcServerEndpoint> server_endpoint_;
 
   FakeQuartcStreamDelegate client_stream_delegate_;
-  FakeQuartcSessionDelegate client_session_delegate_;
   FakeQuartcEndpointDelegate client_endpoint_delegate_;
 
   std::unique_ptr<QuartcClientEndpoint> client_endpoint_;
@@ -215,5 +211,57 @@
   EXPECT_EQ(client_endpoint_delegate_.session()->error(), QUIC_INVALID_VERSION);
 }
 
+// Tests that the client endpoint can create a new session in order to continue
+// version negotiation.
+TEST_F(QuartcEndpointTest,
+       QUIC_TEST_DISABLED_IN_CHROME(CreatesNewSessionWhenRequired)) {
+  // Setting this flag to true requires the client to create a new session when
+  // version negotiation fails.
+  SetQuicReloadableFlag(quic_no_client_conn_ver_negotiation, true);
+
+  // Note: for this test, we need support for two versions.  Which two shouldn't
+  // matter, but they must be enabled so that the version manager doesn't filter
+  // them out.
+  SetQuicReloadableFlag(quic_enable_version_46, true);
+
+  // Reset the client endpoint to prefer version 46 but also be capable of
+  // speaking version 43.
+  ParsedQuicVersionVector client_versions;
+  client_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_46});
+  client_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43});
+  client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>(
+      simulator_.GetAlarmFactory(), simulator_.GetClock(),
+      simulator_.GetRandomGenerator(), &client_endpoint_delegate_,
+      QuartcSessionConfig(),
+      /*serialized_server_config=*/"",
+      QuicMakeUnique<QuicVersionManager>(client_versions));
+
+  // Reset the server endpoint to only speak version 43.
+  ParsedQuicVersionVector server_versions;
+  server_versions.push_back({PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_43});
+  server_endpoint_ = QuicMakeUnique<QuartcServerEndpoint>(
+      simulator_.GetAlarmFactory(), simulator_.GetClock(),
+      simulator_.GetRandomGenerator(), &server_endpoint_delegate_,
+      QuartcSessionConfig(),
+      QuicMakeUnique<QuicVersionManager>(server_versions));
+
+  // The endpoints should be able to establish a connection using version 46.
+  server_endpoint_->Connect(&server_transport_);
+  client_endpoint_->Connect(&client_transport_);
+
+  ASSERT_TRUE(simulator_.RunUntil([this] {
+    return client_endpoint_delegate_.session() != nullptr &&
+           client_endpoint_delegate_.session()->IsEncryptionEstablished() &&
+           server_endpoint_delegate_.session() != nullptr &&
+           server_endpoint_delegate_.session()->IsEncryptionEstablished();
+  }));
+  EXPECT_EQ(client_endpoint_delegate_.session()->connection()->version(),
+            server_versions[0]);
+  EXPECT_EQ(server_endpoint_delegate_.session()->connection()->version(),
+            server_versions[0]);
+
+  EXPECT_EQ(2, client_endpoint_delegate_.num_sessions_created());
+}
+
 }  // namespace
 }  // namespace quic
diff --git a/quic/quartc/quartc_factory.cc b/quic/quartc/quartc_factory.cc
index 93bb03f..06871a2 100644
--- a/quic/quartc/quartc_factory.cc
+++ b/quic/quartc/quartc_factory.cc
@@ -71,16 +71,6 @@
   SetQuicFlag(FLAGS_quic_buffered_data_threshold,
               std::numeric_limits<int>::max());
 
-  // TODO(b/117157454): Perform version negotiation for Quartc outside of
-  // QuicSession/QuicConnection. Currently default of
-  // quic_restart_flag_quic_no_server_conn_ver_negotiation2 is false,
-  // but we fail blueprint test that sets all QUIC flags to true.
-  //
-  // Forcing flag to false to pass blueprint tests, but eventually we'll have
-  // to implement negotiation outside of QuicConnection.
-  SetQuicRestartFlag(quic_no_server_conn_ver_negotiation2, false);
-  SetQuicReloadableFlag(quic_no_client_conn_ver_negotiation, false);
-
   // Enable and request QUIC to include receive timestamps in ACK frames.
   SetQuicReloadableFlag(quic_send_timestamps, true);
 
@@ -190,7 +180,7 @@
   // incomplete streams, but targets 1 second for recovery. Increasing the
   // number of open streams gives sufficient headroom to recover before QUIC
   // refuses new streams.
-  quic_config.SetMaxIncomingDynamicStreamsToSend(1000);
+  quic_config.SetMaxIncomingBidirectionalStreamsToSend(1000);
 
   return quic_config;
 }
diff --git a/quic/quartc/quartc_fakes.h b/quic/quartc/quartc_fakes.h
index 7048a5e..13b65b1 100644
--- a/quic/quartc/quartc_fakes.h
+++ b/quic/quartc/quartc_fakes.h
@@ -18,37 +18,17 @@
 
 class FakeQuartcEndpointDelegate : public QuartcEndpoint::Delegate {
  public:
-  explicit FakeQuartcEndpointDelegate(QuartcSession::Delegate* session_delegate)
-      : session_delegate_(session_delegate) {}
+  explicit FakeQuartcEndpointDelegate(QuartcStream::Delegate* stream_delegate,
+                                      const QuicClock* clock)
+      : stream_delegate_(stream_delegate), clock_(clock) {}
 
   void OnSessionCreated(QuartcSession* session) override {
-    CHECK_EQ(session_, nullptr);
     CHECK_NE(session, nullptr);
     session_ = session;
-    session_->SetDelegate(session_delegate_);
     session_->StartCryptoHandshake();
+    ++num_sessions_created_;
   }
 
-  void OnConnectError(QuicErrorCode error,
-                      const std::string& error_details) override {
-    QUIC_LOG(FATAL)
-        << "Unexpected error during QuartcEndpoint::Connect(); error=" << error
-        << ", error_details=" << error_details;
-  }
-
-  QuartcSession* session() { return session_; }
-
- private:
-  QuartcSession::Delegate* session_delegate_;
-  QuartcSession* session_ = nullptr;
-};
-
-class FakeQuartcSessionDelegate : public QuartcSession::Delegate {
- public:
-  explicit FakeQuartcSessionDelegate(QuartcStream::Delegate* stream_delegate,
-                                     const QuicClock* clock)
-      : stream_delegate_(stream_delegate), clock_(clock) {}
-
   void OnConnectionWritable() override {
     QUIC_LOG(INFO) << "Connection writable!";
     if (!writable_time_.IsInitialized()) {
@@ -87,6 +67,10 @@
                                  QuicBandwidth pacing_rate,
                                  QuicTime::Delta latest_rtt) override {}
 
+  QuartcSession* session() { return session_; }
+
+  int num_sessions_created() const { return num_sessions_created_; }
+
   QuartcStream* last_incoming_stream() const { return last_incoming_stream_; }
 
   // Returns all received messages.
@@ -104,6 +88,12 @@
   QuicTime crypto_handshake_time() const { return crypto_handshake_time_; }
 
  private:
+  // Current session.
+  QuartcSession* session_ = nullptr;
+
+  // Number of new sessions created by the endpoint.
+  int num_sessions_created_ = 0;
+
   QuartcStream* last_incoming_stream_;
   std::vector<std::string> incoming_messages_;
   std::vector<int64_t> sent_datagram_ids_;
diff --git a/quic/quartc/quartc_session_test.cc b/quic/quartc/quartc_session_test.cc
index e150e03..87174dc 100644
--- a/quic/quartc/quartc_session_test.cc
+++ b/quic/quartc/quartc_session_test.cc
@@ -61,29 +61,25 @@
         QuicBandwidth::FromKBitsPerSecond(10 * 1000), kPropagationDelay);
 
     client_stream_delegate_ = QuicMakeUnique<FakeQuartcStreamDelegate>();
-    client_session_delegate_ = QuicMakeUnique<FakeQuartcSessionDelegate>(
+    client_session_delegate_ = QuicMakeUnique<FakeQuartcEndpointDelegate>(
         client_stream_delegate_.get(), simulator_.GetClock());
-    client_endpoint_delegate_ = QuicMakeUnique<FakeQuartcEndpointDelegate>(
-        client_session_delegate_.get());
 
     server_stream_delegate_ = QuicMakeUnique<FakeQuartcStreamDelegate>();
-    server_session_delegate_ = QuicMakeUnique<FakeQuartcSessionDelegate>(
+    server_session_delegate_ = QuicMakeUnique<FakeQuartcEndpointDelegate>(
         server_stream_delegate_.get(), simulator_.GetClock());
-    server_endpoint_delegate_ = QuicMakeUnique<FakeQuartcEndpointDelegate>(
-        server_session_delegate_.get());
 
     // No 0-rtt setup, because server config is empty.
     // CannotCreateDataStreamBeforeHandshake depends on 1-rtt setup.
     if (create_client_endpoint) {
       client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>(
           simulator_.GetAlarmFactory(), simulator_.GetClock(),
-          simulator_.GetRandomGenerator(), client_endpoint_delegate_.get(),
+          simulator_.GetRandomGenerator(), client_session_delegate_.get(),
           quic::QuartcSessionConfig(),
           /*serialized_server_config=*/"");
     }
     server_endpoint_ = QuicMakeUnique<QuartcServerEndpoint>(
         simulator_.GetAlarmFactory(), simulator_.GetClock(),
-        simulator_.GetRandomGenerator(), server_endpoint_delegate_.get(),
+        simulator_.GetRandomGenerator(), server_session_delegate_.get(),
         quic::QuartcSessionConfig());
   }
 
@@ -99,12 +95,12 @@
     client_endpoint_->Connect(client_transport_.get());
 
     CHECK(simulator_.RunUntil([this] {
-      return client_endpoint_delegate_->session() != nullptr &&
-             server_endpoint_delegate_->session() != nullptr;
+      return client_session_delegate_->session() != nullptr &&
+             server_session_delegate_->session() != nullptr;
     }));
 
-    client_peer_ = client_endpoint_delegate_->session();
-    server_peer_ = server_endpoint_delegate_->session();
+    client_peer_ = client_session_delegate_->session();
+    server_peer_ = server_session_delegate_->session();
   }
 
   // Runs all tasks scheduled in the next 200 ms.
@@ -214,11 +210,11 @@
     QuartcSession* const peer_sending =
         direction_from_server ? server_peer_ : client_peer_;
 
-    FakeQuartcSessionDelegate* const delegate_receiving =
+    FakeQuartcEndpointDelegate* const delegate_receiving =
         direction_from_server ? client_session_delegate_.get()
                               : server_session_delegate_.get();
 
-    FakeQuartcSessionDelegate* const delegate_sending =
+    FakeQuartcEndpointDelegate* const delegate_sending =
         direction_from_server ? server_session_delegate_.get()
                               : client_session_delegate_.get();
 
@@ -299,11 +295,9 @@
   std::unique_ptr<simulator::SymmetricLink> client_server_link_;
 
   std::unique_ptr<FakeQuartcStreamDelegate> client_stream_delegate_;
-  std::unique_ptr<FakeQuartcSessionDelegate> client_session_delegate_;
-  std::unique_ptr<FakeQuartcEndpointDelegate> client_endpoint_delegate_;
+  std::unique_ptr<FakeQuartcEndpointDelegate> client_session_delegate_;
   std::unique_ptr<FakeQuartcStreamDelegate> server_stream_delegate_;
-  std::unique_ptr<FakeQuartcSessionDelegate> server_session_delegate_;
-  std::unique_ptr<FakeQuartcEndpointDelegate> server_endpoint_delegate_;
+  std::unique_ptr<FakeQuartcEndpointDelegate> server_session_delegate_;
 
   std::unique_ptr<QuartcClientEndpoint> client_endpoint_;
   std::unique_ptr<QuartcServerEndpoint> server_endpoint_;
@@ -537,7 +531,7 @@
 
   client_endpoint_ = QuicMakeUnique<QuartcClientEndpoint>(
       simulator_.GetAlarmFactory(), simulator_.GetClock(),
-      simulator_.GetRandomGenerator(), client_endpoint_delegate_.get(),
+      simulator_.GetRandomGenerator(), client_session_delegate_.get(),
       QuartcSessionConfig(),
       // This is the key line here. It passes through the server config
       // from the server to the client.
@@ -549,8 +543,8 @@
   // client session should be created, but server won't be created yet.
   simulator_.RunFor(QuicTime::Delta::FromMilliseconds(1));
 
-  client_peer_ = client_endpoint_delegate_->session();
-  server_peer_ = server_endpoint_delegate_->session();
+  client_peer_ = client_session_delegate_->session();
+  server_peer_ = server_session_delegate_->session();
 
   ASSERT_NE(client_peer_, nullptr);
   ASSERT_EQ(server_peer_, nullptr);
diff --git a/quic/quartc/test/quartc_peer.cc b/quic/quartc/test/quartc_peer.cc
index 6857858..761349e 100644
--- a/quic/quartc/test/quartc_peer.cc
+++ b/quic/quartc/test/quartc_peer.cc
@@ -46,7 +46,6 @@
 void QuartcPeer::OnSessionCreated(QuartcSession* session) {
   session_ = session;
 
-  session_->SetDelegate(this);
   session_->StartCryptoHandshake();
 
   QuicByteCount largest_message_payload =
@@ -65,13 +64,6 @@
   }
 }
 
-void QuartcPeer::OnConnectError(QuicErrorCode error,
-                                const std::string& error_details) {
-  QUIC_LOG(WARNING) << "Connect failed, error=" << error
-                    << ", details=" << error_details;
-  SetEnabled(false);
-}
-
 void QuartcPeer::OnCryptoHandshakeComplete() {
   SetEnabled(true);
 }
diff --git a/quic/quartc/test/quartc_peer.h b/quic/quartc/test/quartc_peer.h
index 65d85fa..b96f488 100644
--- a/quic/quartc/test/quartc_peer.h
+++ b/quic/quartc/test/quartc_peer.h
@@ -39,7 +39,6 @@
 // datagram frames.  It also adjusts the bitrate of each source to fit within
 // the bandwidth available to the session.
 class QuartcPeer : public QuartcEndpoint::Delegate,
-                   public QuartcSession::Delegate,
                    public QuartcDataSource::Delegate {
  public:
   // Creates a QuartcPeer that sends data from a set of sources described by
@@ -73,8 +72,6 @@
 
   // QuartcEndpoint::Delegate overrides.
   void OnSessionCreated(QuartcSession* session) override;
-  void OnConnectError(QuicErrorCode error,
-                      const std::string& error_details) override;
 
   // QuartcSession::Delegate overrides.
   void OnCryptoHandshakeComplete() override;
diff --git a/quic/quartc/test/quic_trace_interceptor.cc b/quic/quartc/test/quic_trace_interceptor.cc
index c2fa6ec..741c542 100644
--- a/quic/quartc/test/quic_trace_interceptor.cc
+++ b/quic/quartc/test/quic_trace_interceptor.cc
@@ -33,9 +33,38 @@
   delegate_->OnSessionCreated(session);
 }
 
-void QuicTraceInterceptor::OnConnectError(QuicErrorCode error,
-                                          const std::string& details) {
-  delegate_->OnConnectError(error, details);
+void QuicTraceInterceptor::OnCryptoHandshakeComplete() {
+  delegate_->OnCryptoHandshakeComplete();
+}
+
+void QuicTraceInterceptor::OnConnectionWritable() {
+  delegate_->OnConnectionWritable();
+}
+
+void QuicTraceInterceptor::OnIncomingStream(QuartcStream* stream) {
+  delegate_->OnIncomingStream(stream);
+}
+
+void QuicTraceInterceptor::OnCongestionControlChange(
+    QuicBandwidth bandwidth_estimate,
+    QuicBandwidth pacing_rate,
+    QuicTime::Delta latest_rtt) {
+  delegate_->OnCongestionControlChange(bandwidth_estimate, pacing_rate,
+                                       latest_rtt);
+}
+
+void QuicTraceInterceptor::OnConnectionClosed(QuicErrorCode error_code,
+                                              const std::string& error_details,
+                                              ConnectionCloseSource source) {
+  delegate_->OnConnectionClosed(error_code, error_details, source);
+}
+
+void QuicTraceInterceptor::OnMessageReceived(QuicStringPiece message) {
+  delegate_->OnMessageReceived(message);
+}
+
+void QuicTraceInterceptor::OnMessageSent(int64_t datagram_id) {
+  delegate_->OnMessageSent(datagram_id);
 }
 
 void QuicTraceInterceptor::SetDelegate(QuartcEndpoint::Delegate* delegate) {
diff --git a/quic/quartc/test/quic_trace_interceptor.h b/quic/quartc/test/quic_trace_interceptor.h
index bb731b1..ee79d1e 100644
--- a/quic/quartc/test/quic_trace_interceptor.h
+++ b/quic/quartc/test/quic_trace_interceptor.h
@@ -28,7 +28,17 @@
 
   // QuartcEndpointIntercept overrides.
   void OnSessionCreated(QuartcSession* session) override;
-  void OnConnectError(QuicErrorCode error, const std::string& details) override;
+  void OnCryptoHandshakeComplete() override;
+  void OnConnectionWritable() override;
+  void OnIncomingStream(QuartcStream* stream) override;
+  void OnCongestionControlChange(QuicBandwidth bandwidth_estimate,
+                                 QuicBandwidth pacing_rate,
+                                 QuicTime::Delta latest_rtt) override;
+  void OnConnectionClosed(QuicErrorCode error_code,
+                          const std::string& error_details,
+                          ConnectionCloseSource source) override;
+  void OnMessageReceived(QuicStringPiece message) override;
+  void OnMessageSent(int64_t datagram_id) override;
   void SetDelegate(QuartcEndpoint::Delegate* delegate) override;
 
  private:
diff --git a/quic/test_tools/quic_config_peer.cc b/quic/test_tools/quic_config_peer.cc
index f5a5b31..f9ce04f 100644
--- a/quic/test_tools/quic_config_peer.cc
+++ b/quic/test_tools/quic_config_peer.cc
@@ -45,10 +45,16 @@
 }
 
 // static
-void QuicConfigPeer::SetReceivedMaxIncomingDynamicStreams(
+void QuicConfigPeer::SetReceivedMaxIncomingBidirectionalStreams(
     QuicConfig* config,
     uint32_t max_streams) {
-  config->max_incoming_dynamic_streams_.SetReceivedValue(max_streams);
+  config->max_incoming_bidirectional_streams_.SetReceivedValue(max_streams);
+}
+// static
+void QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
+    QuicConfig* config,
+    uint32_t max_streams) {
+  config->max_incoming_unidirectional_streams_.SetReceivedValue(max_streams);
 }
 
 // static
diff --git a/quic/test_tools/quic_config_peer.h b/quic/test_tools/quic_config_peer.h
index 8869d27..7faee7e 100644
--- a/quic/test_tools/quic_config_peer.h
+++ b/quic/test_tools/quic_config_peer.h
@@ -33,8 +33,10 @@
 
   static void SetReceivedDisableConnectionMigration(QuicConfig* config);
 
-  static void SetReceivedMaxIncomingDynamicStreams(QuicConfig* config,
-                                                   uint32_t max_streams);
+  static void SetReceivedMaxIncomingBidirectionalStreams(QuicConfig* config,
+                                                         uint32_t max_streams);
+  static void SetReceivedMaxIncomingUnidirectionalStreams(QuicConfig* config,
+                                                          uint32_t max_streams);
 
   static void SetConnectionOptionsToSend(QuicConfig* config,
                                          const QuicTagVector& options);
diff --git a/quic/test_tools/quic_session_peer.cc b/quic/test_tools/quic_session_peer.cc
index 176e22a..0683fd7 100644
--- a/quic/test_tools/quic_session_peer.cc
+++ b/quic/test_tools/quic_session_peer.cc
@@ -39,23 +39,73 @@
 void QuicSessionPeer::SetMaxOpenIncomingStreams(QuicSession* session,
                                                 uint32_t max_streams) {
   if (session->connection()->transport_version() == QUIC_VERSION_99) {
-    session->v99_streamid_manager_.SetMaxOpenIncomingStreams(max_streams);
+    QUIC_BUG << "SetmaxOpenIncomingStreams deprecated for IETF QUIC/V99";
+    session->v99_streamid_manager_.SetMaxOpenIncomingUnidirectionalStreams(
+        max_streams);
+    session->v99_streamid_manager_.SetMaxOpenIncomingBidirectionalStreams(
+        max_streams);
     return;
   }
   session->stream_id_manager_.set_max_open_incoming_streams(max_streams);
 }
 
 // static
+void QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(
+    QuicSession* session,
+    uint32_t max_streams) {
+  DCHECK_EQ(QUIC_VERSION_99, session->connection()->transport_version())
+      << "SetmaxOpenIncomingBidirectionalStreams not supported for Google "
+         "QUIC/not-V99";
+  session->v99_streamid_manager_.SetMaxOpenIncomingBidirectionalStreams(
+      max_streams);
+}
+// static
+void QuicSessionPeer::SetMaxOpenIncomingUnidirectionalStreams(
+    QuicSession* session,
+    uint32_t max_streams) {
+  DCHECK_EQ(QUIC_VERSION_99, session->connection()->transport_version())
+      << "SetmaxOpenIncomingUnidirectionalStreams not supported for Google "
+         "QUIC/not-V99";
+  session->v99_streamid_manager_.SetMaxOpenIncomingUnidirectionalStreams(
+      max_streams);
+}
+
+// static
 void QuicSessionPeer::SetMaxOpenOutgoingStreams(QuicSession* session,
                                                 uint32_t max_streams) {
   if (session->connection()->transport_version() == QUIC_VERSION_99) {
-    session->v99_streamid_manager_.SetMaxOpenOutgoingStreams(max_streams);
+    QUIC_BUG << "SetmaxOpenOutgoingStreams deprecated for IETF QUIC/V99";
+    session->v99_streamid_manager_.SetMaxOpenOutgoingUnidirectionalStreams(
+        max_streams);
+    session->v99_streamid_manager_.SetMaxOpenOutgoingBidirectionalStreams(
+        max_streams);
     return;
   }
   session->stream_id_manager_.set_max_open_outgoing_streams(max_streams);
 }
 
 // static
+void QuicSessionPeer::SetMaxOpenOutgoingBidirectionalStreams(
+    QuicSession* session,
+    uint32_t max_streams) {
+  DCHECK_EQ(QUIC_VERSION_99, session->connection()->transport_version())
+      << "SetmaxOpenOutgoingBidirectionalStreams not supported for Google "
+         "QUIC/not-V99";
+  session->v99_streamid_manager_.SetMaxOpenOutgoingBidirectionalStreams(
+      max_streams);
+}
+// static
+void QuicSessionPeer::SetMaxOpenOutgoingUnidirectionalStreams(
+    QuicSession* session,
+    uint32_t max_streams) {
+  DCHECK_EQ(QUIC_VERSION_99, session->connection()->transport_version())
+      << "SetmaxOpenOutgoingUnidirectionalStreams not supported for Google "
+         "QUIC/not-V99";
+  session->v99_streamid_manager_.SetMaxOpenOutgoingUnidirectionalStreams(
+      max_streams);
+}
+
+// static
 QuicCryptoStream* QuicSessionPeer::GetMutableCryptoStream(
     QuicSession* session) {
   return session->GetMutableCryptoStream();
diff --git a/quic/test_tools/quic_session_peer.h b/quic/test_tools/quic_session_peer.h
index 30e358f..994a36c 100644
--- a/quic/test_tools/quic_session_peer.h
+++ b/quic/test_tools/quic_session_peer.h
@@ -32,10 +32,24 @@
       QuicSession* session);
   static void SetNextOutgoingBidirectionalStreamId(QuicSession* session,
                                                    QuicStreamId id);
+  // Following is only for Google-QUIC, will QUIC_BUG if called for IETF
+  // QUIC.
   static void SetMaxOpenIncomingStreams(QuicSession* session,
                                         uint32_t max_streams);
+  // Following two are only for IETF-QUIC, will QUIC_BUG if called for Google
+  // QUIC.
+  static void SetMaxOpenIncomingBidirectionalStreams(QuicSession* session,
+                                                     uint32_t max_streams);
+  static void SetMaxOpenIncomingUnidirectionalStreams(QuicSession* session,
+                                                      uint32_t max_streams);
+
   static void SetMaxOpenOutgoingStreams(QuicSession* session,
                                         uint32_t max_streams);
+  static void SetMaxOpenOutgoingBidirectionalStreams(QuicSession* session,
+                                                     uint32_t max_streams);
+  static void SetMaxOpenOutgoingUnidirectionalStreams(QuicSession* session,
+                                                      uint32_t max_streams);
+
   static QuicCryptoStream* GetMutableCryptoStream(QuicSession* session);
   static QuicWriteBlockedList* GetWriteBlockedStreams(QuicSession* session);
   static QuicStream* GetOrCreateDynamicStream(QuicSession* session,
diff --git a/quic/test_tools/quic_test_utils.cc b/quic/test_tools/quic_test_utils.cc
index d74e0ba..00cd34a 100644
--- a/quic/test_tools/quic_test_utils.cc
+++ b/quic/test_tools/quic_test_utils.cc
@@ -1051,7 +1051,7 @@
       kInitialStreamFlowControlWindowForTest);
   config.SetInitialSessionFlowControlWindowToSend(
       kInitialSessionFlowControlWindowForTest);
-  QuicConfigPeer::SetReceivedMaxIncomingDynamicStreams(
+  QuicConfigPeer::SetReceivedMaxIncomingBidirectionalStreams(
       &config, kDefaultMaxStreamsPerConnection);
   // Default enable NSTP.
   // This is unnecessary for versions > 44
diff --git a/quic/test_tools/simulator/quic_endpoint.cc b/quic/test_tools/simulator/quic_endpoint.cc
index 61d253f..043eae9 100644
--- a/quic/test_tools/simulator/quic_endpoint.cc
+++ b/quic/test_tools/simulator/quic_endpoint.cc
@@ -113,7 +113,7 @@
   CryptoHandshakeMessage peer_hello;
   peer_hello.SetValue(kICSL,
                       static_cast<uint32_t>(kMaximumIdleTimeoutSecs - 1));
-  peer_hello.SetValue(kMIDS,
+  peer_hello.SetValue(kMIBS,
                       static_cast<uint32_t>(kDefaultMaxStreamsPerConnection));
   QuicConfig config;
   QuicErrorCode error_code = config.ProcessPeerHello(
diff --git a/quic/tools/fake_proof_verifier.h b/quic/tools/fake_proof_verifier.h
new file mode 100644
index 0000000..ffeb6e9
--- /dev/null
+++ b/quic/tools/fake_proof_verifier.h
@@ -0,0 +1,49 @@
+// 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_TOOLS_FAKE_PROOF_VERIFIER_H_
+#define QUICHE_QUIC_TOOLS_FAKE_PROOF_VERIFIER_H_
+
+#include "net/third_party/quiche/src/quic/core/crypto/proof_verifier.h"
+
+namespace quic {
+
+// ProofVerifier implementation which always returns success.
+class FakeProofVerifier : public ProofVerifier {
+ public:
+  ~FakeProofVerifier() override {}
+  QuicAsyncStatus VerifyProof(
+      const std::string& /*hostname*/,
+      const uint16_t /*port*/,
+      const std::string& /*server_config*/,
+      QuicTransportVersion /*quic_version*/,
+      QuicStringPiece /*chlo_hash*/,
+      const std::vector<std::string>& /*certs*/,
+      const std::string& /*cert_sct*/,
+      const std::string& /*signature*/,
+      const ProofVerifyContext* /*context*/,
+      std::string* /*error_details*/,
+      std::unique_ptr<ProofVerifyDetails>* /*details*/,
+      std::unique_ptr<ProofVerifierCallback> /*callback*/) override {
+    return QUIC_SUCCESS;
+  }
+  QuicAsyncStatus VerifyCertChain(
+      const std::string& /*hostname*/,
+      const std::vector<std::string>& /*certs*/,
+      const std::string& /*ocsp_response*/,
+      const std::string& /*cert_sct*/,
+      const ProofVerifyContext* /*context*/,
+      std::string* /*error_details*/,
+      std::unique_ptr<ProofVerifyDetails>* /*details*/,
+      std::unique_ptr<ProofVerifierCallback> /*callback*/) override {
+    return QUIC_SUCCESS;
+  }
+  std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override {
+    return nullptr;
+  }
+};
+
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_TOOLS_FAKE_PROOF_VERIFIER_H_
diff --git a/quic/tools/quic_client.cc b/quic/tools/quic_client.cc
index b736c71..e7fe6b4 100644
--- a/quic/tools/quic_client.cc
+++ b/quic/tools/quic_client.cc
@@ -85,7 +85,7 @@
     QuicConnection* connection) {
   return QuicMakeUnique<QuicSimpleClientSession>(
       *config(), supported_versions, connection, server_id(), crypto_config(),
-      push_promise_index(), drop_response_body_);
+      push_promise_index(), drop_response_body());
 }
 
 QuicClientEpollNetworkHelper* QuicClient::epoll_network_helper() {
diff --git a/quic/tools/quic_client.h b/quic/tools/quic_client.h
index b84c597..0e708ec 100644
--- a/quic/tools/quic_client.h
+++ b/quic/tools/quic_client.h
@@ -67,13 +67,8 @@
   QuicClientEpollNetworkHelper* epoll_network_helper();
   const QuicClientEpollNetworkHelper* epoll_network_helper() const;
 
-  void set_drop_response_body(bool drop_response_body) {
-    drop_response_body_ = drop_response_body;
-  }
-
  private:
   friend class test::QuicClientPeer;
-  bool drop_response_body_ = false;
 };
 
 }  // namespace quic
diff --git a/quic/tools/quic_client_bin.cc b/quic/tools/quic_client_bin.cc
index ff711a5..1ec47a9 100644
--- a/quic/tools/quic_client_bin.cc
+++ b/quic/tools/quic_client_bin.cc
@@ -60,6 +60,7 @@
 #include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_system_event_loop.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.h"
 #include "net/third_party/quiche/src/quic/tools/quic_client.h"
 #include "net/third_party/quiche/src/quic/tools/quic_url.h"
 
@@ -70,40 +71,6 @@
 using quic::QuicTextUtils;
 using quic::QuicUrl;
 
-class FakeProofVerifier : public quic::ProofVerifier {
- public:
-  ~FakeProofVerifier() override {}
-  quic::QuicAsyncStatus VerifyProof(
-      const std::string& /*hostname*/,
-      const uint16_t /*port*/,
-      const std::string& /*server_config*/,
-      quic::QuicTransportVersion /*quic_version*/,
-      quic::QuicStringPiece /*chlo_hash*/,
-      const std::vector<std::string>& /*certs*/,
-      const std::string& /*cert_sct*/,
-      const std::string& /*signature*/,
-      const quic::ProofVerifyContext* /*context*/,
-      std::string* /*error_details*/,
-      std::unique_ptr<quic::ProofVerifyDetails>* /*details*/,
-      std::unique_ptr<quic::ProofVerifierCallback> /*callback*/) override {
-    return quic::QUIC_SUCCESS;
-  }
-  quic::QuicAsyncStatus VerifyCertChain(
-      const std::string& /*hostname*/,
-      const std::vector<std::string>& /*certs*/,
-      const std::string& /*ocsp_response*/,
-      const std::string& /*cert_sct*/,
-      const quic::ProofVerifyContext* /*context*/,
-      std::string* /*error_details*/,
-      std::unique_ptr<quic::ProofVerifyDetails>* /*details*/,
-      std::unique_ptr<quic::ProofVerifierCallback> /*callback*/) override {
-    return quic::QUIC_SUCCESS;
-  }
-  std::unique_ptr<quic::ProofVerifyContext> CreateDefaultContext() override {
-    return nullptr;
-  }
-};
-
 QuicSocketAddress LookupAddress(std::string host, std::string port) {
   addrinfo hint;
   memset(&hint, 0, sizeof(hint));
@@ -285,7 +252,7 @@
   const int32_t num_requests(GetQuicFlag(FLAGS_num_requests));
   std::unique_ptr<quic::ProofVerifier> proof_verifier;
   if (GetQuicFlag(FLAGS_disable_certificate_verification)) {
-    proof_verifier = quic::QuicMakeUnique<FakeProofVerifier>();
+    proof_verifier = quic::QuicMakeUnique<quic::FakeProofVerifier>();
   } else {
     proof_verifier = quic::CreateDefaultProofVerifier();
   }
@@ -331,8 +298,8 @@
   header_block[":path"] = url.PathParamsQuery();
 
   // Append any additional headers supplied on the command line.
-  for (QuicStringPiece sp :
-       QuicTextUtils::Split(GetQuicFlag(FLAGS_headers), ';')) {
+  const std::string headers = GetQuicFlag(FLAGS_headers);
+  for (QuicStringPiece sp : QuicTextUtils::Split(headers, ';')) {
     QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&sp);
     if (sp.empty()) {
       continue;
@@ -394,18 +361,18 @@
 
     size_t response_code = client.latest_response_code();
     if (response_code >= 200 && response_code < 300) {
-      std::cerr << "Request succeeded (" << response_code << ")." << std::endl;
+      std::cout << "Request succeeded (" << response_code << ")." << std::endl;
     } else if (response_code >= 300 && response_code < 400) {
       if (GetQuicFlag(FLAGS_redirect_is_success)) {
-        std::cerr << "Request succeeded (redirect " << response_code << ")."
+        std::cout << "Request succeeded (redirect " << response_code << ")."
                   << std::endl;
       } else {
-        std::cerr << "Request failed (redirect " << response_code << ")."
+        std::cout << "Request failed (redirect " << response_code << ")."
                   << std::endl;
         return 1;
       }
     } else {
-      std::cerr << "Request failed (" << response_code << ")." << std::endl;
+      std::cout << "Request failed (" << response_code << ")." << std::endl;
       return 1;
     }
 
diff --git a/quic/tools/quic_simple_server_session_test.cc b/quic/tools/quic_simple_server_session_test.cc
index 330862a..577ff26 100644
--- a/quic/tools/quic_simple_server_session_test.cc
+++ b/quic/tools/quic_simple_server_session_test.cc
@@ -200,9 +200,13 @@
                        TlsServerHandshaker::CreateSslCtx()),
         compressed_certs_cache_(
             QuicCompressedCertsCache::kQuicCompressedCertsCacheSize) {
-    config_.SetMaxIncomingDynamicStreamsToSend(kMaxStreamsForTest);
-    QuicConfigPeer::SetReceivedMaxIncomingDynamicStreams(&config_,
-                                                         kMaxStreamsForTest);
+    config_.SetMaxIncomingBidirectionalStreamsToSend(kMaxStreamsForTest);
+    QuicConfigPeer::SetReceivedMaxIncomingBidirectionalStreams(
+        &config_, kMaxStreamsForTest);
+    config_.SetMaxIncomingUnidirectionalStreamsToSend(kMaxStreamsForTest);
+    QuicConfigPeer::SetReceivedMaxIncomingUnidirectionalStreams(
+        &config_, kMaxStreamsForTest);
+
     config_.SetInitialStreamFlowControlWindowToSend(
         kInitialStreamFlowControlWindowForTest);
     config_.SetInitialSessionFlowControlWindowToSend(
diff --git a/quic/tools/quic_simple_server_stream_test.cc b/quic/tools/quic_simple_server_stream_test.cc
index 7572a75..8aaaf6b 100644
--- a/quic/tools/quic_simple_server_stream_test.cc
+++ b/quic/tools/quic_simple_server_stream_test.cc
@@ -100,8 +100,15 @@
                                 crypto_config,
                                 compressed_certs_cache,
                                 quic_simple_server_backend) {
-    QuicSessionPeer::SetMaxOpenIncomingStreams(this, kMaxStreamsForTest);
-    QuicSessionPeer::SetMaxOpenOutgoingStreams(this, kMaxStreamsForTest);
+    if (connection->transport_version() == QUIC_VERSION_99) {
+      QuicSessionPeer::SetMaxOpenIncomingUnidirectionalStreams(
+          this, kMaxStreamsForTest);
+      QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(
+          this, kMaxStreamsForTest);
+    } else {
+      QuicSessionPeer::SetMaxOpenIncomingStreams(this, kMaxStreamsForTest);
+      QuicSessionPeer::SetMaxOpenOutgoingStreams(this, kMaxStreamsForTest);
+    }
     ON_CALL(*this, WritevData(_, _, _, _, _))
         .WillByDefault(Invoke(MockQuicSession::ConsumeData));
   }
diff --git a/quic/tools/quic_spdy_client_base.h b/quic/tools/quic_spdy_client_base.h
index 03a981e..0bc4f23 100644
--- a/quic/tools/quic_spdy_client_base.h
+++ b/quic/tools/quic_spdy_client_base.h
@@ -135,6 +135,11 @@
     response_listener_ = std::move(listener);
   }
 
+  void set_drop_response_body(bool drop_response_body) {
+    drop_response_body_ = drop_response_body;
+  }
+  bool drop_response_body() const { return drop_response_body_; }
+
  protected:
   int GetNumSentClientHellosFromSession() override;
   int GetNumReceivedServerConfigUpdatesFromSession() override;
@@ -209,6 +214,8 @@
   std::vector<std::unique_ptr<QuicDataToResend>> data_to_resend_on_connect_;
 
   std::unique_ptr<ClientQuicDataToResend> push_promise_data_to_resend_;
+
+  bool drop_response_body_ = false;
 };
 
 }  // namespace quic