Do not send control frames (by session) until encryption gets established.

Protected by FLAGS_quic_reloadable_flag_quic_encrypted_control_frames.

PiperOrigin-RevId: 339672390
Change-Id: I7bcab9157f05d92cbd74b4488957cabc0e7ea6fa
diff --git a/quic/core/http/quic_client_promised_info_test.cc b/quic/core/http/quic_client_promised_info_test.cc
index 1a1ec69..9e21f41 100644
--- a/quic/core/http/quic_client_promised_info_test.cc
+++ b/quic/core/http/quic_client_promised_info_test.cc
@@ -8,6 +8,7 @@
 #include <string>
 #include <utility>
 
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
 #include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
 #include "net/third_party/quiche/src/quic/core/http/spdy_server_push_utils.h"
 #include "net/third_party/quiche/src/quic/core/quic_utils.h"
@@ -52,6 +53,11 @@
 
   void set_authorized(bool authorized) { authorized_ = authorized; }
 
+  MOCK_METHOD(bool,
+              WriteControlFrame,
+              (const QuicFrame& frame, TransmissionType type),
+              (override));
+
  private:
   QuicCryptoClientConfig crypto_config_;
 
@@ -73,6 +79,9 @@
         promise_id_(
             QuicUtils::GetInvalidStreamId(connection_->transport_version())) {
     connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+    connection_->SetEncrypter(
+        ENCRYPTION_FORWARD_SECURE,
+        std::make_unique<NullEncrypter>(connection_->perspective()));
     session_.Initialize();
 
     headers_[":status"] = "200";
@@ -142,7 +151,7 @@
   ASSERT_NE(promised, nullptr);
 
   // Fire the alarm that will cancel the promised stream.
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(session_, WriteControlFrame(_, _));
   EXPECT_CALL(*connection_,
               OnStreamReset(promise_id_, QUIC_PUSH_STREAM_TIMED_OUT));
   alarm_factory_.FireAlarm(QuicClientPromisedInfoPeer::GetAlarm(promised));
@@ -156,7 +165,7 @@
   // Promise with an unsafe method
   push_promise_[":method"] = "PUT";
 
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(session_, WriteControlFrame(_, _));
   EXPECT_CALL(*connection_,
               OnStreamReset(promise_id_, QUIC_INVALID_PROMISE_METHOD));
   ReceivePromise(promise_id_);
@@ -170,7 +179,7 @@
   // Promise with a missing method
   push_promise_.erase(":method");
 
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(session_, WriteControlFrame(_, _));
   EXPECT_CALL(*connection_,
               OnStreamReset(promise_id_, QUIC_INVALID_PROMISE_METHOD));
   ReceivePromise(promise_id_);
@@ -184,7 +193,7 @@
   // Remove required header field to make URL invalid
   push_promise_.erase(":authority");
 
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(session_, WriteControlFrame(_, _));
   EXPECT_CALL(*connection_,
               OnStreamReset(promise_id_, QUIC_INVALID_PROMISE_URL));
   ReceivePromise(promise_id_);
@@ -197,7 +206,7 @@
 TEST_F(QuicClientPromisedInfoTest, PushPromiseUnauthorizedUrl) {
   session_.set_authorized(false);
 
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(session_, WriteControlFrame(_, _));
   EXPECT_CALL(*connection_,
               OnStreamReset(promise_id_, QUIC_UNAUTHORIZED_PROMISE_URL));
 
@@ -222,7 +231,7 @@
                                      headers);
 
   TestPushPromiseDelegate delegate(/*match=*/false);
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(session_, WriteControlFrame(_, _));
   EXPECT_CALL(*connection_,
               OnStreamReset(promise_id_, QUIC_PROMISE_VARY_MISMATCH));
 
@@ -300,7 +309,7 @@
   session_.GetOrCreateStream(promise_id_);
 
   // Cancel the promised stream.
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(session_, WriteControlFrame(_, _));
   EXPECT_CALL(*connection_, OnStreamReset(promise_id_, QUIC_STREAM_CANCELLED));
   promised->Cancel();
 
@@ -323,7 +332,7 @@
   promise_stream->OnStreamHeaderList(false, headers.uncompressed_header_bytes(),
                                      headers);
 
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(session_, WriteControlFrame(_, _));
   EXPECT_CALL(*connection_,
               OnStreamReset(promise_id_, QUIC_STREAM_PEER_GOING_AWAY));
   session_.ResetStream(promise_id_, QUIC_STREAM_PEER_GOING_AWAY);
diff --git a/quic/core/http/quic_server_session_base_test.cc b/quic/core/http/quic_server_session_base_test.cc
index 8cfa01b..4abde42 100644
--- a/quic/core/http/quic_server_session_base_test.cc
+++ b/quic/core/http/quic_server_session_base_test.cc
@@ -71,6 +71,11 @@
 
   ~TestServerSession() override { DeleteConnection(); }
 
+  MOCK_METHOD(bool,
+              WriteControlFrame,
+              (const QuicFrame& frame, TransmissionType type),
+              (override));
+
  protected:
   QuicSpdyStream* CreateIncomingStream(QuicStreamId id) override {
     if (!ShouldCreateIncomingStream(id)) {
@@ -147,6 +152,9 @@
     connection_ = new StrictMock<MockQuicConnection>(
         &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions);
     connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+    connection_->SetEncrypter(
+        ENCRYPTION_FORWARD_SECURE,
+        std::make_unique<NullEncrypter>(connection_->perspective()));
     session_ = std::make_unique<TestServerSession>(
         config_, connection_, &owner_, &stream_helper_, &crypto_config_,
         &compressed_certs_cache_, &memory_cache_backend_);
@@ -193,7 +201,7 @@
     EXPECT_CALL(owner_, OnStopSendingReceived(_)).Times(1);
     // Expect the RESET_STREAM that is generated in response to receiving a
     // STOP_SENDING.
-    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*session_, WriteControlFrame(_, _));
     EXPECT_CALL(*connection_,
                 OnStreamReset(stream_id, QUIC_ERROR_PROCESSING_STREAM));
     session_->OnStopSendingFrame(stop_sending);
@@ -249,7 +257,7 @@
   if (!VersionHasIetfQuicFrames(transport_version())) {
     // For non-version 99, the RESET_STREAM will do the full close.
     // Set up expects accordingly.
-    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*session_, WriteControlFrame(_, _));
     EXPECT_CALL(*connection_,
                 OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
                               QUIC_RST_ACKNOWLEDGEMENT));
@@ -278,7 +286,7 @@
   if (!VersionHasIetfQuicFrames(transport_version())) {
     // For non-version 99, the RESET_STREAM will do the full close.
     // Set up expects accordingly.
-    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*session_, WriteControlFrame(_, _));
     EXPECT_CALL(*connection_,
                 OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
                               QUIC_RST_ACKNOWLEDGEMENT));
@@ -318,7 +326,7 @@
   if (!VersionHasIetfQuicFrames(transport_version())) {
     // For non-version 99, the RESET_STREAM will do the full close.
     // Set up expects accordingly.
-    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*session_, WriteControlFrame(_, _));
     EXPECT_CALL(*connection_,
                 OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
                               QUIC_RST_ACKNOWLEDGEMENT));
@@ -348,9 +356,6 @@
   // streams.  For versions other than version 99, the server accepts slightly
   // more than the negotiated stream limit to deal with rare cases where a
   // client FIN/RST is lost.
-  connection_->SetEncrypter(
-      ENCRYPTION_FORWARD_SECURE,
-      std::make_unique<NullEncrypter>(session_->perspective()));
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   session_->OnConfigNegotiated();
   if (!VersionHasIetfQuicFrames(transport_version())) {
@@ -387,7 +392,7 @@
     // For non-version 99, QUIC responds to an attempt to exceed the stream
     // limit by resetting the stream.
     EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(0);
-    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*session_, WriteControlFrame(_, _));
     EXPECT_CALL(*connection_, OnStreamReset(stream_id, QUIC_REFUSED_STREAM));
   } else {
     // In version 99 QUIC responds to an attempt to exceed the stream limit by
@@ -403,9 +408,6 @@
   // Test that the server closes the connection if a client makes too many data
   // streams available.  The server accepts slightly more than the negotiated
   // stream limit to deal with rare cases where a client FIN/RST is lost.
-  connection_->SetEncrypter(
-      ENCRYPTION_FORWARD_SECURE,
-      std::make_unique<NullEncrypter>(session_->perspective()));
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   session_->OnConfigNegotiated();
   const size_t kAvailableStreamLimit =
@@ -512,9 +514,6 @@
   QuicTagVector copt;
   copt.push_back(kBWRE);
   QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt);
-  connection_->SetEncrypter(
-      ENCRYPTION_FORWARD_SECURE,
-      std::make_unique<NullEncrypter>(session_->perspective()));
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   session_->OnConfigNegotiated();
   EXPECT_TRUE(
@@ -706,9 +705,6 @@
   QuicTagVector copt;
   copt.push_back(kBWMX);
   QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt);
-  connection_->SetEncrypter(
-      ENCRYPTION_FORWARD_SECURE,
-      std::make_unique<NullEncrypter>(session_->perspective()));
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   session_->OnConfigNegotiated();
   EXPECT_TRUE(
@@ -718,9 +714,6 @@
 TEST_P(QuicServerSessionBaseTest, NoBandwidthResumptionByDefault) {
   EXPECT_FALSE(
       QuicServerSessionBasePeer::IsBandwidthResumptionEnabled(session_.get()));
-  connection_->SetEncrypter(
-      ENCRYPTION_FORWARD_SECURE,
-      std::make_unique<NullEncrypter>(session_->perspective()));
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   session_->OnConfigNegotiated();
   EXPECT_FALSE(
@@ -736,9 +729,6 @@
   QuicTagVector copt;
   copt.push_back(kQNSP);
   QuicConfigPeer::SetReceivedConnectionOptions(session_->config(), copt);
-  connection_->SetEncrypter(
-      ENCRYPTION_FORWARD_SECURE,
-      std::make_unique<NullEncrypter>(session_->perspective()));
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   session_->OnConfigNegotiated();
   EXPECT_FALSE(session_->server_push_enabled());
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index 18e8712..c8bae78 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -766,6 +766,7 @@
 }
 
 TEST_P(QuicSpdyClientSessionTest, ReceivingPromiseEnhanceYourCalm) {
+  CompleteCryptoHandshake();
   for (size_t i = 0u; i < session_->get_max_promises(); i++) {
     push_promise_[":path"] = quiche::QuicheStringPrintf("/bar%zu", i);
 
diff --git a/quic/core/http/quic_spdy_client_stream_test.cc b/quic/core/http/quic_spdy_client_stream_test.cc
index c8653ed..90d4ee1 100644
--- a/quic/core/http/quic_spdy_client_stream_test.cc
+++ b/quic/core/http/quic_spdy_client_stream_test.cc
@@ -8,6 +8,7 @@
 #include <string>
 #include <utility>
 
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
 #include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h"
 #include "net/third_party/quiche/src/quic/core/http/spdy_utils.h"
 #include "net/third_party/quiche/src/quic/core/quic_utils.h"
@@ -46,6 +47,11 @@
       delete;
   ~MockQuicSpdyClientSession() override = default;
 
+  MOCK_METHOD(bool,
+              WriteControlFrame,
+              (const QuicFrame& frame, TransmissionType type),
+              (override));
+
   using QuicSession::ActivateStream;
 
  private:
@@ -68,7 +74,9 @@
         body_("hello world") {
     session_.Initialize();
     connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
-
+    connection_->SetEncrypter(
+        ENCRYPTION_FORWARD_SECURE,
+        std::make_unique<NullEncrypter>(connection_->perspective()));
     headers_[":status"] = "200";
     headers_["content-length"] = "11";
 
@@ -109,7 +117,7 @@
 TEST_P(QuicSpdyClientStreamTest, TestReceivingIllegalResponseStatusCode) {
   headers_[":status"] = "200 ok";
 
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(session_, WriteControlFrame(_, _));
   EXPECT_CALL(*connection_,
               OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
   auto headers = AsHeaderList(headers_);
@@ -200,7 +208,7 @@
   // 101 "Switching Protocols" is forbidden in HTTP/3 as per the
   // "HTTP Upgrade" section of draft-ietf-quic-http.
   headers_[":status"] = "101";
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(session_, WriteControlFrame(_, _));
   EXPECT_CALL(*connection_,
               OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
   auto headers = AsHeaderList(headers_);
@@ -246,7 +254,7 @@
   std::string data = VersionUsesHttp3(connection_->transport_version())
                          ? header + large_body
                          : large_body;
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(session_, WriteControlFrame(_, _));
   EXPECT_CALL(*connection_,
               OnStreamReset(stream_->id(), QUIC_BAD_APPLICATION_PAYLOAD));
 
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index 36ebf87..714c21d 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -85,6 +85,13 @@
     params_->cipher_suite = 1;
   }
 
+  void EstablishZeroRttEncryption() {
+    encryption_established_ = true;
+    session()->connection()->SetEncrypter(
+        ENCRYPTION_ZERO_RTT,
+        std::make_unique<NullEncrypter>(session()->perspective()));
+  }
+
   void OnHandshakeMessage(const CryptoHandshakeMessage& /*message*/) override {
     encryption_established_ = true;
     one_rtt_keys_available_ = true;
@@ -573,6 +580,7 @@
 }
 
 TEST_P(QuicSpdySessionTestServer, IsClosedStreamLocallyCreated) {
+  CompleteHandshake();
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   EXPECT_EQ(GetNthServerInitiatedBidirectionalId(0), stream2->id());
   QuicSpdyStream* stream4 = session_.CreateOutgoingBidirectionalStream();
@@ -586,6 +594,7 @@
 }
 
 TEST_P(QuicSpdySessionTestServer, IsClosedStreamPeerCreated) {
+  CompleteHandshake();
   QuicStreamId stream_id1 = GetNthClientInitiatedBidirectionalId(0);
   QuicStreamId stream_id2 = GetNthClientInitiatedBidirectionalId(1);
   session_.GetOrCreateStream(stream_id1);
@@ -988,6 +997,7 @@
 }
 
 TEST_P(QuicSpdySessionTestServer, OnCanWriteWithClosedStream) {
+  CompleteHandshake();
   session_.set_writev_consumes_all_data(true);
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
@@ -1065,6 +1075,7 @@
 }
 
 TEST_P(QuicSpdySessionTestServer, SendGoAway) {
+  CompleteHandshake();
   if (VersionHasIetfQuicFrames(transport_version())) {
     // HTTP/3 GOAWAY has different semantic and thus has its own test.
     return;
@@ -1188,6 +1199,7 @@
 }
 
 TEST_P(QuicSpdySessionTestServer, DoNotSendGoAwayTwice) {
+  CompleteHandshake();
   if (VersionHasIetfQuicFrames(transport_version())) {
     // HTTP/3 GOAWAY doesn't have such restriction.
     return;
@@ -1400,6 +1412,7 @@
 
   // Ensure that Writev consumes all the data it is given (simulate no socket
   // blocking).
+  session_.GetMutableCryptoStream()->EstablishZeroRttEncryption();
   session_.set_writev_consumes_all_data(true);
 
   // Create a stream, and send enough data to make it flow control blocked.
@@ -1426,9 +1439,12 @@
 
 TEST_P(QuicSpdySessionTestServer,
        HandshakeUnblocksFlowControlBlockedCryptoStream) {
-  if (QuicVersionUsesCryptoFrames(transport_version())) {
+  if (QuicVersionUsesCryptoFrames(transport_version()) ||
+      connection_->encrypted_control_frames()) {
     // QUIC version 47 onwards uses CRYPTO frames for the handshake, so this
-    // test doesn't make sense for those versions.
+    // test doesn't make sense for those versions. With
+    // use_encryption_level_context, control frames can only be sent when
+    // encryption gets established, do not send BLOCKED for crypto streams.
     return;
   }
   // Test that if the crypto stream is flow control blocked, then if the SHLO
@@ -1498,6 +1514,7 @@
 
   // Test that if the header stream is flow control blocked, then if the SHLO
   // contains a larger send window offset, the stream becomes unblocked.
+  session_.GetMutableCryptoStream()->EstablishZeroRttEncryption();
   session_.set_writev_consumes_all_data(true);
   TestCryptoStream* crypto_stream = session_.GetMutableCryptoStream();
   EXPECT_FALSE(crypto_stream->IsFlowControlBlocked());
@@ -1614,6 +1631,7 @@
   if (!VersionUsesHttp3(transport_version())) {
     return;
   }
+  session_.GetMutableCryptoStream()->EstablishZeroRttEncryption();
   QuicConfigPeer::SetReceivedMaxUnidirectionalStreams(session_.config(), 2u);
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
 
@@ -1629,7 +1647,6 @@
   QuicTagVector copt;
   copt.push_back(kIFW7);
   QuicConfigPeer::SetReceivedConnectionOptions(session_.config(), copt);
-
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   session_.OnConfigNegotiated();
   EXPECT_EQ(192 * 1024u, QuicFlowControllerPeer::ReceiveWindowSize(
@@ -1668,6 +1685,7 @@
   // If a buggy/malicious peer creates too many streams that are not ended
   // 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.
+  CompleteHandshake();
   const QuicStreamId kMaxStreams = 5;
   if (VersionHasIetfQuicFrames(transport_version())) {
     QuicSessionPeer::SetMaxOpenIncomingBidirectionalStreams(&session_,
@@ -1722,6 +1740,7 @@
   // Verify that a draining stream (which has received a FIN but not consumed
   // it) does not count against the open quota (because it is closed from the
   // protocol point of view).
+  CompleteHandshake();
   if (VersionHasIetfQuicFrames(transport_version())) {
     // Simulate receiving a config. so that MAX_STREAMS/etc frames may
     // be transmitted
@@ -1907,6 +1926,7 @@
   // Verify that an incoming FIN is recorded in a stream object even if the read
   // side has been closed.  This prevents an entry from being made in
   // locally_closed_streams_highest_offset_ (which will never be deleted).
+  CompleteHandshake();
   TestStream* stream = session_.CreateOutgoingBidirectionalStream();
   QuicStreamId stream_id = stream->id();
 
@@ -2111,6 +2131,7 @@
 TEST_P(QuicSpdySessionTestServer, DonotRetransmitDataOfClosedStreams) {
   // Resetting a stream will send a QPACK Stream Cancellation instruction on the
   // decoder stream.  For simplicity, ignore writes on this stream.
+  CompleteHandshake();
   NoopQpackStreamSenderDelegate qpack_stream_sender_delegate;
   if (VersionUsesHttp3(transport_version())) {
     session_.qpack_decoder()->set_qpack_stream_sender_delegate(
@@ -2156,6 +2177,7 @@
 }
 
 TEST_P(QuicSpdySessionTestServer, RetransmitFrames) {
+  CompleteHandshake();
   MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
   QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
   InSequence s;
@@ -2274,7 +2296,7 @@
   if (!VersionUsesHttp3(transport_version())) {
     return;
   }
-
+  CompleteHandshake();
   char input[] = {0x04,            // type
                   'a', 'b', 'c'};  // data
   absl::string_view payload(input, ABSL_ARRAYSIZE(input));
@@ -2326,7 +2348,7 @@
   if (!VersionUsesHttp3(transport_version())) {
     return;
   }
-
+  CompleteHandshake();
   char input[] = {0x04,            // type
                   'a', 'b', 'c'};  // data
   absl::string_view payload(input, ABSL_ARRAYSIZE(input));
@@ -2367,7 +2389,7 @@
   if (!VersionUsesHttp3(transport_version())) {
     return;
   }
-
+  CompleteHandshake();
   char input[] = {0x41, 0x00,      // type (256)
                   'a', 'b', 'c'};  // data
   absl::string_view payload(input, ABSL_ARRAYSIZE(input));
@@ -2479,7 +2501,7 @@
   if (!VersionUsesHttp3(transport_version())) {
     return;
   }
-
+  CompleteHandshake();
   session_.qpack_decoder()->OnSetDynamicTableCapacity(1024);
 
   QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
@@ -2546,6 +2568,7 @@
   if (!VersionUsesHttp3(transport_version())) {
     return;
   }
+  CompleteHandshake();
   ASSERT_TRUE(session_.UsesPendingStreams());
 
   const QuicStreamId stream_id =
@@ -2592,6 +2615,7 @@
   if (!VersionUsesHttp3(transport_version())) {
     return;
   }
+  CompleteHandshake();
   ASSERT_TRUE(session_.UsesPendingStreams());
 
   const QuicStreamId stream_id =
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index f493598..db6facf 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -349,7 +349,10 @@
                              clock_->ApproximateNow(),
                              &arena_,
                              alarm_factory_),
-      support_handshake_done_(version().HasHandshakeDone()) {
+      support_handshake_done_(version().HasHandshakeDone()),
+      encrypted_control_frames_(
+          GetQuicReloadableFlag(quic_encrypted_control_frames) &&
+          packet_creator_.let_connection_handle_pings()) {
   QUIC_BUG_IF(!start_peer_migration_earlier_ && send_path_response_);
   if (GetQuicReloadableFlag(quic_connection_set_initial_self_address)) {
     DCHECK(perspective_ == Perspective::IS_CLIENT ||
@@ -1840,8 +1843,8 @@
   if (!should_last_packet_instigate_acks_) {
     uber_received_packet_manager_.MaybeUpdateAckTimeout(
         should_last_packet_instigate_acks_, last_decrypted_packet_level_,
-        last_header_.packet_number,
-        clock_->ApproximateNow(), sent_packet_manager_.GetRttStats());
+        last_header_.packet_number, clock_->ApproximateNow(),
+        sent_packet_manager_.GetRttStats());
   }
 
   ClearLastFrames();
@@ -5342,8 +5345,8 @@
   should_last_packet_instigate_acks_ = true;
   uber_received_packet_manager_.MaybeUpdateAckTimeout(
       /*should_last_packet_instigate_acks=*/true, last_decrypted_packet_level_,
-      last_header_.packet_number,
-      clock_->ApproximateNow(), sent_packet_manager_.GetRttStats());
+      last_header_.packet_number, clock_->ApproximateNow(),
+      sent_packet_manager_.GetRttStats());
 }
 
 QuicTime QuicConnection::GetPathDegradingDeadline() const {
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 45eb1cb..681a64a 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -1127,6 +1127,8 @@
 
   bool is_processing_packet() const { return framer_.is_processing_packet(); }
 
+  bool encrypted_control_frames() const { return encrypted_control_frames_; }
+
  protected:
   // Calls cancel() on all the alarms owned by this connection.
   void CancelAllAlarms();
@@ -1926,6 +1928,8 @@
 
   const bool check_keys_before_writing_ =
       GetQuicReloadableFlag(quic_check_keys_before_writing);
+
+  bool encrypted_control_frames_;
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_control_frame_manager_test.cc b/quic/core/quic_control_frame_manager_test.cc
index cf90e90..68658bb 100644
--- a/quic/core/quic_control_frame_manager_test.cc
+++ b/quic/core/quic_control_frame_manager_test.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
 #include "net/third_party/quiche/src/quic/core/frames/quic_ack_frequency_frame.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
@@ -37,7 +38,7 @@
 
 class QuicControlFrameManagerTest : public QuicTest {
  public:
-  bool SaveControlFrame(const QuicFrame& frame) {
+  bool SaveControlFrame(const QuicFrame& frame, TransmissionType /*type*/) {
     frame_ = frame;
     return true;
   }
@@ -54,13 +55,16 @@
   void Initialize() {
     connection_ = new MockQuicConnection(&helper_, &alarm_factory_,
                                          Perspective::IS_SERVER);
+    connection_->SetEncrypter(
+        ENCRYPTION_FORWARD_SECURE,
+        std::make_unique<NullEncrypter>(connection_->perspective()));
     session_ = std::make_unique<StrictMock<MockQuicSession>>(connection_);
     manager_ = std::make_unique<QuicControlFrameManager>(session_.get());
     EXPECT_EQ(0u, QuicControlFrameManagerPeer::QueueSize(manager_.get()));
     EXPECT_FALSE(manager_->HasPendingRetransmission());
     EXPECT_FALSE(manager_->WillingToWrite());
 
-    EXPECT_CALL(*connection_, SendControlFrame(_)).WillOnce(Return(false));
+    EXPECT_CALL(*session_, WriteControlFrame(_, _)).WillOnce(Return(false));
     manager_->WriteOrBufferRstStream(kTestStreamId, QUIC_STREAM_CANCELLED, 0);
     manager_->WriteOrBufferGoAway(QUIC_PEER_GOING_AWAY, kTestStreamId,
                                   "Going away.");
@@ -103,10 +107,10 @@
 TEST_F(QuicControlFrameManagerTest, OnControlFrameAcked) {
   Initialize();
   InSequence s;
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(3)
-      .WillRepeatedly(Invoke(&ClearControlFrame));
-  EXPECT_CALL(*connection_, SendControlFrame(_)).WillOnce(Return(false));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _)).WillOnce(Return(false));
   // Send control frames 1, 2, 3.
   manager_->OnCanWrite();
   EXPECT_TRUE(manager_->IsControlFrameOutstanding(QuicFrame(&rst_stream_)));
@@ -139,8 +143,8 @@
   EXPECT_TRUE(manager_->WillingToWrite());
 
   // Send control frames 4, 5.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   manager_->OnCanWrite();
   manager_->WritePing();
   EXPECT_FALSE(manager_->WillingToWrite());
@@ -149,10 +153,10 @@
 TEST_F(QuicControlFrameManagerTest, OnControlFrameLost) {
   Initialize();
   InSequence s;
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(3)
-      .WillRepeatedly(Invoke(&ClearControlFrame));
-  EXPECT_CALL(*connection_, SendControlFrame(_)).WillOnce(Return(false));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _)).WillOnce(Return(false));
   // Send control frames 1, 2, 3.
   manager_->OnCanWrite();
 
@@ -166,17 +170,17 @@
   manager_->OnControlFrameAcked(QuicFrame(&goaway_));
 
   // Retransmit control frames 1, 3.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(2)
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   manager_->OnCanWrite();
   EXPECT_FALSE(manager_->HasPendingRetransmission());
   EXPECT_TRUE(manager_->WillingToWrite());
 
   // Send control frames 4, 5, and 6.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(number_of_frames_ - 2u)
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   manager_->OnCanWrite();
   manager_->WritePing();
   EXPECT_FALSE(manager_->WillingToWrite());
@@ -186,26 +190,26 @@
   Initialize();
   InSequence s;
   // Send control frames 1, 2, 3, 4.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(number_of_frames_)
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   manager_->OnCanWrite();
 
   // Ack control frame 2.
   manager_->OnControlFrameAcked(QuicFrame(&goaway_));
-  // Do not retransmit an acked frame.
-  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(0);
+  // Do not retransmit an acked frame
+  EXPECT_CALL(*session_, WriteControlFrame(_, _)).Times(0);
   EXPECT_TRUE(manager_->RetransmitControlFrame(QuicFrame(&goaway_),
                                                PTO_RETRANSMISSION));
 
   // Retransmit control frame 3.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
-      .WillOnce(Invoke(&ClearControlFrame));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
+      .WillOnce(Invoke(&ClearControlFrameWithTransmissionType));
   EXPECT_TRUE(manager_->RetransmitControlFrame(QuicFrame(&window_update_),
                                                PTO_RETRANSMISSION));
 
   // Retransmit control frame 4, and connection is write blocked.
-  EXPECT_CALL(*connection_, SendControlFrame(_)).WillOnce(Return(false));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _)).WillOnce(Return(false));
   EXPECT_FALSE(manager_->RetransmitControlFrame(QuicFrame(&window_update_),
                                                 PTO_RETRANSMISSION));
 }
@@ -213,9 +217,9 @@
 TEST_F(QuicControlFrameManagerTest, DonotSendPingWithBufferedFrames) {
   Initialize();
   InSequence s;
-  EXPECT_CALL(*connection_, SendControlFrame(_))
-      .WillOnce(Invoke(&ClearControlFrame));
-  EXPECT_CALL(*connection_, SendControlFrame(_)).WillOnce(Return(false));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
+      .WillOnce(Invoke(&ClearControlFrameWithTransmissionType));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _)).WillOnce(Return(false));
   // Send control frame 1.
   manager_->OnCanWrite();
   EXPECT_FALSE(manager_->HasPendingRetransmission());
@@ -224,9 +228,9 @@
   // Send PING when there is buffered frames.
   manager_->WritePing();
   // Verify only the buffered frames are sent.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(number_of_frames_ - 1)
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   manager_->OnCanWrite();
   EXPECT_FALSE(manager_->HasPendingRetransmission());
   EXPECT_FALSE(manager_->WillingToWrite());
@@ -236,10 +240,10 @@
   Initialize();
   InSequence s;
   // Send Non-AckFrequency frame 1-5.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(5)
-      .WillRepeatedly(Invoke(&ClearControlFrame));
-  EXPECT_CALL(*connection_, SendControlFrame(_)).WillOnce(Return(false));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _)).WillOnce(Return(false));
   manager_->OnCanWrite();
 
   // Send AckFrequencyFrame as frame 6.
@@ -247,8 +251,8 @@
   frame_to_send.packet_tolerance = 10;
   frame_to_send.max_ack_delay = QuicTime::Delta::FromMilliseconds(24);
   manager_->WriteOrBufferAckFrequency(frame_to_send);
-  EXPECT_CALL(*connection_, SendControlFrame(_))
-      .WillOnce(Invoke(&ClearControlFrame));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
+      .WillOnce(Invoke(&ClearControlFrameWithTransmissionType));
   manager_->OnCanWrite();
 
   // Ack AckFrequencyFrame.
@@ -270,8 +274,8 @@
                                        300);
   InSequence s;
   // Flush all buffered control frames.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   manager_->OnCanWrite();
 
   // Mark all 3 window updates as lost.
@@ -282,7 +286,7 @@
   EXPECT_TRUE(manager_->WillingToWrite());
 
   // Verify only the latest window update gets retransmitted.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .WillOnce(Invoke(this, &QuicControlFrameManagerTest::SaveControlFrame));
   manager_->OnCanWrite();
   EXPECT_EQ(number_of_frames_ + 2u,
@@ -302,8 +306,8 @@
   QuicWindowUpdateFrame window_update3(6, kTestStreamId + 4, 300);
   InSequence s;
   // Flush all buffered control frames.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   manager_->OnCanWrite();
 
   // Mark all 3 window updates as lost.
@@ -314,9 +318,9 @@
   EXPECT_TRUE(manager_->WillingToWrite());
 
   // Verify all 3 window updates get retransmitted.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(3)
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   manager_->OnCanWrite();
   EXPECT_FALSE(manager_->HasPendingRetransmission());
   EXPECT_FALSE(manager_->WillingToWrite());
@@ -324,13 +328,13 @@
 
 TEST_F(QuicControlFrameManagerTest, TooManyBufferedControlFrames) {
   Initialize();
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(5)
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   // Flush buffered frames.
   manager_->OnCanWrite();
   // Write 995 control frames.
-  EXPECT_CALL(*connection_, SendControlFrame(_)).WillOnce(Return(false));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _)).WillOnce(Return(false));
   for (size_t i = 0; i < 995; ++i) {
     manager_->WriteOrBufferRstStream(kTestStreamId, QUIC_STREAM_CANCELLED, 0);
   }
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 0526112..e06a766 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -4,80 +4,81 @@
 
 // This file is autogenerated by the QUICHE Copybara export script.
 
-QUIC_FLAG(FLAGS_quic_reloadable_flag_send_quic_fallback_server_config_on_leto_error, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_reject_spdy_settings, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_unified_iw_options, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_use_fast_huffman_encoder, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_testonly_default_false, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_testonly_default_true, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_require_handshake_confirmation, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_goaway_with_connection_close, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_timestamps, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_reject_spdy_frames, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_split_up_send_rst, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_stop_sending_uses_ietf_error_code, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_version_negotiation_for_short_connection_ids, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_record_received_min_ack_delay, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_path_response, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_process_undecryptable_packets_after_async_decrypt_callback, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_let_connection_handle_pings, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_granular_qpack_error_codes, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_key_update_supported, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_http3_new_default_urgency_value, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_out_of_order_sending2, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_undecryptable_packets2, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_missing_initial_keys2, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_arm_pto_for_application_data, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_give_sent_packet_to_debug_visitor_after_sent, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_aead_limits, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_pto_pending_timer_count, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_willing_and_able_to_write2, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_donot_reset_ideal_next_packet_send_time, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_http3_goaway_stream_id, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_mtu_discovery_at_server, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_do_not_clip_received_error_code, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_address_validation, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_q043, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_draft_27, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_q050, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_t051, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_draft_29, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_discard_initial_packet_with_key_dropped, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_t050, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_q046, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_server_blackhole_detection, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_delay_initial_ack, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_copy_bbr_cwnd_to_bbr2, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr_v2, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_deallocate_message_right_after_sent, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_2_rttvar, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_close_connection_in_on_can_write_with_blocked_writer, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_on_pto, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_enable_5rto_blackhole_detection2, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_use_tcp_inflight_hi_headroom, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_bursts, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_check_keys_before_writing, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_can_send_ack_frequency, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_support_max_bootstrap_cwnd, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_connection_set_initial_self_address, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_clean_up_spdy_session_destructor, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_use_post_inflight_to_detect_queuing, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_no_exit_startup_on_loss_with_bw_growth, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_startup_loss_exit_use_max_delivered, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_fewer_startup_round_trips, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_limit_inflight_hi, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_avoid_too_low_probe_bw_cwnd, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allow_client_enabled_bbr_v2, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ack_delay_alarm_granularity, false)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_stream_info_to_idle_close_detail, true)
-QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_abort_qpack_on_stream_close, true)
-QUIC_FLAG(FLAGS_quic_restart_flag_quic_testonly_default_true, true)
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_testonly_default_false, false)
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_support_release_time_for_gso, false)
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_session_tickets_always_enabled, false)
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_enable_zero_rtt_for_tls_v2, true)
+QUIC_FLAG(FLAGS_quic_restart_flag_quic_testonly_default_true, true)
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_offload_pacing_to_usps2, false)
 QUIC_FLAG(FLAGS_quic_restart_flag_dont_fetch_quic_private_keys_from_leto, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_pto_pending_timer_count, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_out_of_order_sending2, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_split_up_send_rst, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_missing_initial_keys2, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_goaway_with_connection_close, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_send_quic_fallback_server_config_on_leto_error, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_testonly_default_true, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_stop_sending_uses_ietf_error_code, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_unified_iw_options, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_use_fast_huffman_encoder, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_version_negotiation_for_short_connection_ids, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_timestamps, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_testonly_default_false, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_path_response, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_require_handshake_confirmation, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_reject_spdy_frames, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_process_undecryptable_packets_after_async_decrypt_callback, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_donot_reset_ideal_next_packet_send_time, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_record_received_min_ack_delay, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_reject_spdy_settings, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_abort_qpack_on_stream_close, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_let_connection_handle_pings, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_key_update_supported, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_http3_new_default_urgency_value, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_willing_and_able_to_write2, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_granular_qpack_error_codes, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_give_sent_packet_to_debug_visitor_after_sent, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_undecryptable_packets2, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_http3_goaway_stream_id, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_arm_pto_for_application_data, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_address_validation, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_encrypted_control_frames, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_mtu_discovery_at_server, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_aead_limits, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_do_not_clip_received_error_code, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_t051, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_discard_initial_packet_with_key_dropped, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_t050, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_q050, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_q046, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_q043, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_draft_29, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_version_draft_27, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr_v2, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_server_blackhole_detection, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_delay_initial_ack, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_2_rttvar, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_on_pto, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_enable_5rto_blackhole_detection2, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_deallocate_message_right_after_sent, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_copy_bbr_cwnd_to_bbr2, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_bursts, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_connection_set_initial_self_address, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_close_connection_in_on_can_write_with_blocked_writer, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_clean_up_spdy_session_destructor, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_can_send_ack_frequency, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_check_keys_before_writing, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_use_tcp_inflight_hi_headroom, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_use_post_inflight_to_detect_queuing, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_support_max_bootstrap_cwnd, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_startup_loss_exit_use_max_delivered, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_limit_inflight_hi, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_no_exit_startup_on_loss_with_bw_growth, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_fewer_startup_round_trips, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_avoid_too_low_probe_bw_cwnd, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allow_client_enabled_bbr_v2, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_stream_info_to_idle_close_detail, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ack_delay_alarm_granularity, false)
diff --git a/quic/core/quic_flow_controller_test.cc b/quic/core/quic_flow_controller_test.cc
index 29a9ba3..5a27570 100644
--- a/quic/core/quic_flow_controller_test.cc
+++ b/quic/core/quic_flow_controller_test.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
@@ -39,6 +40,9 @@
   void Initialize() {
     connection_ = new MockQuicConnection(&helper_, &alarm_factory_,
                                          Perspective::IS_CLIENT);
+    connection_->SetEncrypter(
+        ENCRYPTION_FORWARD_SECURE,
+        std::make_unique<NullEncrypter>(connection_->perspective()));
     session_ = std::make_unique<MockQuicSession>(connection_);
     flow_controller_ = std::make_unique<QuicFlowController>(
         session_.get(), stream_id_, /*is_connection_flow_controller*/ false,
@@ -115,7 +119,7 @@
             QuicFlowControllerPeer::ReceiveWindowSize(flow_controller_.get()));
 
   // Consume enough bytes to send a WINDOW_UPDATE frame.
-  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+  EXPECT_CALL(*session_, WriteControlFrame(_, _)).Times(1);
 
   flow_controller_->AddBytesConsumed(1 + receive_window_ / 2);
 
@@ -185,7 +189,7 @@
   should_auto_tune_receive_window_ = true;
   Initialize();
   // This test will generate two WINDOW_UPDATE frames.
-  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+  EXPECT_CALL(*session_, WriteControlFrame(_, _)).Times(1);
   EXPECT_TRUE(flow_controller_->auto_tune_receive_window());
 
   // Make sure clock is inititialized.
@@ -237,9 +241,9 @@
 TEST_F(QuicFlowControllerTest, ReceivingBytesFastNoAutoTune) {
   Initialize();
   // This test will generate two WINDOW_UPDATE frames.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(2)
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   EXPECT_FALSE(flow_controller_->auto_tune_receive_window());
 
   // Make sure clock is inititialized.
@@ -292,7 +296,7 @@
   should_auto_tune_receive_window_ = true;
   Initialize();
   // This test will generate two WINDOW_UPDATE frames.
-  EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
+  EXPECT_CALL(*session_, WriteControlFrame(_, _)).Times(1);
   EXPECT_TRUE(flow_controller_->auto_tune_receive_window());
 
   // Make sure clock is inititialized.
@@ -347,9 +351,9 @@
 TEST_F(QuicFlowControllerTest, ReceivingBytesNormalNoAutoTune) {
   Initialize();
   // This test will generate two WINDOW_UPDATE frames.
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(2)
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   EXPECT_FALSE(flow_controller_->auto_tune_receive_window());
 
   // Make sure clock is inititialized.
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 5079b46..5f1c5ef 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -804,6 +804,14 @@
                                     TransmissionType type) {
   DCHECK(connection()->connected())
       << ENDPOINT << "Try to write control frames when connection is closed.";
+  if (connection_->encrypted_control_frames()) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_encrypted_control_frames);
+    if (!IsEncryptionEstablished()) {
+      QUIC_BUG << ENDPOINT << "Tried to send control frame " << frame
+               << " before encryption is established.";
+      return false;
+    }
+  }
   SetTransmissionType(type);
   return connection_->SendControlFrame(frame);
 }
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 738ee0b..129c937 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -70,6 +70,13 @@
     params_->cipher_suite = 1;
   }
 
+  void EstablishZeroRttEncryption() {
+    encryption_established_ = true;
+    session()->connection()->SetEncrypter(
+        ENCRYPTION_ZERO_RTT,
+        std::make_unique<NullEncrypter>(session()->perspective()));
+  }
+
   void OnHandshakeMessage(const CryptoHandshakeMessage& /*message*/) override {
     encryption_established_ = true;
     one_rtt_keys_available_ = true;
@@ -715,6 +722,7 @@
 }
 
 TEST_P(QuicSessionTestServer, IsClosedBidirectionalStreamLocallyCreated) {
+  CompleteHandshake();
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   EXPECT_EQ(GetNthServerInitiatedBidirectionalId(0), stream2->id());
   TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
@@ -728,6 +736,7 @@
 }
 
 TEST_P(QuicSessionTestServer, IsClosedUnidirectionalStreamLocallyCreated) {
+  CompleteHandshake();
   TestStream* stream2 = session_.CreateOutgoingUnidirectionalStream();
   EXPECT_EQ(GetNthServerInitiatedUnidirectionalId(0), stream2->id());
   TestStream* stream4 = session_.CreateOutgoingUnidirectionalStream();
@@ -741,6 +750,7 @@
 }
 
 TEST_P(QuicSessionTestServer, IsClosedBidirectionalStreamPeerCreated) {
+  CompleteHandshake();
   QuicStreamId stream_id1 = GetNthClientInitiatedBidirectionalId(0);
   QuicStreamId stream_id2 = GetNthClientInitiatedBidirectionalId(1);
   session_.GetOrCreateStream(stream_id1);
@@ -761,6 +771,7 @@
 }
 
 TEST_P(QuicSessionTestServer, IsClosedUnidirectionalStreamPeerCreated) {
+  CompleteHandshake();
   QuicStreamId stream_id1 = GetNthClientInitiatedUnidirectionalId(0);
   QuicStreamId stream_id2 = GetNthClientInitiatedUnidirectionalId(1);
   session_.GetOrCreateStream(stream_id1);
@@ -923,6 +934,7 @@
 }
 
 TEST_P(QuicSessionTestServer, DebugDFatalIfMarkingClosedStreamWriteBlocked) {
+  CompleteHandshake();
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   QuicStreamId closed_stream_id = stream2->id();
   // Close the stream.
@@ -1278,6 +1290,7 @@
   if (!VersionHasIetfQuicFrames(transport_version())) {
     return;
   }
+  CompleteHandshake();
   for (size_t i = 0; i < kDefaultMaxStreamsPerConnection; ++i) {
     ASSERT_TRUE(session_.CanOpenNextOutgoingBidirectionalStream());
     session_.GetNextOutgoingBidirectionalStreamId();
@@ -1363,6 +1376,7 @@
 }
 
 TEST_P(QuicSessionTestServer, OnCanWriteWithClosedStream) {
+  CompleteHandshake();
   session_.set_writev_consumes_all_data(true);
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
@@ -1432,6 +1446,7 @@
     // In IETF QUIC, GOAWAY lives up in the HTTP layer.
     return;
   }
+  CompleteHandshake();
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   MockPacketWriter* writer = static_cast<MockPacketWriter*>(
       QuicConnectionPeer::GetWriter(session_.connection()));
@@ -1453,6 +1468,7 @@
 }
 
 TEST_P(QuicSessionTestServer, DoNotSendGoAwayTwice) {
+  CompleteHandshake();
   if (VersionHasIetfQuicFrames(transport_version())) {
     // In IETF QUIC, GOAWAY lives up in the HTTP layer.
     return;
@@ -1554,12 +1570,7 @@
 TEST_P(QuicSessionTestServer, IncreasedTimeoutAfterCryptoHandshake) {
   EXPECT_EQ(kInitialIdleTimeoutSecs + 3,
             QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
-  if (connection_->version().HasHandshakeDone()) {
-    EXPECT_CALL(*connection_, SendControlFrame(_));
-  }
-  CryptoHandshakeMessage msg;
-  connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
-  session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+  CompleteHandshake();
   EXPECT_EQ(kMaximumIdleTimeoutSecs + 3,
             QuicConnectionPeer::GetNetworkTimeout(connection_).ToSeconds());
 }
@@ -1621,6 +1632,7 @@
   // Ensure that Writev consumes all the data it is given (simulate no socket
   // blocking).
   session_.set_writev_consumes_all_data(true);
+  session_.GetMutableCryptoStream()->EstablishZeroRttEncryption();
 
   // Create a stream, and send enough data to make it flow control blocked.
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
@@ -1636,8 +1648,7 @@
 
   // Now complete the crypto handshake, resulting in an increased flow control
   // send window.
-  CryptoHandshakeMessage msg;
-  session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+  CompleteHandshake();
   EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked(&session_, stream2->id()));
   // Stream is now unblocked.
   EXPECT_FALSE(stream2->IsFlowControlBlocked());
@@ -1646,7 +1657,8 @@
 }
 
 TEST_P(QuicSessionTestServer, HandshakeUnblocksFlowControlBlockedCryptoStream) {
-  if (QuicVersionUsesCryptoFrames(GetParam().transport_version)) {
+  if (QuicVersionUsesCryptoFrames(GetParam().transport_version) ||
+      connection_->encrypted_control_frames()) {
     // QUIC version 47 onwards uses CRYPTO frames for the handshake, so this
     // test doesn't make sense for those versions since CRYPTO frames aren't
     // flow controlled.
@@ -1684,8 +1696,7 @@
 
   // Now complete the crypto handshake, resulting in an increased flow control
   // send window.
-  CryptoHandshakeMessage msg;
-  session_.GetMutableCryptoStream()->OnHandshakeMessage(msg);
+  CompleteHandshake();
   EXPECT_TRUE(QuicSessionPeer::IsStreamWriteBlocked(
       &session_,
       QuicUtils::GetCryptoStreamId(connection_->transport_version())));
@@ -1696,6 +1707,7 @@
 }
 
 TEST_P(QuicSessionTestServer, ConnectionFlowControlAccountingRstOutOfOrder) {
+  CompleteHandshake();
   // Test that when we receive an out of order stream RST we correctly adjust
   // our connection level flow control receive window.
   // On close, the stream should mark as consumed all bytes between the highest
@@ -1725,6 +1737,7 @@
 }
 
 TEST_P(QuicSessionTestServer, ConnectionFlowControlAccountingFinAndLocalReset) {
+  CompleteHandshake();
   // Test the situation where we receive a FIN on a stream, and before we fully
   // consume all the data from the sequencer buffer we locally RST the stream.
   // The bytes between highest consumed byte, and the final byte offset that we
@@ -1751,6 +1764,7 @@
 }
 
 TEST_P(QuicSessionTestServer, ConnectionFlowControlAccountingFinAfterRst) {
+  CompleteHandshake();
   // Test that when we RST the stream (and tear down stream state), and then
   // receive a FIN from the peer, we correctly adjust our connection level flow
   // control receive window.
@@ -1790,6 +1804,7 @@
 }
 
 TEST_P(QuicSessionTestServer, ConnectionFlowControlAccountingRstAfterRst) {
+  CompleteHandshake();
   // Test that when we RST the stream (and tear down stream state), and then
   // receive a RST from the peer, we correctly adjust our connection level flow
   // control receive window.
@@ -1855,6 +1870,7 @@
 }
 
 TEST_P(QuicSessionTestServer, FlowControlWithInvalidFinalOffset) {
+  CompleteHandshake();
   // Test that if we receive a stream RST with a highest byte offset that
   // violates flow control, that we close the connection.
   const uint64_t kLargeOffset = kInitialSessionFlowControlWindowForTest + 1;
@@ -1877,6 +1893,7 @@
 }
 
 TEST_P(QuicSessionTestServer, TooManyUnfinishedStreamsCauseServerRejectStream) {
+  CompleteHandshake();
   // If a buggy/malicious peer creates too many streams that are not ended
   // with a FIN or RST then we send an RST to refuse streams. For IETF QUIC the
   // connection is closed.
@@ -1919,6 +1936,7 @@
   // Verify that a draining stream (which has received a FIN but not consumed
   // it) does not count against the open quota (because it is closed from the
   // protocol point of view).
+  CompleteHandshake();
   TestStream* stream = session_.CreateOutgoingBidirectionalStream();
   QuicStreamId stream_id = stream->id();
   QuicStreamFrame data1(stream_id, true, 0, absl::string_view("HT"));
@@ -2030,6 +2048,7 @@
   // Verify that a draining stream (which has received a FIN but not consumed
   // it) does not count against the open quota (because it is closed from the
   // protocol point of view).
+  CompleteHandshake();
   if (VersionHasIetfQuicFrames(transport_version())) {
     // On IETF QUIC, we will expect to see a MAX_STREAMS go out when there are
     // not enough streams to create the next one.
@@ -2186,6 +2205,7 @@
 }
 
 TEST_P(QuicSessionTestClient, RecordFinAfterReadSideClosed) {
+  CompleteHandshake();
   // Verify that an incoming FIN is recorded in a stream object even if the read
   // side has been closed.  This prevents an entry from being made in
   // locally_closed_streams_highest_offset_ (which will never be deleted).
@@ -2291,6 +2311,7 @@
 }
 
 TEST_P(QuicSessionTestServer, ZombieStreams) {
+  CompleteHandshake();
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   QuicStreamPeer::SetStreamBytesWritten(3, stream2);
   EXPECT_TRUE(stream2->IsWaitingForAcks());
@@ -2304,6 +2325,7 @@
 }
 
 TEST_P(QuicSessionTestServer, RstStreamReceivedAfterRstStreamSent) {
+  CompleteHandshake();
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   QuicStreamPeer::SetStreamBytesWritten(3, stream2);
   EXPECT_TRUE(stream2->IsWaitingForAcks());
@@ -2323,6 +2345,7 @@
 
 // Regression test of b/71548958.
 TEST_P(QuicSessionTestServer, TestZombieStreams) {
+  CompleteHandshake();
   session_.set_writev_consumes_all_data(true);
 
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
@@ -2455,6 +2478,7 @@
 }
 
 TEST_P(QuicSessionTestServer, DonotRetransmitDataOfClosedStreams) {
+  CompleteHandshake();
   InSequence s;
 
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
@@ -2494,6 +2518,7 @@
 }
 
 TEST_P(QuicSessionTestServer, RetransmitFrames) {
+  CompleteHandshake();
   MockSendAlgorithm* send_algorithm = new StrictMock<MockSendAlgorithm>;
   QuicConnectionPeer::SetSendAlgorithm(session_.connection(), send_algorithm);
   InSequence s;
@@ -2530,6 +2555,7 @@
 
 // Regression test of b/110082001.
 TEST_P(QuicSessionTestServer, RetransmitLostDataCausesConnectionClose) {
+  CompleteHandshake();
   // This test mimics the scenario when a dynamic stream retransmits lost data
   // and causes connection close.
   TestStream* stream = session_.CreateOutgoingBidirectionalStream();
@@ -2568,13 +2594,7 @@
                 MakeSpan(connection_->helper()->GetStreamSendBufferAllocator(),
                          "", &storage)));
 
-  // Finish handshake.
-  if (connection_->version().HasHandshakeDone()) {
-    EXPECT_CALL(*connection_, SendControlFrame(_));
-  }
-  CryptoHandshakeMessage handshake_message;
-  connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
-  session_.GetMutableCryptoStream()->OnHandshakeMessage(handshake_message);
+  CompleteHandshake();
   EXPECT_TRUE(session_.OneRttKeysAvailable());
 
   absl::string_view message;
@@ -2615,6 +2635,7 @@
 
 // Regression test of b/115323618.
 TEST_P(QuicSessionTestServer, LocallyResetZombieStreams) {
+  CompleteHandshake();
   session_.set_writev_consumes_all_data(true);
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   std::string body(100, '.');
@@ -2644,6 +2665,7 @@
 }
 
 TEST_P(QuicSessionTestServer, CleanUpClosedStreamsAlarm) {
+  CompleteHandshake();
   EXPECT_FALSE(
       QuicSessionPeer::GetCleanUpClosedStreamsAlarm(&session_)->IsSet());
 
@@ -2916,7 +2938,7 @@
   if (!VersionHasIetfQuicFrames(transport_version())) {
     return;
   }
-
+  CompleteHandshake();
   TestStream* stream = session_.CreateOutgoingBidirectionalStream();
   QuicStreamId stream_id = stream->id();
   CloseStream(stream_id);
@@ -2943,6 +2965,7 @@
 // If a STOP_SENDING is received for a peer initiated stream, the new stream
 // will be created.
 TEST_P(QuicSessionTestServer, OnStopSendingNewStream) {
+  CompleteHandshake();
   if (!VersionHasIetfQuicFrames(transport_version())) {
     return;
   }
@@ -2962,6 +2985,7 @@
 
 // For a valid stream, ensure that all works
 TEST_P(QuicSessionTestServer, OnStopSendingInputValidStream) {
+  CompleteHandshake();
   if (!VersionHasIetfQuicFrames(transport_version())) {
     // Applicable only to IETF QUIC
     return;
@@ -3027,6 +3051,7 @@
 }
 
 TEST_P(QuicSessionTestServer, ResetForIETFStreamTypes) {
+  CompleteHandshake();
   if (!VersionHasIetfQuicFrames(transport_version())) {
     return;
   }
diff --git a/quic/core/quic_stream_test.cc b/quic/core/quic_stream_test.cc
index d6ad3f8..563a7fd 100644
--- a/quic/core/quic_stream_test.cc
+++ b/quic/core/quic_stream_test.cc
@@ -621,9 +621,9 @@
   EXPECT_CALL(*connection_,
               CloseConnection(QUIC_FLOW_CONTROL_RECEIVED_TOO_MUCH_DATA, _, _))
       .Times(0);
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(AtLeast(1))
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
 
   std::string data(1000, 'x');
   for (QuicStreamOffset offset = 0;
@@ -951,9 +951,9 @@
   EXPECT_TRUE(session_->HasUnackedStreamData());
   EXPECT_CALL(*connection_,
               OnStreamReset(stream_->id(), QUIC_STREAM_CANCELLED));
-  EXPECT_CALL(*connection_, SendControlFrame(_))
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
       .Times(AtLeast(1))
-      .WillRepeatedly(Invoke(&ClearControlFrame));
+      .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
   if (!session_->split_up_send_rst()) {
     EXPECT_CALL(*session_,
                 SendRstStream(stream_->id(), QUIC_STREAM_CANCELLED, 9, _))
@@ -1475,8 +1475,8 @@
 
   EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _))
       .WillRepeatedly(Invoke(session_.get(), &MockQuicSession::ConsumeData));
-  EXPECT_CALL(*connection_, SendControlFrame(_))
-      .WillOnce(Invoke(&ClearControlFrame));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
+      .WillOnce(Invoke(&ClearControlFrameWithTransmissionType));
   std::string data(1024, '.');
   stream->WriteOrBufferData(data, false, nullptr);
   EXPECT_FALSE(HasWriteBlockedStreams());
@@ -1509,8 +1509,8 @@
   std::string data(100, '.');
   EXPECT_CALL(*session_, WritevData(_, _, _, _, _, _))
       .WillRepeatedly(Invoke(session_.get(), &MockQuicSession::ConsumeData));
-  EXPECT_CALL(*connection_, SendControlFrame(_))
-      .WillOnce(Invoke(&ClearControlFrame));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
+      .WillOnce(Invoke(&ClearControlFrameWithTransmissionType));
   stream->WriteOrBufferData(data, false, nullptr);
   EXPECT_FALSE(HasWriteBlockedStreams());
 
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc
index d6395d0..30ed4fd 100644
--- a/quic/test_tools/quic_connection_peer.cc
+++ b/quic/test_tools/quic_connection_peer.cc
@@ -394,5 +394,10 @@
   connection->connected_ = false;
 }
 
+// static
+void QuicConnectionPeer::SendPing(QuicConnection* connection) {
+  connection->SendPingAtLevel(connection->encryption_level());
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h
index ecc98c5..8859a58 100644
--- a/quic/test_tools/quic_connection_peer.h
+++ b/quic/test_tools/quic_connection_peer.h
@@ -162,6 +162,8 @@
   pending_path_challenge_payloads(QuicConnection* connection);
 
   static void SetConnectionClose(QuicConnection* connection);
+
+  static void SendPing(QuicConnection* connection);
 };
 
 }  // namespace test
diff --git a/quic/test_tools/quic_test_utils.cc b/quic/test_tools/quic_test_utils.cc
index 0357ee2..8bf49d9 100644
--- a/quic/test_tools/quic_test_utils.cc
+++ b/quic/test_tools/quic_test_utils.cc
@@ -214,6 +214,11 @@
   return true;
 }
 
+bool ClearControlFrameWithTransmissionType(const QuicFrame& frame,
+                                           TransmissionType /*type*/) {
+  return ClearControlFrame(frame);
+}
+
 uint64_t SimpleRandom::RandUint64() {
   uint64_t result;
   RandBytes(&result, sizeof(result));
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h
index 0224235..f05859d 100644
--- a/quic/test_tools/quic_test_utils.h
+++ b/quic/test_tools/quic_test_utils.h
@@ -254,6 +254,8 @@
 
 // Delete |frame| and return true.
 bool ClearControlFrame(const QuicFrame& frame);
+bool ClearControlFrameWithTransmissionType(const QuicFrame& frame,
+                                           TransmissionType type);
 
 // Simple random number generator used to compute random numbers suitable
 // for pseudo-randomly dropping packets in tests.
@@ -840,6 +842,10 @@
                TransmissionType type,
                absl::optional<EncryptionLevel> level),
               (override));
+  MOCK_METHOD(bool,
+              WriteControlFrame,
+              (const QuicFrame& frame, TransmissionType type),
+              (override));
 
   MOCK_METHOD(void,
               SendRstStream,
diff --git a/quic/tools/quic_simple_server_session_test.cc b/quic/tools/quic_simple_server_session_test.cc
index 8cd89f4..28e8899 100644
--- a/quic/tools/quic_simple_server_session_test.cc
+++ b/quic/tools/quic_simple_server_session_test.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "absl/strings/string_view.h"
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
 #include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h"
 #include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
 #include "net/third_party/quiche/src/quic/core/http/http_encoder.h"
@@ -203,6 +204,10 @@
               ());
 
   MOCK_METHOD(void, SendBlocked, (QuicStreamId), (override));
+  MOCK_METHOD(bool,
+              WriteControlFrame,
+              (const QuicFrame& frame, TransmissionType type),
+              (override));
 };
 
 class QuicSimpleServerSessionTest
@@ -256,6 +261,9 @@
     connection_ = new StrictMock<MockQuicConnectionWithSendStreamData>(
         &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions);
     connection_->AdvanceTime(QuicTime::Delta::FromSeconds(1));
+    connection_->SetEncrypter(
+        ENCRYPTION_FORWARD_SECURE,
+        std::make_unique<NullEncrypter>(connection_->perspective()));
     session_ = std::make_unique<MockQuicSimpleServerSession>(
         config_, connection_, &owner_, &stream_helper_, &crypto_config_,
         &compressed_certs_cache_, &memory_cache_backend_);
@@ -266,9 +274,8 @@
     session_->Initialize();
 
     if (VersionHasIetfQuicFrames(transport_version())) {
-      EXPECT_CALL(*connection_, SendControlFrame(_))
-          .WillRepeatedly(Invoke(
-              this, &QuicSimpleServerSessionTest::ClearMaxStreamsControlFrame));
+      EXPECT_CALL(*session_, WriteControlFrame(_, _))
+          .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
     }
     session_->OnConfigNegotiated();
   }
@@ -335,7 +342,8 @@
                           GetNthClientInitiatedBidirectionalId(0),
                           QUIC_ERROR_PROCESSING_STREAM, 0);
   EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _));
+
   if (!VersionHasIetfQuicFrames(transport_version())) {
     // For version 99, this is covered in InjectStopSending()
     EXPECT_CALL(*connection_,
@@ -365,7 +373,7 @@
                           QUIC_ERROR_PROCESSING_STREAM, 0);
   EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
   if (!VersionHasIetfQuicFrames(transport_version())) {
-    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*session_, WriteControlFrame(_, _));
     // For version 99, this is covered in InjectStopSending()
     EXPECT_CALL(*connection_,
                 OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
@@ -406,7 +414,7 @@
                          QUIC_ERROR_PROCESSING_STREAM, 0);
   EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
   if (!VersionHasIetfQuicFrames(transport_version())) {
-    EXPECT_CALL(*connection_, SendControlFrame(_));
+    EXPECT_CALL(*session_, WriteControlFrame(_, _));
     // For version 99, this is covered in InjectStopSending()
     EXPECT_CALL(*connection_,
                 OnStreamReset(GetNthClientInitiatedBidirectionalId(0),
@@ -622,6 +630,9 @@
     ParsedQuicVersionVector supported_versions = SupportedVersions(GetParam());
     connection_ = new StrictMock<MockQuicConnectionWithSendStreamData>(
         &helper_, &alarm_factory_, Perspective::IS_SERVER, supported_versions);
+    connection_->SetEncrypter(
+        ENCRYPTION_FORWARD_SECURE,
+        std::make_unique<NullEncrypter>(connection_->perspective()));
     session_ = std::make_unique<MockQuicSimpleServerSession>(
         config_, connection_, &owner_, &stream_helper_, &crypto_config_,
         &compressed_certs_cache_, &memory_cache_backend_);
@@ -629,9 +640,8 @@
     // Needed to make new session flow control window and server push work.
 
     if (VersionHasIetfQuicFrames(transport_version())) {
-      EXPECT_CALL(*connection_, SendControlFrame(_))
-          .WillRepeatedly(Invoke(this, &QuicSimpleServerSessionServerPushTest::
-                                           ClearMaxStreamsControlFrame));
+      EXPECT_CALL(*session_, WriteControlFrame(_, _))
+          .WillRepeatedly(Invoke(&ClearControlFrameWithTransmissionType));
     }
     session_->OnConfigNegotiated();
 
@@ -881,8 +891,8 @@
     // V99 will send out a STREAMS_BLOCKED frame when it tries to exceed the
     // limit. This will clear the frames so that they do not block the later
     // rst-stream frame.
-    EXPECT_CALL(*connection_, SendControlFrame(_))
-        .WillOnce(Invoke(&ClearControlFrame));
+    EXPECT_CALL(*session_, WriteControlFrame(_, _))
+        .WillOnce(Invoke(&ClearControlFrameWithTransmissionType));
   }
   QuicByteCount data_frame_header_length = PromisePushResources(num_resources);
 
@@ -898,8 +908,8 @@
   QuicRstStreamFrame rst(kInvalidControlFrameId, stream_got_reset,
                          QUIC_STREAM_CANCELLED, 0);
   EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
-  EXPECT_CALL(*connection_, SendControlFrame(_))
-      .WillOnce(Invoke(&ClearControlFrame));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _))
+      .WillOnce(Invoke(&ClearControlFrameWithTransmissionType));
   EXPECT_CALL(*connection_,
               OnStreamReset(stream_got_reset, QUIC_RST_ACKNOWLEDGEMENT));
   session_->OnRstStream(rst);
@@ -968,8 +978,8 @@
     // V99 will send out a stream-id-blocked frame when the we desired to exceed
     // the limit. This will clear the frames so that they do not block the later
     // rst-stream frame.
-    EXPECT_CALL(*connection_, SendControlFrame(_))
-        .WillOnce(Invoke(&ClearControlFrame));
+    EXPECT_CALL(*session_, WriteControlFrame(_, _))
+        .WillOnce(Invoke(&ClearControlFrameWithTransmissionType));
   }
   QuicByteCount data_frame_header_length = PromisePushResources(num_resources);
   QuicStreamId stream_to_open;
@@ -983,7 +993,7 @@
   // Resetting an open stream will close the stream and give space for extra
   // stream to be opened.
   QuicStreamId stream_got_reset = GetNthServerInitiatedUnidirectionalId(3);
-  EXPECT_CALL(*connection_, SendControlFrame(_));
+  EXPECT_CALL(*session_, WriteControlFrame(_, _));
   if (!VersionHasIetfQuicFrames(transport_version())) {
     EXPECT_CALL(owner_, OnRstStreamReceived(_)).Times(1);
     // For version 99, this is covered in InjectStopSending()