Add QUIC_BUG_IF to QuicUtils::GetCryptoStreamId

In QUIC versions that use CRYPTO frames (instead of stream 1 frames) for
the crypto handshake, the concept of a "crypto stream ID" makes no
sense, so QuicUtils::GetCryptoStreamId should hit a QUIC_BUG_IF to
prevent its misuse.

gfe-relnote: Add QUIC_BUG_IF protected behind QuicVersionUsesCryptoFrames
PiperOrigin-RevId: 248463613
Change-Id: If6768658e9ffc058778b53a91f95839826602fbf
diff --git a/quic/core/chlo_extractor.cc b/quic/core/chlo_extractor.cc
index 2398693..ec3a899 100644
--- a/quic/core/chlo_extractor.cc
+++ b/quic/core/chlo_extractor.cc
@@ -151,8 +151,8 @@
     return false;
   }
   QuicStringPiece data(frame.data_buffer, frame.data_length);
-  if (frame.stream_id ==
-          QuicUtils::GetCryptoStreamId(framer_->transport_version()) &&
+  if (QuicUtils::IsCryptoStreamId(framer_->transport_version(),
+                                  frame.stream_id) &&
       frame.offset == 0 && QuicTextUtils::StartsWith(data, "CHLO")) {
     return OnHandshakeData(data);
   }
diff --git a/quic/core/chlo_extractor_test.cc b/quic/core/chlo_extractor_test.cc
index e5e084c..258e141 100644
--- a/quic/core/chlo_extractor_test.cc
+++ b/quic/core/chlo_extractor_test.cc
@@ -141,11 +141,15 @@
 }
 
 TEST_F(ChloExtractorTest, DoesNotFindValidChloOnWrongStream) {
+  ParsedQuicVersion version = AllSupportedVersions()[0];
+  if (QuicVersionUsesCryptoFrames(version.transport_version)) {
+    return;
+  }
   CryptoHandshakeMessage client_hello;
   client_hello.set_tag(kCHLO);
 
   std::string client_hello_str(client_hello.GetSerialized().AsStringPiece());
-  MakePacket(AllSupportedVersions()[0], client_hello_str,
+  MakePacket(version, client_hello_str,
              /*munge_offset*/ false, /*munge_stream_id*/ true);
   EXPECT_FALSE(ChloExtractor::Extract(*packet_, AllSupportedVersions(), {},
                                       &delegate_,
@@ -165,7 +169,11 @@
 }
 
 TEST_F(ChloExtractorTest, DoesNotFindInvalidChlo) {
-  MakePacket(AllSupportedVersions()[0], "foo", /*munge_offset*/ false,
+  ParsedQuicVersion version = AllSupportedVersions()[0];
+  if (QuicVersionUsesCryptoFrames(version.transport_version)) {
+    return;
+  }
+  MakePacket(version, "foo", /*munge_offset*/ false,
              /*munge_stream_id*/ true);
   EXPECT_FALSE(ChloExtractor::Extract(*packet_, AllSupportedVersions(), {},
                                       &delegate_,
diff --git a/quic/core/congestion_control/uber_loss_algorithm_test.cc b/quic/core/congestion_control/uber_loss_algorithm_test.cc
index 33b9393..1fe3465 100644
--- a/quic/core/congestion_control/uber_loss_algorithm_test.cc
+++ b/quic/core/congestion_control/uber_loss_algorithm_test.cc
@@ -31,12 +31,17 @@
 
   void SendPacket(uint64_t packet_number, EncryptionLevel encryption_level) {
     QuicStreamFrame frame;
-    frame.stream_id =
-        encryption_level == ENCRYPTION_INITIAL
-            ? QuicUtils::GetCryptoStreamId(
-                  CurrentSupportedVersions()[0].transport_version)
-            : QuicUtils::GetHeadersStreamId(
-                  CurrentSupportedVersions()[0].transport_version);
+    QuicTransportVersion version =
+        CurrentSupportedVersions()[0].transport_version;
+    frame.stream_id = QuicUtils::GetHeadersStreamId(version);
+    if (encryption_level == ENCRYPTION_INITIAL) {
+      if (QuicVersionUsesCryptoFrames(version)) {
+        frame.stream_id = QuicUtils::GetFirstBidirectionalStreamId(
+            version, Perspective::IS_CLIENT);
+      } else {
+        frame.stream_id = QuicUtils::GetCryptoStreamId(version);
+      }
+    }
     SerializedPacket packet(QuicPacketNumber(packet_number),
                             PACKET_1BYTE_PACKET_NUMBER, nullptr, kDefaultLength,
                             false, false);
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index 599f939..77d105c 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -207,10 +207,8 @@
   EXPECT_TRUE(session_->IsEncryptionEstablished());
   QuicSpdyClientStream* stream = session_->CreateOutgoingBidirectionalStream();
   ASSERT_TRUE(stream != nullptr);
-  if (!QuicVersionUsesCryptoFrames(GetParam().transport_version)) {
-    EXPECT_NE(QuicUtils::GetCryptoStreamId(connection_->transport_version()),
-              stream->id());
-  }
+  EXPECT_FALSE(QuicUtils::IsCryptoStreamId(connection_->transport_version(),
+                                           stream->id()));
 
   // Process an "inchoate" REJ from the server which will cause
   // an inchoate CHLO to be sent and will leave the encryption level
@@ -300,11 +298,8 @@
     // frame; pretend we got one.
 
     // Note that this is to be the second stream created, hence
-    // the stream count is 4 (the two streams created as a part of
-    // the test plus the crypto and header stream, internally created).
-    // TODO(nharper): Change 4 to 3 & update comment accordingly when the crypto
-    // stuff is no longer in a regular stream.
-    // TODO(fkastenholz): do above if nharper doesn't :-)
+    // the stream count is 3 (the two streams created as a part of
+    // the test plus the header stream, internally created).
     QuicMaxStreamsFrame frame(
         0,
         QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
@@ -316,11 +311,9 @@
   stream = session_->CreateOutgoingBidirectionalStream();
   EXPECT_NE(nullptr, stream);
   if (GetParam().transport_version == QUIC_VERSION_99) {
-    // Ensure that we have/have had four open streams, crypto, header, and the
-    // two test streams. Primary purpose of this is to fail when crypto
-    // no longer uses a normal stream. Some above constants will then need
-    // to be changed.
-    EXPECT_EQ(4u,
+    // Ensure that we have/have had three open streams: two test streams and the
+    // header stream.
+    EXPECT_EQ(3u,
               QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
                   ->outgoing_stream_count());
   }
@@ -384,11 +377,8 @@
   EXPECT_EQ(0u, session_->GetNumOpenOutgoingStreams());
   if (GetParam().transport_version == QUIC_VERSION_99) {
     // Note that this is to be the second stream created, hence
-    // the stream count is 4 (the two streams created as a part of
-    // the test plus the crypto and header stream, internally created).
-    // TODO(nharper): Change 4 to 3 & update comment accordingly when the crypto
-    // stuff is no longer in a regular stream.
-    // TODO(fkastenholz): do above if nharper doesn't :-)
+    // the stream count is 3 (the two streams created as a part of
+    // the test plus the header stream, internally created).
     QuicMaxStreamsFrame frame(
         0,
         QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
@@ -401,11 +391,9 @@
   stream = session_->CreateOutgoingBidirectionalStream();
   EXPECT_NE(nullptr, stream);
   if (GetParam().transport_version == QUIC_VERSION_99) {
-    // Ensure that we have/have had four open streams, crypto, header, and the
-    // two test streams. Primary purpose of this is to fail when crypto
-    // no longer uses a normal stream. Some above constants will then need
-    // to be changed.
-    EXPECT_EQ(4u,
+    // Ensure that we have/have had three open streams: two test streams and the
+    // header stream.
+    EXPECT_EQ(3u,
               QuicSessionPeer::v99_bidirectional_stream_id_manager(&*session_)
                   ->outgoing_stream_count());
   }
@@ -448,7 +436,7 @@
 
   EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
   session_->OnStreamHeaderList(
-      QuicUtils::GetCryptoStreamId(connection_->transport_version()),
+      QuicUtils::GetHeadersStreamId(connection_->transport_version()),
       /*fin=*/false, 0, trailers);
 }
 
@@ -464,7 +452,7 @@
 
   EXPECT_CALL(*connection_, CloseConnection(_, _, _)).Times(1);
   session_->OnPromiseHeaderList(
-      QuicUtils::GetCryptoStreamId(connection_->transport_version()),
+      QuicUtils::GetHeadersStreamId(connection_->transport_version()),
       promised_stream_id_, 0, trailers);
 }
 
diff --git a/quic/core/http/quic_spdy_session_test.cc b/quic/core/http/quic_spdy_session_test.cc
index c7ce057..e0e417a 100644
--- a/quic/core/http/quic_spdy_session_test.cc
+++ b/quic/core/http/quic_spdy_session_test.cc
@@ -94,7 +94,7 @@
 
   MOCK_METHOD0(OnCanWrite, void());
 
-  bool HasPendingCryptoRetransmission() override { return false; }
+  bool HasPendingCryptoRetransmission() const override { return false; }
 
   MOCK_CONST_METHOD0(HasPendingRetransmission, bool());
 
@@ -241,9 +241,8 @@
 
   QuicConsumedData SendStreamData(QuicStream* stream) {
     struct iovec iov;
-    if ((QuicVersionUsesCryptoFrames(connection()->transport_version()) ||
-         stream->id() !=
-             QuicUtils::GetCryptoStreamId(connection()->transport_version())) &&
+    if (!QuicUtils::IsCryptoStreamId(connection()->transport_version(),
+                                     stream->id()) &&
         connection()->encryption_level() != ENCRYPTION_FORWARD_SECURE) {
       this->connection()->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
     }
@@ -396,6 +395,12 @@
     if (perspective == Perspective::IS_SERVER) {
       id |= 0x1;
     }
+    if (bidirectional && perspective == Perspective::IS_CLIENT &&
+        QuicVersionUsesCryptoFrames(transport_version())) {
+      // Once stream ID 0 is used as a normal client initiated bidirectional
+      // stream, this shouldn't be needed any more.
+      id += 4;
+    }
     return id;
   }
 
@@ -1072,7 +1077,7 @@
 TEST_P(QuicSpdySessionTestServer, OnStreamFrameFinStaticStreamId) {
   // Send two bytes of payload.
   QuicStreamFrame data1(
-      QuicUtils::GetCryptoStreamId(connection_->transport_version()), true, 0,
+      QuicUtils::GetHeadersStreamId(connection_->transport_version()), true, 0,
       QuicStringPiece("HT"));
   EXPECT_CALL(*connection_,
               CloseConnection(
@@ -1085,7 +1090,7 @@
   // Send two bytes of payload.
   QuicRstStreamFrame rst1(
       kInvalidControlFrameId,
-      QuicUtils::GetCryptoStreamId(connection_->transport_version()),
+      QuicUtils::GetHeadersStreamId(connection_->transport_version()),
       QUIC_ERROR_PROCESSING_STREAM, 0);
   EXPECT_CALL(*connection_,
               CloseConnection(
@@ -1559,7 +1564,7 @@
     EXPECT_CALL(
         *connection_,
         CloseConnection(QUIC_INVALID_STREAM_ID,
-                        "Stream id 28 would exceed stream count limit 7", _));
+                        "Stream id 28 would exceed stream count limit 6", _));
   }
   // Create one more data streams to exceed limit of open stream.
   QuicStreamFrame data1(kFinalStreamId, false, 0, QuicStringPiece("HT"));
diff --git a/quic/core/http/quic_spdy_stream.cc b/quic/core/http/quic_spdy_stream.cc
index bcea7e5..d04efc5 100644
--- a/quic/core/http/quic_spdy_stream.cc
+++ b/quic/core/http/quic_spdy_stream.cc
@@ -146,9 +146,8 @@
       http_decoder_visitor_(new HttpDecoderVisitor(this)),
       body_buffer_(sequencer()),
       ack_listener_(nullptr) {
-  DCHECK_NE(QuicUtils::GetCryptoStreamId(
-                spdy_session->connection()->transport_version()),
-            id);
+  DCHECK(!QuicUtils::IsCryptoStreamId(
+      spdy_session->connection()->transport_version(), id));
   // Don't receive any callbacks from the sequencer until headers
   // are complete.
   sequencer()->SetBlockedUntilFlush();
@@ -172,9 +171,8 @@
       http_decoder_visitor_(new HttpDecoderVisitor(this)),
       body_buffer_(sequencer()),
       ack_listener_(nullptr) {
-  DCHECK_NE(QuicUtils::GetCryptoStreamId(
-                spdy_session->connection()->transport_version()),
-            id());
+  DCHECK(!QuicUtils::IsCryptoStreamId(
+      spdy_session->connection()->transport_version(), id()));
   // Don't receive any callbacks from the sequencer until headers
   // are complete.
   sequencer()->SetBlockedUntilFlush();
diff --git a/quic/core/legacy_quic_stream_id_manager.cc b/quic/core/legacy_quic_stream_id_manager.cc
index 6fb662a..447f16d 100644
--- a/quic/core/legacy_quic_stream_id_manager.cc
+++ b/quic/core/legacy_quic_stream_id_manager.cc
@@ -19,14 +19,18 @@
     : session_(session),
       max_open_outgoing_streams_(max_open_outgoing_streams),
       max_open_incoming_streams_(max_open_incoming_streams),
-      next_outgoing_stream_id_(
-          QuicUtils::GetCryptoStreamId(
-              session->connection()->transport_version()) +
-          (session->perspective() == Perspective::IS_SERVER ? 1 : 2)),
+      next_outgoing_stream_id_(QuicUtils::GetFirstBidirectionalStreamId(
+          session->connection()->transport_version(),
+          session->perspective())),
       largest_peer_created_stream_id_(
           session->perspective() == Perspective::IS_SERVER
-              ? QuicUtils::GetCryptoStreamId(
-                    session->connection()->transport_version())
+              ? (QuicVersionUsesCryptoFrames(
+                     session->connection()->transport_version())
+                     ? QuicUtils::GetFirstBidirectionalStreamId(
+                           session->connection()->transport_version(),
+                           Perspective::IS_CLIENT)
+                     : QuicUtils::GetCryptoStreamId(
+                           session->connection()->transport_version()))
               : QuicUtils::GetInvalidStreamId(
                     session->connection()->transport_version())) {}
 
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index af388a9..c1d1d76 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -1027,7 +1027,7 @@
   if (debug_visitor_ != nullptr) {
     debug_visitor_->OnStreamFrame(frame);
   }
-  if (frame.stream_id != QuicUtils::GetCryptoStreamId(transport_version()) &&
+  if (!QuicUtils::IsCryptoStreamId(transport_version(), frame.stream_id) &&
       last_decrypted_packet_level_ == ENCRYPTION_INITIAL) {
     if (MaybeConsiderAsMemoryCorruption(frame)) {
       CloseConnection(QUIC_MAYBE_CORRUPTED_MEMORY,
@@ -3744,7 +3744,7 @@
 
 bool QuicConnection::MaybeConsiderAsMemoryCorruption(
     const QuicStreamFrame& frame) {
-  if (frame.stream_id == QuicUtils::GetCryptoStreamId(transport_version()) ||
+  if (QuicUtils::IsCryptoStreamId(transport_version(), frame.stream_id) ||
       last_decrypted_packet_level_ != ENCRYPTION_INITIAL) {
     return false;
   }
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index d80b55f..4ecb780 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -660,7 +660,7 @@
                                             QuicStreamOffset offset,
                                             StreamSendingState state) {
     ScopedPacketFlusher flusher(this, NO_ACK);
-    if (id != QuicUtils::GetCryptoStreamId(transport_version()) &&
+    if (!QuicUtils::IsCryptoStreamId(transport_version(), id) &&
         this->encryption_level() == ENCRYPTION_INITIAL) {
       this->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
     }
@@ -709,6 +709,11 @@
   QuicConsumedData SendCryptoStreamData() {
     QuicStreamOffset offset = 0;
     QuicStringPiece data("chlo");
+    return SendCryptoDataWithString(data, offset);
+  }
+
+  QuicConsumedData SendCryptoDataWithString(QuicStringPiece data,
+                                            QuicStreamOffset offset) {
     if (!QuicVersionUsesCryptoFrames(transport_version())) {
       return SendStreamDataWithString(
           QuicUtils::GetCryptoStreamId(transport_version()), data, offset,
@@ -815,6 +820,8 @@
     next_effective_peer_addr_ = QuicMakeUnique<QuicSocketAddress>(addr);
   }
 
+  SimpleDataProducer* producer() { return &producer_; }
+
   using QuicConnection::active_effective_peer_migration_type;
   using QuicConnection::IsCurrentPacketConnectivityProbing;
   using QuicConnection::SelectMutualVersion;
@@ -917,14 +924,9 @@
         creator_(QuicConnectionPeer::GetPacketCreator(&connection_)),
         generator_(QuicConnectionPeer::GetPacketGenerator(&connection_)),
         manager_(QuicConnectionPeer::GetSentPacketManager(&connection_)),
-        frame1_(QuicUtils::GetCryptoStreamId(version().transport_version),
-                false,
-                0,
-                QuicStringPiece(data1)),
-        frame2_(QuicUtils::GetCryptoStreamId(version().transport_version),
-                false,
-                3,
-                QuicStringPiece(data2)),
+        frame1_(0, false, 0, QuicStringPiece(data1)),
+        frame2_(0, false, 3, QuicStringPiece(data2)),
+        crypto_frame_(ENCRYPTION_INITIAL, 0, QuicStringPiece(data1)),
         packet_number_length_(PACKET_4BYTE_PACKET_NUMBER),
         connection_id_included_(CONNECTION_ID_PRESENT),
         notifier_(&connection_) {
@@ -944,6 +946,15 @@
       QuicConnectionPeer::SetNoStopWaitingFrames(&connection_,
                                                  GetParam().no_stop_waiting);
     }
+    QuicStreamId stream_id;
+    if (QuicVersionUsesCryptoFrames(version().transport_version)) {
+      stream_id = QuicUtils::GetFirstBidirectionalStreamId(
+          version().transport_version, Perspective::IS_CLIENT);
+    } else {
+      stream_id = QuicUtils::GetCryptoStreamId(version().transport_version);
+    }
+    frame1_.stream_id = stream_id;
+    frame2_.stream_id = stream_id;
     connection_.set_visitor(&visitor_);
     if (connection_.session_decides_what_to_write()) {
       connection_.SetSessionNotifier(&notifier_);
@@ -1180,6 +1191,29 @@
                                     level);
   }
 
+  size_t ProcessCryptoPacketAtLevel(uint64_t number, EncryptionLevel level) {
+    QuicPacketHeader header = ConstructPacketHeader(1000, ENCRYPTION_INITIAL);
+    QuicFrames frames;
+    if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+      frames.push_back(QuicFrame(&crypto_frame_));
+    } else {
+      frames.push_back(QuicFrame(frame1_));
+    }
+    std::unique_ptr<QuicPacket> packet = ConstructPacket(header, frames);
+    char buffer[kMaxOutgoingPacketSize];
+    peer_creator_.set_encryption_level(ENCRYPTION_INITIAL);
+    size_t encrypted_length =
+        peer_framer_.EncryptPayload(ENCRYPTION_INITIAL, QuicPacketNumber(1000),
+                                    *packet, buffer, kMaxOutgoingPacketSize);
+    connection_.ProcessUdpPacket(
+        kSelfAddress, kPeerAddress,
+        QuicReceivedPacket(buffer, encrypted_length, clock_.Now(), false));
+    if (connection_.GetSendAlarm()->IsSet()) {
+      connection_.GetSendAlarm()->Fire();
+    }
+    return encrypted_length;
+  }
+
   size_t ProcessDataPacketAtLevel(uint64_t number,
                                   bool has_stop_waiting,
                                   EncryptionLevel level) {
@@ -1302,9 +1336,8 @@
     return packet;
   }
 
-  std::unique_ptr<QuicPacket> ConstructDataPacket(uint64_t number,
-                                                  bool has_stop_waiting,
-                                                  EncryptionLevel level) {
+  QuicPacketHeader ConstructPacketHeader(uint64_t number,
+                                         EncryptionLevel level) {
     QuicPacketHeader header;
     if (peer_framer_.transport_version() > QUIC_VERSION_43 &&
         level < ENCRYPTION_FORWARD_SECURE) {
@@ -1344,7 +1377,13 @@
     }
     header.packet_number_length = packet_number_length_;
     header.packet_number = QuicPacketNumber(number);
+    return header;
+  }
 
+  std::unique_ptr<QuicPacket> ConstructDataPacket(uint64_t number,
+                                                  bool has_stop_waiting,
+                                                  EncryptionLevel level) {
+    QuicPacketHeader header = ConstructPacketHeader(number, level);
     QuicFrames frames;
     frames.push_back(QuicFrame(frame1_));
     if (has_stop_waiting) {
@@ -1514,6 +1553,7 @@
 
   QuicStreamFrame frame1_;
   QuicStreamFrame frame2_;
+  QuicCryptoFrame crypto_frame_;
   QuicAckFrame ack_;
   QuicStopWaitingFrame stop_waiting_;
   QuicPacketNumberLength packet_number_length_;
@@ -1533,19 +1573,27 @@
   EXPECT_EQ(Perspective::IS_CLIENT, connection_.perspective());
   EXPECT_TRUE(connection_.connected());
 
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_));
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_));
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_));
+  }
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   // Cause change in self_address.
   QuicIpAddress host;
   host.FromString("1.1.1.1");
   QuicSocketAddress self_address(host, 123);
-  EXPECT_CALL(visitor_, OnStreamFrame(_));
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), self_address,
-                                  kPeerAddress);
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    EXPECT_CALL(visitor_, OnCryptoFrame(_));
+  } else {
+    EXPECT_CALL(visitor_, OnStreamFrame(_));
+  }
+  ProcessFramePacketWithAddresses(frame, self_address, kPeerAddress);
   EXPECT_TRUE(connection_.connected());
 }
 
@@ -1558,20 +1606,24 @@
   EXPECT_EQ(Perspective::IS_SERVER, connection_.perspective());
   EXPECT_TRUE(connection_.connected());
 
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_));
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_));
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_));
+  }
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   // Cause change in self_address.
   QuicIpAddress host;
   host.FromString("1.1.1.1");
   QuicSocketAddress self_address(host, 123);
   EXPECT_CALL(visitor_, AllowSelfAddressChange()).WillOnce(Return(false));
   EXPECT_CALL(visitor_, OnConnectionClosed(QUIC_ERROR_MIGRATING_ADDRESS, _, _));
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), self_address,
-                                  kPeerAddress);
+  ProcessFramePacketWithAddresses(frame, self_address, kPeerAddress);
   EXPECT_FALSE(connection_.connected());
 }
 
@@ -1584,26 +1636,29 @@
   EXPECT_EQ(Perspective::IS_SERVER, connection_.perspective());
   EXPECT_TRUE(connection_.connected());
 
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(3);
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(3);
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(3);
+  }
   QuicIpAddress host;
   host.FromString("1.1.1.1");
   QuicSocketAddress self_address1(host, 443);
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), self_address1,
-                                  kPeerAddress);
+  ProcessFramePacketWithAddresses(frame, self_address1, kPeerAddress);
   // Cause self_address change to mapped Ipv4 address.
   QuicIpAddress host2;
   host2.FromString(
       QuicStrCat("::ffff:", connection_.self_address().host().ToString()));
   QuicSocketAddress self_address2(host2, connection_.self_address().port());
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), self_address2,
-                                  kPeerAddress);
+  ProcessFramePacketWithAddresses(frame, self_address2, kPeerAddress);
   EXPECT_TRUE(connection_.connected());
   // self_address change back to Ipv4 address.
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), self_address1,
-                                  kPeerAddress);
+  ProcessFramePacketWithAddresses(frame, self_address1, kPeerAddress);
   EXPECT_TRUE(connection_.connected());
 }
 
@@ -1619,16 +1674,21 @@
   QuicConnectionPeer::SetEffectivePeerAddress(&connection_,
                                               QuicSocketAddress());
 
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+  }
   QuicPacketCreatorPeer::SetPacketNumber(&peer_creator_, 5);
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
   const QuicSocketAddress kNewPeerAddress =
       QuicSocketAddress(QuicIpAddress::Loopback6(),
                         /*port=*/23456);
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kNewPeerAddress);
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kNewPeerAddress);
   EXPECT_EQ(kNewPeerAddress, connection_.peer_address());
   EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address());
 
@@ -1636,8 +1696,7 @@
   QuicPacketCreatorPeer::SetPacketNumber(&peer_creator_, 4);
   // This is an old packet, do not migrate.
   EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0);
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kNewPeerAddress, connection_.peer_address());
   EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address());
 }
@@ -1656,12 +1715,17 @@
                                               QuicSocketAddress());
   EXPECT_FALSE(connection_.effective_peer_address().IsInitialized());
 
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+  }
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kPeerAddress, connection_.peer_address());
   EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
 
@@ -1670,8 +1734,7 @@
   const QuicSocketAddress kNewPeerAddress =
       QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
   EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(1);
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kNewPeerAddress);
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kNewPeerAddress);
   EXPECT_EQ(kNewPeerAddress, connection_.peer_address());
   EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address());
 }
@@ -1692,12 +1755,17 @@
       QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/43210);
   connection_.ReturnEffectivePeerAddressForNextPacket(kEffectivePeerAddress);
 
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+  }
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kPeerAddress, connection_.peer_address());
   EXPECT_EQ(kEffectivePeerAddress, connection_.effective_peer_address());
 
@@ -1707,8 +1775,7 @@
       QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/54321);
   connection_.ReturnEffectivePeerAddressForNextPacket(kNewEffectivePeerAddress);
   EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(1);
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kPeerAddress, connection_.peer_address());
   EXPECT_EQ(kNewEffectivePeerAddress, connection_.effective_peer_address());
 
@@ -1737,8 +1804,7 @@
   connection_.ReturnEffectivePeerAddressForNextPacket(
       kNewerEffectivePeerAddress);
   EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(1);
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kFinalPeerAddress);
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kFinalPeerAddress);
   EXPECT_EQ(kFinalPeerAddress, connection_.peer_address());
   EXPECT_EQ(kNewerEffectivePeerAddress, connection_.effective_peer_address());
   EXPECT_EQ(PORT_CHANGE, connection_.active_effective_peer_migration_type());
@@ -1752,8 +1818,7 @@
       kNewestEffectivePeerAddress);
   EXPECT_CALL(visitor_, OnConnectionMigration(IPV6_TO_IPV4_CHANGE)).Times(1);
   EXPECT_CALL(*send_algorithm_, OnConnectionMigration()).Times(1);
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kFinalPeerAddress);
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kFinalPeerAddress);
   EXPECT_EQ(kFinalPeerAddress, connection_.peer_address());
   EXPECT_EQ(kNewestEffectivePeerAddress, connection_.effective_peer_address());
   EXPECT_EQ(IPV6_TO_IPV4_CHANGE,
@@ -1774,12 +1839,17 @@
                                               QuicSocketAddress());
   EXPECT_FALSE(connection_.effective_peer_address().IsInitialized());
 
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+  }
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kPeerAddress, connection_.peer_address());
   EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
 
@@ -1881,12 +1951,17 @@
                                               QuicSocketAddress());
   EXPECT_FALSE(connection_.effective_peer_address().IsInitialized());
 
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+  }
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kPeerAddress, connection_.peer_address());
   EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
 
@@ -1916,8 +1991,7 @@
   // Process another packet with the old peer address on server side will not
   // start peer migration.
   EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0);
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kPeerAddress, connection_.peer_address());
   EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
 }
@@ -1936,13 +2010,18 @@
                                               QuicSocketAddress());
   EXPECT_FALSE(connection_.effective_peer_address().IsInitialized());
 
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+  }
   QuicPacketCreatorPeer::SetPacketNumber(&peer_creator_, 5);
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kPeerAddress, connection_.peer_address());
   EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
 
@@ -1988,12 +2067,17 @@
                                               QuicSocketAddress());
   EXPECT_FALSE(connection_.effective_peer_address().IsInitialized());
 
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+  }
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kPeerAddress, connection_.peer_address());
   EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
 
@@ -2018,8 +2102,7 @@
   // side will start peer migration.
   EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(1);
 
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kNewPeerAddress);
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kNewPeerAddress);
   EXPECT_EQ(kNewPeerAddress, connection_.peer_address());
   EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address());
 }
@@ -2037,12 +2120,17 @@
                                               QuicSocketAddress());
   EXPECT_FALSE(connection_.effective_peer_address().IsInitialized());
 
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+  }
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kPeerAddress, connection_.peer_address());
   EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
 
@@ -2079,12 +2167,17 @@
                                               QuicSocketAddress());
   EXPECT_FALSE(connection_.effective_peer_address().IsInitialized());
 
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+  }
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kPeerAddress, connection_.peer_address());
   EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
 
@@ -2124,12 +2217,17 @@
                                               QuicSocketAddress());
   EXPECT_FALSE(connection_.effective_peer_address().IsInitialized());
 
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+  }
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
   EXPECT_EQ(kPeerAddress, connection_.peer_address());
   EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
 
@@ -2138,8 +2236,7 @@
   const QuicSocketAddress kNewPeerAddress =
       QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
   EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0);
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kNewPeerAddress);
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kNewPeerAddress);
   EXPECT_EQ(kNewPeerAddress, connection_.peer_address());
   EXPECT_EQ(kNewPeerAddress, connection_.effective_peer_address());
 }
@@ -2177,7 +2274,11 @@
 
   QuicFrames frames;
   QuicPaddingFrame padding;
-  frames.push_back(QuicFrame(frame1_));
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frames.push_back(QuicFrame(&crypto_frame_));
+  } else {
+    frames.push_back(QuicFrame(frame1_));
+  }
   frames.push_back(QuicFrame(padding));
   std::unique_ptr<QuicPacket> packet(ConstructPacket(header, frames));
   char buffer[kMaxOutgoingPacketSize];
@@ -2187,7 +2288,11 @@
   EXPECT_EQ(kMaxOutgoingPacketSize, encrypted_length);
 
   framer_.set_version(version());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(1);
+  } else {
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+  }
   connection_.ProcessUdpPacket(
       kSelfAddress, kPeerAddress,
       QuicReceivedPacket(buffer, encrypted_length, QuicTime::Zero(), false));
@@ -2218,7 +2323,11 @@
 
   QuicFrames frames;
   QuicPaddingFrame padding;
-  frames.push_back(QuicFrame(frame1_));
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frames.push_back(QuicFrame(&crypto_frame_));
+  } else {
+    frames.push_back(QuicFrame(frame1_));
+  }
   frames.push_back(QuicFrame(padding));
   std::unique_ptr<QuicPacket> packet(ConstructPacket(header, frames));
   char buffer[kMaxOutgoingPacketSize];
@@ -2228,7 +2337,11 @@
   EXPECT_EQ(kMaxOutgoingPacketSize, encrypted_length);
 
   framer_.set_version(version());
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(1);
+  } else {
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+  }
   connection_.ProcessUdpPacket(
       kSelfAddress, kPeerAddress,
       QuicReceivedPacket(buffer, encrypted_length, QuicTime::Zero(), false));
@@ -2587,7 +2700,8 @@
   }
   // Send a packet containing stream frame.
   SendStreamDataToPeer(
-      QuicUtils::GetCryptoStreamId(connection_.version().transport_version),
+      QuicUtils::GetFirstBidirectionalStreamId(
+          connection_.version().transport_version, Perspective::IS_CLIENT),
       "bar", 0, NO_FIN, nullptr);
 
   // Session will not be informed until receiving another 20 packets.
@@ -2917,9 +3031,13 @@
   EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
   ProcessDataPacket(1);
   QuicPacketNumber last_packet;
-  SendStreamDataToPeer(
-      QuicUtils::GetCryptoStreamId(connection_.version().transport_version),
-      "foo", 0, NO_FIN, &last_packet);
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    connection_.SendCryptoDataWithString("foo", 0);
+  } else {
+    SendStreamDataToPeer(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), "foo", 0,
+        NO_FIN, &last_packet);
+  }
   // Verify ack is bundled with outging packet.
   EXPECT_FALSE(writer_->ack_frames().empty());
 
@@ -2960,6 +3078,7 @@
 }
 
 TEST_P(QuicConnectionTest, FramePackingSendv) {
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   // Send data in 1 packet by writing multiple blocks in a single iovector
   // using writev.
   EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
@@ -2970,25 +3089,25 @@
   iov[0].iov_len = 4;
   iov[1].iov_base = data + 4;
   iov[1].iov_len = 2;
-  connection_.SaveAndSendStreamData(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), iov, 2, 6,
-      0, NO_FIN);
+  QuicStreamId stream_id = QuicUtils::GetFirstBidirectionalStreamId(
+      connection_.transport_version(), Perspective::IS_CLIENT);
+  connection_.SaveAndSendStreamData(stream_id, iov, 2, 6, 0, NO_FIN);
 
   EXPECT_EQ(0u, connection_.NumQueuedPackets());
   EXPECT_FALSE(connection_.HasQueuedData());
 
   // Parse the last packet and ensure multiple iovector blocks have
   // been packed into a single stream frame from one stream.
-  EXPECT_EQ(2u, writer_->frame_count());
+  EXPECT_EQ(1u, writer_->frame_count());
   EXPECT_EQ(1u, writer_->stream_frames().size());
-  EXPECT_EQ(1u, writer_->padding_frames().size());
+  EXPECT_EQ(0u, writer_->padding_frames().size());
   QuicStreamFrame* frame = writer_->stream_frames()[0].get();
-  EXPECT_EQ(QuicUtils::GetCryptoStreamId(connection_.transport_version()),
-            frame->stream_id);
+  EXPECT_EQ(stream_id, frame->stream_id);
   EXPECT_EQ("ABCDEF", QuicStringPiece(frame->data_buffer, frame->data_length));
 }
 
 TEST_P(QuicConnectionTest, FramePackingSendvQueued) {
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   // Try to send two stream frames in 1 packet by using writev.
   EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
 
@@ -2999,9 +3118,9 @@
   iov[0].iov_len = 4;
   iov[1].iov_base = data + 4;
   iov[1].iov_len = 2;
-  connection_.SaveAndSendStreamData(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), iov, 2, 6,
-      0, NO_FIN);
+  QuicStreamId stream_id = QuicUtils::GetFirstBidirectionalStreamId(
+      connection_.transport_version(), Perspective::IS_CLIENT);
+  connection_.SaveAndSendStreamData(stream_id, iov, 2, 6, 0, NO_FIN);
 
   EXPECT_EQ(1u, connection_.NumQueuedPackets());
   EXPECT_TRUE(connection_.HasQueuedData());
@@ -3012,20 +3131,19 @@
   EXPECT_EQ(0u, connection_.NumQueuedPackets());
 
   // Parse the last packet and ensure it's one stream frame from one stream.
-  EXPECT_EQ(2u, writer_->frame_count());
+  EXPECT_EQ(1u, writer_->frame_count());
   EXPECT_EQ(1u, writer_->stream_frames().size());
-  EXPECT_EQ(1u, writer_->padding_frames().size());
-  EXPECT_EQ(QuicUtils::GetCryptoStreamId(connection_.transport_version()),
-            writer_->stream_frames()[0]->stream_id);
+  EXPECT_EQ(0u, writer_->padding_frames().size());
+  EXPECT_EQ(stream_id, writer_->stream_frames()[0]->stream_id);
 }
 
 TEST_P(QuicConnectionTest, SendingZeroBytes) {
   connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   // Send a zero byte write with a fin using writev.
   EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _));
-  connection_.SaveAndSendStreamData(
-      QuicUtils::GetHeadersStreamId(connection_.transport_version()), nullptr,
-      0, 0, 0, FIN);
+  QuicStreamId stream_id = QuicUtils::GetFirstBidirectionalStreamId(
+      connection_.transport_version(), Perspective::IS_CLIENT);
+  connection_.SaveAndSendStreamData(stream_id, nullptr, 0, 0, 0, FIN);
 
   EXPECT_EQ(0u, connection_.NumQueuedPackets());
   EXPECT_FALSE(connection_.HasQueuedData());
@@ -3040,8 +3158,7 @@
   EXPECT_EQ(1u + extra_padding_frames, writer_->frame_count());
   EXPECT_EQ(extra_padding_frames, writer_->padding_frames().size());
   ASSERT_EQ(1u, writer_->stream_frames().size());
-  EXPECT_EQ(QuicUtils::GetHeadersStreamId(connection_.transport_version()),
-            writer_->stream_frames()[0]->stream_id);
+  EXPECT_EQ(stream_id, writer_->stream_frames()[0]->stream_id);
   EXPECT_TRUE(writer_->stream_frames()[0]->fin);
 }
 
@@ -3563,7 +3680,7 @@
   const bool has_stop_waiting = false;
   const EncryptionLevel level = ENCRYPTION_INITIAL;
   std::unique_ptr<QuicPacket> packet(ConstructDataPacket(
-      received_packet_num, has_stop_waiting, ENCRYPTION_INITIAL));
+      received_packet_num, has_stop_waiting, ENCRYPTION_FORWARD_SECURE));
   char buffer[kMaxOutgoingPacketSize];
   size_t encrypted_length =
       peer_framer_.EncryptPayload(level, QuicPacketNumber(received_packet_num),
@@ -3953,9 +4070,11 @@
   // the end of the packet. We can test this to check which encrypter was used.
   connection_.SetEncrypter(ENCRYPTION_INITIAL,
                            QuicMakeUnique<TaggingEncrypter>(0x01));
-  SendStreamDataToPeer(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), "foo", 0,
-      NO_FIN, nullptr);
+  QuicByteCount packet_size;
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .WillOnce(SaveArg<3>(&packet_size));
+  connection_.SendCryptoDataWithString("foo", 0);
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(AnyNumber());
   EXPECT_EQ(0x01010101u, writer_->final_bytes_of_last_packet());
 
   connection_.SetEncrypter(ENCRYPTION_ZERO_RTT,
@@ -3992,9 +4111,7 @@
   // Attempt to send a handshake message and have the socket block.
   EXPECT_CALL(*send_algorithm_, CanSend(_)).WillRepeatedly(Return(true));
   BlockOnNextWrite();
-  connection_.SendStreamDataWithString(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), "foo", 0,
-      NO_FIN);
+  connection_.SendCryptoDataWithString("foo", 0);
   // The packet should be serialized, but not queued.
   EXPECT_EQ(1u, connection_.NumQueuedPackets());
 
@@ -4046,9 +4163,7 @@
                            QuicMakeUnique<TaggingEncrypter>(0x01));
   connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
 
-  SendStreamDataToPeer(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), "foo", 0,
-      NO_FIN, nullptr);
+  connection_.SendCryptoDataWithString("foo", 0);
 
   connection_.SetEncrypter(ENCRYPTION_ZERO_RTT,
                            QuicMakeUnique<TaggingEncrypter>(0x02));
@@ -6235,9 +6350,7 @@
   }
   EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
   ProcessPacket(1);
-  connection_.SendStreamDataWithString(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), "foo", 0,
-      NO_FIN);
+  connection_.SendCryptoDataWithString("foo", 0);
   // Check that ack is bundled with outgoing crypto data.
   if (GetParam().no_stop_waiting) {
     EXPECT_EQ(3u, writer_->frame_count());
@@ -6254,14 +6367,10 @@
   ProcessPacket(1);
   BlockOnNextWrite();
   writer_->set_is_write_blocked_data_buffered(true);
-  connection_.SendStreamDataWithString(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), "foo", 0,
-      NO_FIN);
+  connection_.SendCryptoDataWithString("foo", 0);
   EXPECT_TRUE(writer_->IsWriteBlocked());
   EXPECT_FALSE(connection_.HasQueuedData());
-  connection_.SendStreamDataWithString(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), "bar", 3,
-      NO_FIN);
+  connection_.SendCryptoDataWithString("bar", 3);
   EXPECT_TRUE(writer_->IsWriteBlocked());
   EXPECT_TRUE(connection_.HasQueuedData());
 }
@@ -8209,13 +8318,18 @@
     return;
   }
   set_perspective(Perspective::IS_SERVER);
-  QuicStreamFrame stream_frame(
-      QuicUtils::GetCryptoStreamId(connection_.transport_version()), false, 0u,
-      QuicStringPiece());
+  QuicFrame frame;
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    frame = QuicFrame(&crypto_frame_);
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(1);
+  } else {
+    frame = QuicFrame(QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_.transport_version()), false,
+        0u, QuicStringPiece()));
+    EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+  }
   EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
-  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
-  ProcessFramePacketWithAddresses(QuicFrame(stream_frame), kSelfAddress,
-                                  kPeerAddress);
+  ProcessFramePacketWithAddresses(frame, kSelfAddress, kPeerAddress);
 
   // Let connection process a Google QUIC packet.
   peer_framer_.set_version_for_tests(
@@ -8334,10 +8448,13 @@
     return;
   }
   EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  }
   EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
   use_tagging_decrypter();
   // Receives packet 1000 in initial data.
-  ProcessDataPacketAtLevel(1000, false, ENCRYPTION_INITIAL);
+  ProcessCryptoPacketAtLevel(1000, ENCRYPTION_INITIAL);
   EXPECT_TRUE(connection_.GetAckAlarm()->IsSet());
   peer_framer_.SetEncrypter(ENCRYPTION_ZERO_RTT,
                             QuicMakeUnique<TaggingEncrypter>(0x02));
@@ -8382,10 +8499,13 @@
     return;
   }
   EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+  }
   EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
   use_tagging_decrypter();
   // Receives packet 1000 in initial data.
-  ProcessDataPacketAtLevel(1000, false, ENCRYPTION_INITIAL);
+  ProcessCryptoPacketAtLevel(1000, ENCRYPTION_INITIAL);
   EXPECT_TRUE(connection_.GetAckAlarm()->IsSet());
   peer_framer_.SetEncrypter(ENCRYPTION_ZERO_RTT,
                             QuicMakeUnique<TaggingEncrypter>(0x02));
diff --git a/quic/core/quic_crypto_stream.cc b/quic/core/quic_crypto_stream.cc
index 2263546..f394b3d 100644
--- a/quic/core/quic_crypto_stream.cc
+++ b/quic/core/quic_crypto_stream.cc
@@ -24,8 +24,12 @@
                                                         " ")
 
 QuicCryptoStream::QuicCryptoStream(QuicSession* session)
-    : QuicStream(QuicUtils::GetCryptoStreamId(
-                     session->connection()->transport_version()),
+    : QuicStream(QuicVersionUsesCryptoFrames(
+                     session->connection()->transport_version())
+                     ? QuicUtils::GetInvalidStreamId(
+                           session->connection()->transport_version())
+                     : QuicUtils::GetCryptoStreamId(
+                           session->connection()->transport_version()),
                  session,
                  /*is_static=*/true,
                  QuicVersionUsesCryptoFrames(
@@ -159,6 +163,9 @@
   size_t bytes_consumed =
       session()->connection()->SendCryptoData(level, data.length(), offset);
   session()->connection()->SetDefaultEncryptionLevel(current_level);
+  // Since CRYPTO frames aren't flow controlled, SendCryptoData should have sent
+  // all data we asked it to send.
+  DCHECK_EQ(bytes_consumed, data.length());
 
   send_buffer->OnStreamDataConsumed(bytes_consumed);
 }
@@ -213,7 +220,7 @@
   QuicStream::OnStreamDataConsumed(bytes_consumed);
 }
 
-bool QuicCryptoStream::HasPendingCryptoRetransmission() {
+bool QuicCryptoStream::HasPendingCryptoRetransmission() const {
   if (!QuicVersionUsesCryptoFrames(
           session()->connection()->transport_version())) {
     return false;
diff --git a/quic/core/quic_crypto_stream.h b/quic/core/quic_crypto_stream.h
index 0c6d08a..1b641b1 100644
--- a/quic/core/quic_crypto_stream.h
+++ b/quic/core/quic_crypto_stream.h
@@ -92,7 +92,7 @@
 
   // Returns whether there are any bytes pending retransmission in CRYPTO
   // frames.
-  virtual bool HasPendingCryptoRetransmission();
+  virtual bool HasPendingCryptoRetransmission() const;
 
   // Writes any pending CRYPTO frame retransmissions.
   void WritePendingCryptoRetransmission();
diff --git a/quic/core/quic_dispatcher.cc b/quic/core/quic_dispatcher.cc
index 831aac5..9af5f84 100644
--- a/quic/core/quic_dispatcher.cc
+++ b/quic/core/quic_dispatcher.cc
@@ -186,7 +186,8 @@
         }
         offset += frame.crypto_frame->data_length;
       }
-      if (offset < reject.length()) {
+      if (offset < reject.length() &&
+          !QuicVersionUsesCryptoFrames(framer_.transport_version())) {
         DCHECK(!creator_.HasRoomForStreamFrame(
             QuicUtils::GetCryptoStreamId(framer_.transport_version()), offset,
             frame.stream_frame.data_length));
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index 347df84..bf87ace 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -10012,7 +10012,7 @@
 static QuicStreamId kTestQuicStreamId = 1;
 static bool ExpectedStreamFrame(const QuicStreamFrame& frame) {
   return (frame.stream_id == kTestQuicStreamId ||
-          frame.stream_id == QuicUtils::GetCryptoStreamId(QUIC_VERSION_99)) &&
+          QuicUtils::IsCryptoStreamId(QUIC_VERSION_99, frame.stream_id)) &&
          !frame.fin && frame.offset == 0 &&
          std::string(frame.data_buffer, frame.data_length) == kTestString;
   // FIN is hard-coded false in ConstructEncryptedPacket.
diff --git a/quic/core/quic_packet_creator.cc b/quic/core/quic_packet_creator.cc
index 0ffcc37..c013ce8 100644
--- a/quic/core/quic_packet_creator.cc
+++ b/quic/core/quic_packet_creator.cc
@@ -887,8 +887,8 @@
   QUIC_DVLOG(1) << ENDPOINT << "Adding frame with transmission type "
                 << transmission_type << ": " << frame;
   if (frame.type == STREAM_FRAME &&
-      frame.stream_frame.stream_id !=
-          QuicUtils::GetCryptoStreamId(framer_->transport_version()) &&
+      !QuicUtils::IsCryptoStreamId(framer_->transport_version(),
+                                   frame.stream_frame.stream_id) &&
       (packet_.encryption_level == ENCRYPTION_INITIAL ||
        packet_.encryption_level == ENCRYPTION_HANDSHAKE)) {
     const std::string error_details = QuicStrCat(
@@ -1011,8 +1011,8 @@
 bool QuicPacketCreator::StreamFrameIsClientHello(
     const QuicStreamFrame& frame) const {
   if (framer_->perspective() == Perspective::IS_SERVER ||
-      frame.stream_id !=
-          QuicUtils::GetCryptoStreamId(framer_->transport_version())) {
+      !QuicUtils::IsCryptoStreamId(framer_->transport_version(),
+                                   frame.stream_id)) {
     return false;
   }
   // The ClientHello is always sent with INITIAL encryption.
diff --git a/quic/core/quic_packet_creator_test.cc b/quic/core/quic_packet_creator_test.cc
index f87715b..655d587 100644
--- a/quic/core/quic_packet_creator_test.cc
+++ b/quic/core/quic_packet_creator_test.cc
@@ -1285,8 +1285,14 @@
   const size_t max_plaintext_size =
       client_framer_.GetMaxPlaintextSize(creator_.max_packet_length());
   EXPECT_FALSE(creator_.HasPendingFrames());
-  EXPECT_FALSE(creator_.HasPendingStreamFramesOfStream(
-      QuicUtils::GetCryptoStreamId(client_framer_.transport_version())));
+  creator_.set_encryption_level(ENCRYPTION_FORWARD_SECURE);
+  QuicStreamId stream_id = QuicUtils::GetFirstBidirectionalStreamId(
+      client_framer_.transport_version(), Perspective::IS_CLIENT);
+  if (!QuicVersionUsesCryptoFrames(client_framer_.transport_version())) {
+    stream_id =
+        QuicUtils::GetCryptoStreamId(client_framer_.transport_version());
+  }
+  EXPECT_FALSE(creator_.HasPendingStreamFramesOfStream(stream_id));
   EXPECT_EQ(max_plaintext_size -
                 GetPacketHeaderSize(
                     client_framer_.transport_version(),
@@ -1307,20 +1313,17 @@
   EXPECT_TRUE(
       creator_.AddSavedFrame(QuicFrame(&ack_frame), NOT_RETRANSMISSION));
   EXPECT_TRUE(creator_.HasPendingFrames());
-  EXPECT_FALSE(creator_.HasPendingStreamFramesOfStream(
-      QuicUtils::GetCryptoStreamId(client_framer_.transport_version())));
+  EXPECT_FALSE(creator_.HasPendingStreamFramesOfStream(stream_id));
 
   QuicFrame frame;
   MakeIOVector("test", &iov_);
   EXPECT_CALL(debug, OnFrameAddedToPacket(_));
-  ASSERT_TRUE(creator_.ConsumeData(
-      QuicUtils::GetCryptoStreamId(client_framer_.transport_version()), &iov_,
-      1u, iov_.iov_len, 0u, 0u, false, false, NOT_RETRANSMISSION, &frame));
+  ASSERT_TRUE(creator_.ConsumeData(stream_id, &iov_, 1u, iov_.iov_len, 0u, 0u,
+                                   false, false, NOT_RETRANSMISSION, &frame));
   size_t consumed = frame.stream_frame.data_length;
   EXPECT_EQ(4u, consumed);
   EXPECT_TRUE(creator_.HasPendingFrames());
-  EXPECT_TRUE(creator_.HasPendingStreamFramesOfStream(
-      QuicUtils::GetCryptoStreamId(client_framer_.transport_version())));
+  EXPECT_TRUE(creator_.HasPendingStreamFramesOfStream(stream_id));
 
   QuicPaddingFrame padding_frame;
   EXPECT_CALL(debug, OnFrameAddedToPacket(_));
@@ -1346,8 +1349,7 @@
   DeleteSerializedPacket();
 
   EXPECT_FALSE(creator_.HasPendingFrames());
-  EXPECT_FALSE(creator_.HasPendingStreamFramesOfStream(
-      QuicUtils::GetCryptoStreamId(client_framer_.transport_version())));
+  EXPECT_FALSE(creator_.HasPendingStreamFramesOfStream(stream_id));
   EXPECT_EQ(max_plaintext_size -
                 GetPacketHeaderSize(
                     client_framer_.transport_version(),
diff --git a/quic/core/quic_packet_generator.cc b/quic/core/quic_packet_generator.cc
index ee15df0..d811f9d 100644
--- a/quic/core/quic_packet_generator.cc
+++ b/quic/core/quic_packet_generator.cc
@@ -138,7 +138,7 @@
   QUIC_BUG_IF(!flusher_attached_) << "Packet flusher is not attached when "
                                      "generator tries to write stream data.";
   bool has_handshake =
-      (id == QuicUtils::GetCryptoStreamId(packet_creator_.transport_version()));
+      QuicUtils::IsCryptoStreamId(packet_creator_.transport_version(), id);
   if (deprecate_ack_bundling_mode_) {
     MaybeBundleAckOpportunistically();
   }
@@ -228,8 +228,7 @@
     QuicStreamOffset offset,
     bool fin,
     size_t total_bytes_consumed) {
-  DCHECK_NE(id,
-            QuicUtils::GetCryptoStreamId(packet_creator_.transport_version()));
+  DCHECK(!QuicUtils::IsCryptoStreamId(packet_creator_.transport_version(), id));
 
   while (total_bytes_consumed < write_length &&
          delegate_->ShouldGeneratePacket(HAS_RETRANSMITTABLE_DATA,
diff --git a/quic/core/quic_packet_generator_test.cc b/quic/core/quic_packet_generator_test.cc
index 705142a..676d6e7 100644
--- a/quic/core/quic_packet_generator_test.cc
+++ b/quic/core/quic_packet_generator_test.cc
@@ -587,16 +587,29 @@
 
   EXPECT_CALL(delegate_, OnSerializedPacket(_))
       .WillOnce(Invoke(this, &QuicPacketGeneratorTest::SavePacket));
-  MakeIOVector("foo bar", &iov_);
-  QuicConsumedData consumed = generator_.ConsumeData(
-      QuicUtils::GetCryptoStreamId(framer_.transport_version()), &iov_, 1u,
-      iov_.iov_len, 0, NO_FIN);
-  EXPECT_EQ(7u, consumed.bytes_consumed);
+  std::string data = "foo bar";
+  MakeIOVector(data, &iov_);
+  size_t consumed_bytes = 0;
+  if (QuicVersionUsesCryptoFrames(framer_.transport_version())) {
+    consumed_bytes = generator_.ConsumeCryptoData(ENCRYPTION_INITIAL, data, 0);
+  } else {
+    consumed_bytes =
+        generator_
+            .ConsumeData(
+                QuicUtils::GetCryptoStreamId(framer_.transport_version()),
+                &iov_, 1u, iov_.iov_len, 0, NO_FIN)
+            .bytes_consumed;
+  }
+  EXPECT_EQ(7u, consumed_bytes);
   EXPECT_FALSE(generator_.HasQueuedFrames());
   EXPECT_FALSE(generator_.HasRetransmittableFrames());
 
   PacketContents contents;
-  contents.num_stream_frames = 1;
+  if (QuicVersionUsesCryptoFrames(framer_.transport_version())) {
+    contents.num_crypto_frames = 1;
+  } else {
+    contents.num_stream_frames = 1;
+  }
   contents.num_padding_frames = 1;
   CheckPacketContains(contents, 0);
 
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index d56689f..96f98ad 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -102,6 +102,10 @@
   connection_->SetDataProducer(this);
   connection_->SetFromConfig(config_);
 
+  if (QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+    return;
+  }
+
   DCHECK_EQ(QuicUtils::GetCryptoStreamId(connection_->transport_version()),
             GetMutableCryptoStream()->id());
   if (!eliminate_static_stream_map_) {
@@ -250,8 +254,8 @@
   // make exceptions for them with respect to processing things like
   // STOP_SENDING.
   if (QuicContainsKey(static_stream_map_, stream_id) ||
-      stream_id ==
-          QuicUtils::GetCryptoStreamId(connection_->transport_version())) {
+      QuicUtils::IsCryptoStreamId(connection_->transport_version(),
+                                  stream_id)) {
     QUIC_DVLOG(1) << ENDPOINT
                   << "Received STOP_SENDING for a static stream, id: "
                   << stream_id << " Closing connection";
@@ -658,6 +662,11 @@
 }
 
 bool QuicSession::HasPendingHandshake() const {
+  if (QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+    // Writing CRYPTO frames is not subject to flow control, so there can't be
+    // pending data to write, only pending retransmissions.
+    return GetCryptoStream()->HasPendingCryptoRetransmission();
+  }
   return QuicContainsKey(
              streams_with_pending_retransmission_,
              QuicUtils::GetCryptoStreamId(connection_->transport_version())) ||
@@ -687,7 +696,7 @@
   // it might end up resulting in unencrypted stream data being sent.
   // While this is impossible to avoid given sufficient corruption, this
   // seems like a reasonable mitigation.
-  if (id == QuicUtils::GetCryptoStreamId(connection_->transport_version()) &&
+  if (QuicUtils::IsCryptoStreamId(connection_->transport_version(), id) &&
       stream != GetMutableCryptoStream()) {
     QUIC_BUG << "Stream id mismatch";
     connection_->CloseConnection(
@@ -697,7 +706,7 @@
     return QuicConsumedData(0, false);
   }
   if (!IsEncryptionEstablished() &&
-      id != QuicUtils::GetCryptoStreamId(connection_->transport_version())) {
+      !QuicUtils::IsCryptoStreamId(connection_->transport_version(), id)) {
     // Do not let streams write without encryption. The calling stream will end
     // up write blocked until OnCanWrite is next called.
     return QuicConsumedData(0, false);
@@ -1265,8 +1274,8 @@
 QuicSession::StreamHandler QuicSession::GetOrCreateStreamImpl(
     QuicStreamId stream_id) {
   if (eliminate_static_stream_map_ &&
-      stream_id ==
-          QuicUtils::GetCryptoStreamId(connection_->transport_version())) {
+      QuicUtils::IsCryptoStreamId(connection_->transport_version(),
+                                  stream_id)) {
     QUIC_RELOADABLE_FLAG_COUNT_N(quic_eliminate_static_stream_map_2, 13, 17);
     return StreamHandler(GetMutableCryptoStream());
   }
@@ -1407,7 +1416,7 @@
   if (QuicContainsKey(static_stream_map_, id) ||
       QuicContainsKey(dynamic_stream_map_, id) ||
       QuicContainsKey(pending_stream_map_, id) ||
-      id == QuicUtils::GetCryptoStreamId(connection_->transport_version())) {
+      QuicUtils::IsCryptoStreamId(connection_->transport_version(), id)) {
     // Stream is active
     return true;
   }
@@ -1562,7 +1571,7 @@
   }
 
   if (eliminate_static_stream_map_ &&
-      id == QuicUtils::GetCryptoStreamId(connection_->transport_version())) {
+      QuicUtils::IsCryptoStreamId(connection_->transport_version(), id)) {
     QUIC_RELOADABLE_FLAG_COUNT_N(quic_eliminate_static_stream_map_2, 15, 17);
     return const_cast<QuicCryptoStream*>(GetCryptoStream());
   }
@@ -1803,7 +1812,8 @@
   if (connection_->session_decides_what_to_write()) {
     QuicCryptoStream* crypto_stream = GetMutableCryptoStream();
     crypto_stream->NeuterUnencryptedStreamData();
-    if (!crypto_stream->HasPendingRetransmission()) {
+    if (!crypto_stream->HasPendingRetransmission() &&
+        !QuicVersionUsesCryptoFrames(connection_->transport_version())) {
       streams_with_pending_retransmission_.erase(
           QuicUtils::GetCryptoStreamId(connection_->transport_version()));
     }
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 95bb6d4..deb6643 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -94,7 +94,7 @@
   }
 
   MOCK_METHOD0(OnCanWrite, void());
-  bool HasPendingCryptoRetransmission() override { return false; }
+  bool HasPendingCryptoRetransmission() const override { return false; }
 
   MOCK_CONST_METHOD0(HasPendingRetransmission, bool());
 
@@ -109,7 +109,13 @@
 class TestStream : public QuicStream {
  public:
   TestStream(QuicStreamId id, QuicSession* session, StreamType type)
-      : QuicStream(id, session, /*is_static=*/false, type) {}
+      : TestStream(id, session, /*is_static=*/false, type) {}
+
+  TestStream(QuicStreamId id,
+             QuicSession* session,
+             bool is_static,
+             StreamType type)
+      : QuicStream(id, session, is_static, type) {}
 
   TestStream(PendingStream pending, StreamType type)
       : QuicStream(std::move(pending), type, /*is_static=*/false) {}
@@ -260,8 +266,8 @@
 
   QuicConsumedData SendStreamData(QuicStream* stream) {
     struct iovec iov;
-    if (stream->id() !=
-            QuicUtils::GetCryptoStreamId(connection()->transport_version()) &&
+    if (!QuicUtils::IsCryptoStreamId(connection()->transport_version(),
+                                     stream->id()) &&
         this->connection()->encryption_level() != ENCRYPTION_FORWARD_SECURE) {
       this->connection()->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
     }
@@ -417,6 +423,10 @@
     if (perspective == Perspective::IS_SERVER) {
       id |= 0x1;
     }
+    if (QuicVersionUsesCryptoFrames(connection_->transport_version()) &&
+        bidirectional && perspective == Perspective::IS_CLIENT) {
+      id += 4;
+    }
     return id;
   }
 
@@ -780,7 +790,7 @@
     EXPECT_CALL(
         *connection_,
         CloseConnection(QUIC_INVALID_STREAM_ID,
-                        "Stream id 800 would exceed stream count limit 51",
+                        "Stream id 800 would exceed stream count limit 50",
                         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET
 
                         ))
@@ -1255,10 +1265,19 @@
 }
 
 TEST_P(QuicSessionTestServer, OnStreamFrameFinStaticStreamId) {
+  QuicStreamId headers_stream_id =
+      QuicUtils::GetHeadersStreamId(connection_->transport_version());
+  std::unique_ptr<TestStream> fake_headers_stream = QuicMakeUnique<TestStream>(
+      headers_stream_id, &session_, /*is_static*/ true, BIDIRECTIONAL);
+  if (GetQuicReloadableFlag(quic_eliminate_static_stream_map_2)) {
+    QuicSessionPeer::RegisterStaticStreamNew(&session_,
+                                             std::move(fake_headers_stream));
+  } else {
+    QuicSessionPeer::RegisterStaticStream(&session_, headers_stream_id,
+                                          fake_headers_stream.get());
+  }
   // Send two bytes of payload.
-  QuicStreamFrame data1(
-      QuicUtils::GetCryptoStreamId(connection_->transport_version()), true, 0,
-      QuicStringPiece("HT"));
+  QuicStreamFrame data1(headers_stream_id, true, 0, QuicStringPiece("HT"));
   EXPECT_CALL(*connection_,
               CloseConnection(
                   QUIC_INVALID_STREAM_ID, "Attempt to close a static stream",
@@ -1267,11 +1286,20 @@
 }
 
 TEST_P(QuicSessionTestServer, OnRstStreamStaticStreamId) {
+  QuicStreamId headers_stream_id =
+      QuicUtils::GetHeadersStreamId(connection_->transport_version());
+  std::unique_ptr<TestStream> fake_headers_stream = QuicMakeUnique<TestStream>(
+      headers_stream_id, &session_, /*is_static*/ true, BIDIRECTIONAL);
+  if (GetQuicReloadableFlag(quic_eliminate_static_stream_map_2)) {
+    QuicSessionPeer::RegisterStaticStreamNew(&session_,
+                                             std::move(fake_headers_stream));
+  } else {
+    QuicSessionPeer::RegisterStaticStream(&session_, headers_stream_id,
+                                          fake_headers_stream.get());
+  }
   // Send two bytes of payload.
-  QuicRstStreamFrame rst1(
-      kInvalidControlFrameId,
-      QuicUtils::GetCryptoStreamId(connection_->transport_version()),
-      QUIC_ERROR_PROCESSING_STREAM, 0);
+  QuicRstStreamFrame rst1(kInvalidControlFrameId, headers_stream_id,
+                          QUIC_ERROR_PROCESSING_STREAM, 0);
   EXPECT_CALL(*connection_,
               CloseConnection(
                   QUIC_INVALID_STREAM_ID, "Attempt to reset a static stream",
@@ -1620,7 +1648,7 @@
     EXPECT_CALL(
         *connection_,
         CloseConnection(QUIC_INVALID_STREAM_ID,
-                        "Stream id 24 would exceed stream count limit 6", _));
+                        "Stream id 24 would exceed stream count limit 5", _));
   } else {
     EXPECT_CALL(*connection_, SendControlFrame(_)).Times(1);
     EXPECT_CALL(*connection_,
@@ -1916,9 +1944,12 @@
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   TestStream* stream4 = session_.CreateOutgoingBidirectionalStream();
 
-  QuicStreamFrame frame1(
-      QuicUtils::GetCryptoStreamId(connection_->transport_version()), false, 0,
-      1300);
+  QuicStreamFrame frame1;
+  if (!QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+    frame1 = QuicStreamFrame(
+        QuicUtils::GetCryptoStreamId(connection_->transport_version()), false,
+        0, 1300);
+  }
   QuicStreamFrame frame2(stream2->id(), false, 0, 9);
   QuicStreamFrame frame3(stream4->id(), false, 0, 9);
 
@@ -2354,7 +2385,7 @@
   EXPECT_CALL(
       *connection_,
       CloseConnection(QUIC_INVALID_STREAM_ID,
-                      "Stream id 404 would exceed stream count limit 101", _));
+                      "Stream id 404 would exceed stream count limit 100", _));
   session_.OnStreamFrame(bidirectional_stream_frame);
 
   QuicStreamId unidirectional_stream_id = StreamCountToId(
@@ -2395,9 +2426,19 @@
     // Applicable only to V99
     return;
   }
+  QuicStreamId stream_id = 0;
+  std::unique_ptr<TestStream> fake_static_stream = QuicMakeUnique<TestStream>(
+      stream_id, &session_, /*is_static*/ true, BIDIRECTIONAL);
+  if (GetQuicReloadableFlag(quic_eliminate_static_stream_map_2)) {
+    QuicSessionPeer::RegisterStaticStreamNew(&session_,
+                                             std::move(fake_static_stream));
+  } else {
+    QuicSessionPeer::RegisterStaticStream(&session_, stream_id,
+                                          fake_static_stream.get());
+  }
   // Check that a stream id in the static stream map is ignored.
   // Note that the notion of a static stream is Google-specific.
-  QuicStopSendingFrame frame(1, 0, 123);
+  QuicStopSendingFrame frame(1, stream_id, 123);
   EXPECT_CALL(*connection_,
               CloseConnection(QUIC_INVALID_STREAM_ID,
                               "Received STOP_SENDING for a static stream", _));
diff --git a/quic/core/quic_stream_id_manager.cc b/quic/core/quic_stream_id_manager.cc
index 94a8487..7fac723 100644
--- a/quic/core/quic_stream_id_manager.cc
+++ b/quic/core/quic_stream_id_manager.cc
@@ -283,24 +283,6 @@
     return false;
   }
 
-  // This is a stream id for a stream that is started by this node
-  if (perspective() == Perspective::IS_CLIENT &&
-      stream_id == QuicUtils::GetCryptoStreamId(transport_version())) {
-    // TODO(fkastenholz): When crypto is moved into the CRYPTO_STREAM
-    // and streamID 0 is no longer special, this needs to be removed.
-    // Stream-id-0 seems not be allocated via get-next-stream-id,
-    // which would increment outgoing_stream_count_, so increment
-    // the count here to account for it.
-    // Do not need to update next_outgoing_stream_id_ because it is
-    // initiated to 4 (that is, it skips the crypto stream ID).
-    if (outgoing_stream_count_ >=
-        QuicUtils::GetMaxStreamCount(unidirectional_, perspective())) {
-      // Already at the implementation limit, return false...
-      return false;
-    }
-    outgoing_stream_count_++;
-  }
-
   // Increase the outgoing_max_streams_ limit to reflect the semantic that
   // outgoing_max_streams_ was inialized to a "maximum request/response" count
   // and only becomes a maximum stream count when we receive the first
diff --git a/quic/core/quic_stream_id_manager_test.cc b/quic/core/quic_stream_id_manager_test.cc
index 164e911..79c4845 100644
--- a/quic/core/quic_stream_id_manager_test.cc
+++ b/quic/core/quic_stream_id_manager_test.cc
@@ -202,10 +202,14 @@
   // If bidi, Crypto stream default created  at start up, it is one
   // more stream to account for since initialization is "number of
   // request/responses" & crypto is added in to that, not streams.
-  EXPECT_EQ(kDefaultMaxStreamsPerConnection + (GetParam() ? 1u : 0u),
+  size_t extra_stream_count = 0;
+  if (GetParam() && !QuicVersionUsesCryptoFrames(transport_version())) {
+    extra_stream_count = 1;
+  }
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection + extra_stream_count,
             stream_id_manager_->outgoing_max_streams());
   // Test is predicated on having 1 static stream going if bidi, 0 if not...)
-  EXPECT_EQ((GetParam() ? 1u : 0u),
+  EXPECT_EQ(extra_stream_count,
             stream_id_manager_->outgoing_static_stream_count());
 
   EXPECT_EQ(kDefaultMaxStreamsPerConnection,
@@ -243,7 +247,11 @@
   // If bidi, Crypto stream default created  at start up, it is one
   // more stream to account for since initialization is "number of
   // request/responses" & crypto is added in to that, not streams.
-  EXPECT_EQ(implementation_max - 1u + (GetParam() ? 1u : 0u),
+  size_t extra_stream_count = 0;
+  if (GetParam() && !QuicVersionUsesCryptoFrames(transport_version())) {
+    extra_stream_count = 1;
+  }
+  EXPECT_EQ(implementation_max - 1u + extra_stream_count,
             stream_id_manager_->outgoing_max_streams());
 
   stream_id_manager_->AdjustMaxOpenOutgoingStreams(implementation_max);
@@ -496,7 +504,11 @@
   // If bidi, Crypto stream default created  at start up, it is one
   // more stream to account for since initialization is "number of
   // request/responses" & crypto is added in to that, not streams.
-  EXPECT_EQ(number_of_streams + (IsBidi() ? 1u : 0u),
+  size_t extra_stream_count = 0;
+  if (IsBidi() && !QuicVersionUsesCryptoFrames(transport_version())) {
+    extra_stream_count = 1;
+  }
+  EXPECT_EQ(number_of_streams + extra_stream_count,
             stream_id_manager_->outgoing_max_streams());
   while (number_of_streams) {
     EXPECT_TRUE(stream_id_manager_->CanOpenNextOutgoingStream());
@@ -515,7 +527,7 @@
   // If bidi, Crypto stream default created  at start up, it is one
   // more stream to account for since initialization is "number of
   // request/responses" & crypto is added in to that, not streams.
-  EXPECT_EQ(kDefaultMaxStreamsPerConnection + (IsBidi() ? 1u : 0u),
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection + extra_stream_count,
             session_->save_frame().max_streams_frame.stream_count);
   // If we try to get the next id (above the limit), it should cause a quic-bug.
   EXPECT_QUIC_BUG(
@@ -783,7 +795,11 @@
   // more stream to account for since initialization is "number of
   // request/responses" & crypto is added in to that, not streams.
   // Since this is the server, the stream is incoming.
-  EXPECT_EQ(kDefaultMaxStreamsPerConnection + (IsBidi() ? 1u : 0u),
+  size_t extra_stream_count = 0;
+  if (IsBidi() && !QuicVersionUsesCryptoFrames(transport_version())) {
+    extra_stream_count = 1;
+  }
+  EXPECT_EQ(kDefaultMaxStreamsPerConnection + extra_stream_count,
             stream_id_manager_->incoming_actual_max_streams());
   EXPECT_EQ(kDefaultMaxStreamsPerConnection,
             stream_id_manager_->outgoing_max_streams());
@@ -814,9 +830,17 @@
       stream_id_manager_->incoming_actual_max_streams() + 20,
       Perspective::IS_CLIENT);  // This node is a server, incoming stream
                                 // ids must be client-originated.
-  std::string error_details =
-      IsBidi() ? "Stream id 480 would exceed stream count limit 101"
-               : "Stream id 478 would exceed stream count limit 100";
+  std::string error_details;
+  if (IsBidi()) {
+    if (QuicVersionUsesCryptoFrames(transport_version())) {
+      error_details = "Stream id 476 would exceed stream count limit 100";
+    } else {
+      error_details = "Stream id 480 would exceed stream count limit 101";
+    }
+  } else {
+    error_details = "Stream id 478 would exceed stream count limit 100";
+  }
+
   EXPECT_CALL(*connection_,
               CloseConnection(QUIC_INVALID_STREAM_ID, error_details, _));
   EXPECT_FALSE(
diff --git a/quic/core/quic_utils.cc b/quic/core/quic_utils.cc
index 85ce4f2..dc40122 100644
--- a/quic/core/quic_utils.cc
+++ b/quic/core/quic_utils.cc
@@ -383,9 +383,18 @@
 
 // static
 QuicStreamId QuicUtils::GetCryptoStreamId(QuicTransportVersion version) {
-  // TODO(nharper): Change this to return GetInvalidStreamId for version 47 or
-  // greater. Currently, too many things break with that change.
-  return version == QUIC_VERSION_99 ? 0 : 1;
+  QUIC_BUG_IF(QuicVersionUsesCryptoFrames(version))
+      << "CRYPTO data aren't in stream frames; they have no stream ID.";
+  return QuicVersionUsesCryptoFrames(version) ? GetInvalidStreamId(version) : 1;
+}
+
+// static
+bool QuicUtils::IsCryptoStreamId(QuicTransportVersion version,
+                                 QuicStreamId stream_id) {
+  if (QuicVersionUsesCryptoFrames(version)) {
+    return false;
+  }
+  return stream_id == GetCryptoStreamId(version);
 }
 
 // static
@@ -453,6 +462,7 @@
     QuicTransportVersion version,
     Perspective perspective) {
   if (perspective == Perspective::IS_CLIENT) {
+    // TODO(nharper): Return 0 instead of 4 when CRYPTO frames are used.
     return version == QUIC_VERSION_99 ? 4 : 3;
   }
   return version == QUIC_VERSION_99 ? 1 : 2;
diff --git a/quic/core/quic_utils.h b/quic/core/quic_utils.h
index befeee7..bac025e 100644
--- a/quic/core/quic_utils.h
+++ b/quic/core/quic_utils.h
@@ -116,6 +116,11 @@
   // Returns crypto stream ID of |version|.
   static QuicStreamId GetCryptoStreamId(QuicTransportVersion version);
 
+  // Returns whether |id| is the stream ID for the crypto stream. If |version|
+  // is a version where crypto data doesn't go over stream frames, this function
+  // will always return false.
+  static bool IsCryptoStreamId(QuicTransportVersion version, QuicStreamId id);
+
   // Returns headers stream ID of |version|.
   static QuicStreamId GetHeadersStreamId(QuicTransportVersion version);
 
diff --git a/quic/test_tools/quic_server_session_base_peer.h b/quic/test_tools/quic_server_session_base_peer.h
index 30c9b22..d2a21a6 100644
--- a/quic/test_tools/quic_server_session_base_peer.h
+++ b/quic/test_tools/quic_server_session_base_peer.h
@@ -21,9 +21,11 @@
   static void SetCryptoStream(QuicServerSessionBase* s,
                               QuicCryptoServerStream* crypto_stream) {
     s->crypto_stream_.reset(crypto_stream);
-    s->RegisterStaticStream(
-        QuicUtils::GetCryptoStreamId(s->connection()->transport_version()),
-        crypto_stream);
+    if (!QuicVersionUsesCryptoFrames(s->connection()->transport_version())) {
+      s->RegisterStaticStream(
+          QuicUtils::GetCryptoStreamId(s->connection()->transport_version()),
+          crypto_stream);
+    }
   }
   static bool IsBandwidthResumptionEnabled(QuicServerSessionBase* s) {
     return s->bandwidth_resumption_enabled_;
diff --git a/quic/test_tools/quic_session_peer.cc b/quic/test_tools/quic_session_peer.cc
index 0683fd7..ed7043d 100644
--- a/quic/test_tools/quic_session_peer.cc
+++ b/quic/test_tools/quic_session_peer.cc
@@ -166,6 +166,20 @@
 }
 
 // static
+void QuicSessionPeer::RegisterStaticStream(QuicSession* session,
+                                           QuicStreamId id,
+                                           QuicStream* stream) {
+  return session->RegisterStaticStream(id, stream);
+}
+
+// static
+void QuicSessionPeer::RegisterStaticStreamNew(
+    QuicSession* session,
+    std::unique_ptr<QuicStream> stream) {
+  return session->RegisterStaticStreamNew(std::move(stream));
+}
+
+// static
 bool QuicSessionPeer::IsStreamClosed(QuicSession* session, QuicStreamId id) {
   DCHECK_NE(0u, id);
   return session->IsClosedStream(id);
diff --git a/quic/test_tools/quic_session_peer.h b/quic/test_tools/quic_session_peer.h
index 994a36c..3828981 100644
--- a/quic/test_tools/quic_session_peer.h
+++ b/quic/test_tools/quic_session_peer.h
@@ -64,6 +64,11 @@
       QuicSession* session);
   static void ActivateStream(QuicSession* session,
                              std::unique_ptr<QuicStream> stream);
+  static void RegisterStaticStream(QuicSession* session,
+                                   QuicStreamId stream_id,
+                                   QuicStream* stream);
+  static void RegisterStaticStreamNew(QuicSession* session,
+                                      std::unique_ptr<QuicStream> stream);
 
   // Discern the state of a stream.  Exactly one of these should be true at a
   // time for any stream id > 0 (other than the special streams 1 and 3).
diff --git a/quic/test_tools/simple_session_notifier.cc b/quic/test_tools/simple_session_notifier.cc
index 9ae4d22..e72e0e1 100644
--- a/quic/test_tools/simple_session_notifier.cc
+++ b/quic/test_tools/simple_session_notifier.cc
@@ -72,7 +72,7 @@
                                                  QuicByteCount data_length,
                                                  bool fin) {
   StreamState& state = stream_map_.find(id)->second;
-  if (id == QuicUtils::GetCryptoStreamId(connection_->transport_version()) &&
+  if (QuicUtils::IsCryptoStreamId(connection_->transport_version(), id) &&
       data_length > 0) {
     crypto_bytes_transferred_[connection_->encryption_level()].Add(
         offset, offset + data_length);
@@ -127,8 +127,11 @@
 }
 
 void SimpleSessionNotifier::NeuterUnencryptedData() {
+  // TODO(nharper): Handle CRYPTO frame case.
+  if (QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+    return;
+  }
   for (const auto& interval : crypto_bytes_transferred_[ENCRYPTION_INITIAL]) {
-    // TODO(nharper): Handle CRYPTO frame case.
     QuicStreamFrame stream_frame(
         QuicUtils::GetCryptoStreamId(connection_->transport_version()), false,
         interval.min(), interval.max() - interval.min());
@@ -146,7 +149,6 @@
     return;
   }
   // Write new data.
-  // TODO(nharper): Write CRYPTO frames.
   for (const auto& pair : stream_map_) {
     const auto& state = pair.second;
     if (!StreamHasBufferedData(pair.first)) {
@@ -320,8 +322,8 @@
     EncryptionLevel retransmission_encryption_level =
         connection_->encryption_level();
     EncryptionLevel current_encryption_level = connection_->encryption_level();
-    if (frame.stream_frame.stream_id ==
-        QuicUtils::GetCryptoStreamId(connection_->transport_version())) {
+    if (QuicUtils::IsCryptoStreamId(connection_->transport_version(),
+                                    frame.stream_frame.stream_id)) {
       for (size_t i = 0; i < NUM_ENCRYPTION_LEVELS; ++i) {
         if (retransmission.Intersects(crypto_bytes_transferred_[i])) {
           retransmission_encryption_level = static_cast<EncryptionLevel>(i);
@@ -339,8 +341,8 @@
       const bool can_bundle_fin =
           retransmit_fin &&
           (retransmission_offset + retransmission_length == state.bytes_sent);
-      if (frame.stream_frame.stream_id ==
-          QuicUtils::GetCryptoStreamId(connection_->transport_version())) {
+      if (QuicUtils::IsCryptoStreamId(connection_->transport_version(),
+                                      frame.stream_frame.stream_id)) {
         // Set appropriate encryption level for crypto stream.
         connection_->SetDefaultEncryptionLevel(retransmission_encryption_level);
       }
@@ -356,8 +358,8 @@
       if (can_bundle_fin) {
         retransmit_fin = !consumed.fin_consumed;
       }
-      if (frame.stream_frame.stream_id ==
-          QuicUtils::GetCryptoStreamId(connection_->transport_version())) {
+      if (QuicUtils::IsCryptoStreamId(connection_->transport_version(),
+                                      frame.stream_frame.stream_id)) {
         // Restore encryption level.
         connection_->SetDefaultEncryptionLevel(current_encryption_level);
       }
@@ -497,7 +499,36 @@
 }
 
 bool SimpleSessionNotifier::RetransmitLostCryptoData() {
-  // TODO(nharper): Handle CRYPTO frame case.
+  if (QuicVersionUsesCryptoFrames(connection_->transport_version())) {
+    for (EncryptionLevel level :
+         {ENCRYPTION_INITIAL, ENCRYPTION_HANDSHAKE, ENCRYPTION_ZERO_RTT,
+          ENCRYPTION_FORWARD_SECURE}) {
+      auto& state = crypto_state_[level];
+      while (!state.pending_retransmissions.Empty()) {
+        connection_->SetTransmissionType(HANDSHAKE_RETRANSMISSION);
+        EncryptionLevel current_encryption_level =
+            connection_->encryption_level();
+        connection_->SetDefaultEncryptionLevel(level);
+        QuicIntervalSet<QuicStreamOffset> retransmission(
+            state.pending_retransmissions.begin()->min(),
+            state.pending_retransmissions.begin()->max());
+        retransmission.Intersection(crypto_bytes_transferred_[level]);
+        QuicStreamOffset retransmission_offset = retransmission.begin()->min();
+        QuicByteCount retransmission_length =
+            retransmission.begin()->max() - retransmission.begin()->min();
+        size_t bytes_consumed = connection_->SendCryptoData(
+            level, retransmission_length, retransmission_offset);
+        // Restore encryption level.
+        connection_->SetDefaultEncryptionLevel(current_encryption_level);
+        state.pending_retransmissions.Difference(
+            retransmission_offset, retransmission_offset + bytes_consumed);
+        if (bytes_consumed < retransmission_length) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
   if (!QuicContainsKey(stream_map_, QuicUtils::GetCryptoStreamId(
                                         connection_->transport_version()))) {
     return true;
diff --git a/quic/test_tools/simple_session_notifier_test.cc b/quic/test_tools/simple_session_notifier_test.cc
index 53712fd..e5298eb 100644
--- a/quic/test_tools/simple_session_notifier_test.cc
+++ b/quic/test_tools/simple_session_notifier_test.cc
@@ -4,10 +4,12 @@
 
 #include "net/third_party/quiche/src/quic/test_tools/simple_session_notifier.h"
 
+#include "net/third_party/quiche/src/quic/core/crypto/null_encrypter.h"
 #include "net/third_party/quiche/src/quic/core/quic_utils.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"
 #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/simple_data_producer.h"
 
 using testing::_;
 using testing::InSequence;
@@ -126,6 +128,9 @@
 }
 
 TEST_F(SimpleSessionNotifierTest, NeuterUnencryptedData) {
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    return;
+  }
   InSequence s;
   // Send crypto data [0, 1024) in ENCRYPTION_INITIAL.
   connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
@@ -159,6 +164,9 @@
 }
 
 TEST_F(SimpleSessionNotifierTest, OnCanWrite) {
+  if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    return;
+  }
   InSequence s;
   // Send crypto data [0, 1024) in ENCRYPTION_INITIAL.
   connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
@@ -221,6 +229,71 @@
   EXPECT_FALSE(notifier_.WillingToWrite());
 }
 
+TEST_F(SimpleSessionNotifierTest, OnCanWriteCryptoFrames) {
+  if (!QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+    return;
+  }
+  SimpleDataProducer producer;
+  connection_.SetDataProducer(&producer);
+  InSequence s;
+  // Send crypto data [0, 1024) in ENCRYPTION_INITIAL.
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+  EXPECT_CALL(connection_, SendCryptoData(ENCRYPTION_INITIAL, 1024, 0))
+      .WillOnce(Invoke(&connection_,
+                       &MockQuicConnection::QuicConnection_SendCryptoData));
+  producer.SaveCryptoData(ENCRYPTION_INITIAL, 0, std::string(1024, 'a'));
+  producer.SaveCryptoData(ENCRYPTION_INITIAL, 500, std::string(524, 'a'));
+  notifier_.WriteCryptoData(ENCRYPTION_INITIAL, 1024, 0);
+  // Send crypto data [1024, 2048) in ENCRYPTION_ZERO_RTT.
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_ZERO_RTT);
+  connection_.SetEncrypter(ENCRYPTION_ZERO_RTT, QuicMakeUnique<NullEncrypter>(
+                                                    Perspective::IS_CLIENT));
+  EXPECT_CALL(connection_, SendCryptoData(ENCRYPTION_ZERO_RTT, 1024, 0))
+      .WillOnce(Invoke(&connection_,
+                       &MockQuicConnection::QuicConnection_SendCryptoData));
+  producer.SaveCryptoData(ENCRYPTION_ZERO_RTT, 0, std::string(1024, 'a'));
+  notifier_.WriteCryptoData(ENCRYPTION_ZERO_RTT, 1024, 0);
+  // Send stream 3 [0, 1024) and connection is blocked.
+  EXPECT_CALL(connection_, SendStreamData(3, 1024, 0, FIN))
+      .WillOnce(Return(QuicConsumedData(512, false)));
+  notifier_.WriteOrBufferData(3, 1024, FIN);
+  // Send stream 5 [0, 1024).
+  EXPECT_CALL(connection_, SendStreamData(5, _, _, _)).Times(0);
+  notifier_.WriteOrBufferData(5, 1024, NO_FIN);
+  // Reset stream 5 with error.
+  EXPECT_CALL(connection_, SendControlFrame(_)).Times(0);
+  notifier_.WriteOrBufferRstStream(5, QUIC_ERROR_PROCESSING_STREAM, 1024);
+
+  // Lost crypto data [500, 1500) and stream 3 [0, 512).
+  QuicCryptoFrame crypto_frame1(ENCRYPTION_INITIAL, 500, 524);
+  QuicCryptoFrame crypto_frame2(ENCRYPTION_ZERO_RTT, 0, 476);
+  QuicStreamFrame stream3_frame(3, false, 0, 512);
+  notifier_.OnFrameLost(QuicFrame(&crypto_frame1));
+  notifier_.OnFrameLost(QuicFrame(&crypto_frame2));
+  notifier_.OnFrameLost(QuicFrame(stream3_frame));
+
+  // Connection becomes writable.
+  // Lost crypto data gets retransmitted as [500, 1024) and [1024, 1500), as
+  // they are in different encryption levels.
+  EXPECT_CALL(connection_, SendCryptoData(ENCRYPTION_INITIAL, 524, 500))
+      .WillOnce(Invoke(&connection_,
+                       &MockQuicConnection::QuicConnection_SendCryptoData));
+  EXPECT_CALL(connection_, SendCryptoData(ENCRYPTION_ZERO_RTT, 476, 0))
+      .WillOnce(Invoke(&connection_,
+                       &MockQuicConnection::QuicConnection_SendCryptoData));
+  // Lost stream 3 data gets retransmitted.
+  EXPECT_CALL(connection_, SendStreamData(3, 512, 0, NO_FIN))
+      .WillOnce(Return(QuicConsumedData(512, false)));
+  // Buffered control frames get sent.
+  EXPECT_CALL(connection_, SendControlFrame(_))
+      .WillOnce(Invoke(this, &SimpleSessionNotifierTest::ControlFrameConsumed));
+  // Buffered stream 3 data [512, 1024) gets sent.
+  EXPECT_CALL(connection_, SendStreamData(3, 512, 512, FIN))
+      .WillOnce(Return(QuicConsumedData(512, true)));
+  notifier_.OnCanWrite();
+  EXPECT_FALSE(notifier_.WillingToWrite());
+}
+
 TEST_F(SimpleSessionNotifierTest, RetransmitFrames) {
   InSequence s;
   // Send stream 3 data [0, 10) and fin.
diff --git a/quic/tools/quic_simple_server_session_test.cc b/quic/tools/quic_simple_server_session_test.cc
index 7429732..aa9104a 100644
--- a/quic/tools/quic_simple_server_session_test.cc
+++ b/quic/tools/quic_simple_server_session_test.cc
@@ -54,9 +54,11 @@
   static void SetCryptoStream(QuicSimpleServerSession* s,
                               QuicCryptoServerStream* crypto_stream) {
     s->crypto_stream_.reset(crypto_stream);
-    s->RegisterStaticStream(
-        QuicUtils::GetCryptoStreamId(s->connection()->transport_version()),
-        crypto_stream);
+    if (!QuicVersionUsesCryptoFrames(s->connection()->transport_version())) {
+      s->RegisterStaticStream(
+          QuicUtils::GetCryptoStreamId(s->connection()->transport_version()),
+          crypto_stream);
+    }
   }
 
   static QuicSpdyStream* CreateIncomingStream(QuicSimpleServerSession* s,