Do not queue coalesced undecryptable packets twice

This CL adds QuicFramerVisitorInterface::OnUndecryptablePacket and uses it to send undecryptable packets from QuicFramer to QuicConnection, instead of the previous mechanism which relied on QuicFramer::ProcessPacket returning QUIC_DECRYPTION_FAILURE. The new mechanism has the following advantages:
1) It only sends the current packet, without any subsequent coalesced packets
2) It knows if the decryption failed due to a missing key, which allows us to avoid buffering packets that we know we will never be able to decrypt

This mechanism is enabled for versions that KnowsWhichDecrypterToUse() (which are v47+ || TLS, none of which are currently enabled) and when the new flag quic_framer_uses_undecryptable_upcall is true - the intent being to enable this for all versions once the flag protection process is complete.

This CL also adds QuicDataReader::FullPayload which is required to extract only this packet without further coalesced packets, and associated test.

gfe-relnote: do not queue coalesced undecryptable packets twice, protected by disabled flag gfe2_restart_flag_quic_framer_uses_undecryptable_upcall
PiperOrigin-RevId: 263658152
Change-Id: I66aca2138e353306a5cf4fa9ec259680f4115890
diff --git a/quic/core/chlo_extractor.cc b/quic/core/chlo_extractor.cc
index 0e8e6f5..38e97bd 100644
--- a/quic/core/chlo_extractor.cc
+++ b/quic/core/chlo_extractor.cc
@@ -44,6 +44,9 @@
   void OnDecryptedPacket(EncryptionLevel /*level*/) override {}
   bool OnPacketHeader(const QuicPacketHeader& header) override;
   void OnCoalescedPacket(const QuicEncryptedPacket& packet) override;
+  void OnUndecryptablePacket(const QuicEncryptedPacket& packet,
+                             EncryptionLevel decryption_level,
+                             bool has_decryption_key) override;
   bool OnStreamFrame(const QuicStreamFrame& frame) override;
   bool OnCryptoFrame(const QuicCryptoFrame& frame) override;
   bool OnAckFrameStart(QuicPacketNumber largest_acked,
@@ -142,8 +145,15 @@
 bool ChloFramerVisitor::OnPacketHeader(const QuicPacketHeader& /*header*/) {
   return true;
 }
+
 void ChloFramerVisitor::OnCoalescedPacket(
     const QuicEncryptedPacket& /*packet*/) {}
+
+void ChloFramerVisitor::OnUndecryptablePacket(
+    const QuicEncryptedPacket& /*packet*/,
+    EncryptionLevel /*decryption_level*/,
+    bool /*has_decryption_key*/) {}
+
 bool ChloFramerVisitor::OnStreamFrame(const QuicStreamFrame& frame) {
   if (QuicVersionUsesCryptoFrames(framer_->transport_version())) {
     // CHLO will be sent in CRYPTO frames in v47 and above.
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index cd065af..864f652 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -1623,6 +1623,45 @@
   QueueCoalescedPacket(packet);
 }
 
+void QuicConnection::OnUndecryptablePacket(const QuicEncryptedPacket& packet,
+                                           EncryptionLevel decryption_level,
+                                           bool has_decryption_key) {
+  QUIC_DVLOG(1) << ENDPOINT << "Received undecryptable packet of length "
+                << packet.length() << " with"
+                << (has_decryption_key ? "" : "out") << " key at level "
+                << QuicUtils::EncryptionLevelToString(decryption_level)
+                << " while connection is at encryption level "
+                << QuicUtils::EncryptionLevelToString(encryption_level_);
+  DCHECK(GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall));
+  QUIC_RESTART_FLAG_COUNT_N(quic_framer_uses_undecryptable_upcall, 1, 7);
+  DCHECK(EncryptionLevelIsValid(decryption_level));
+  ++stats_.undecryptable_packets_received;
+
+  bool should_enqueue = true;
+  if (encryption_level_ == ENCRYPTION_FORWARD_SECURE) {
+    // We do not expect to install any further keys.
+    should_enqueue = false;
+  } else if (undecryptable_packets_.size() >= max_undecryptable_packets_) {
+    // We do not queue more than max_undecryptable_packets_ packets.
+    should_enqueue = false;
+  } else if (has_decryption_key) {
+    // We already have the key for this decryption level, therefore no
+    // future keys will allow it be decrypted.
+    should_enqueue = false;
+  } else if (version().KnowsWhichDecrypterToUse() &&
+             decryption_level <= encryption_level_) {
+    // On versions that know which decrypter to use, we install keys in order
+    // so we will not get newer keys for lower encryption levels.
+    should_enqueue = false;
+  }
+
+  if (should_enqueue) {
+    QueueUndecryptablePacket(packet);
+  } else if (debug_visitor_ != nullptr) {
+    debug_visitor_->OnUndecryptablePacket();
+  }
+}
+
 void QuicConnection::ProcessUdpPacket(const QuicSocketAddress& self_address,
                                       const QuicSocketAddress& peer_address,
                                       const QuicReceivedPacket& packet) {
@@ -1682,7 +1721,8 @@
   if (!framer_.ProcessPacket(packet)) {
     // If we are unable to decrypt this packet, it might be
     // because the CHLO or SHLO packet was lost.
-    if (framer_.error() == QUIC_DECRYPTION_FAILURE) {
+    if (framer_.error() == QUIC_DECRYPTION_FAILURE &&
+        !GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall)) {
       ++stats_.undecryptable_packets_received;
       if (encryption_level_ != ENCRYPTION_FORWARD_SECURE &&
           undecryptable_packets_.size() < max_undecryptable_packets_) {
@@ -1690,6 +1730,8 @@
       } else if (debug_visitor_ != nullptr) {
         debug_visitor_->OnUndecryptablePacket();
       }
+    } else if (GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall)) {
+      QUIC_RESTART_FLAG_COUNT_N(quic_framer_uses_undecryptable_upcall, 2, 7);
     }
     QUIC_DVLOG(1) << ENDPOINT
                   << "Unable to process packet.  Last packet processed: "
@@ -2536,6 +2578,9 @@
 }
 
 void QuicConnection::SetDefaultEncryptionLevel(EncryptionLevel level) {
+  QUIC_DVLOG(1) << ENDPOINT << "Setting default encryption level from "
+                << QuicUtils::EncryptionLevelToString(encryption_level_)
+                << " to " << QuicUtils::EncryptionLevelToString(level);
   if (level != encryption_level_ && packet_generator_.HasPendingFrames()) {
     // Flush all queued frames when encryption level changes.
     ScopedPacketFlusher flusher(this);
@@ -2591,6 +2636,16 @@
 
 void QuicConnection::QueueUndecryptablePacket(
     const QuicEncryptedPacket& packet) {
+  if (GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall)) {
+    QUIC_RESTART_FLAG_COUNT_N(quic_framer_uses_undecryptable_upcall, 3, 7);
+    for (const auto& saved_packet : undecryptable_packets_) {
+      if (packet.data() == saved_packet->data() &&
+          packet.length() == saved_packet->length()) {
+        QUIC_DVLOG(1) << ENDPOINT << "Not queueing known undecryptable packet";
+        return;
+      }
+    }
+  }
   QUIC_DVLOG(1) << ENDPOINT << "Queueing undecryptable packet.";
   undecryptable_packets_.push_back(packet.Clone());
 }
@@ -2656,7 +2711,8 @@
     } else {
       // If we are unable to decrypt this packet, it might be
       // because the CHLO or SHLO packet was lost.
-      if (framer_.error() == QUIC_DECRYPTION_FAILURE) {
+      if (framer_.error() == QUIC_DECRYPTION_FAILURE &&
+          !GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall)) {
         ++stats_.undecryptable_packets_received;
         if (encryption_level_ != ENCRYPTION_FORWARD_SECURE &&
             undecryptable_packets_.size() < max_undecryptable_packets_) {
@@ -2664,6 +2720,8 @@
         } else if (debug_visitor_ != nullptr) {
           debug_visitor_->OnUndecryptablePacket();
         }
+      } else if (GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall)) {
+        QUIC_RESTART_FLAG_COUNT_N(quic_framer_uses_undecryptable_upcall, 4, 7);
       }
     }
   }
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 7637584..87d0d81 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -476,6 +476,9 @@
   void OnDecryptedPacket(EncryptionLevel level) override;
   bool OnPacketHeader(const QuicPacketHeader& header) override;
   void OnCoalescedPacket(const QuicEncryptedPacket& packet) override;
+  void OnUndecryptablePacket(const QuicEncryptedPacket& packet,
+                             EncryptionLevel decryption_level,
+                             bool has_decryption_key) override;
   bool OnStreamFrame(const QuicStreamFrame& frame) override;
   bool OnCryptoFrame(const QuicCryptoFrame& frame) override;
   bool OnAckFrameStart(QuicPacketNumber largest_acked,
diff --git a/quic/core/quic_data_reader.cc b/quic/core/quic_data_reader.cc
index d4f28a0..87e9645 100644
--- a/quic/core/quic_data_reader.cc
+++ b/quic/core/quic_data_reader.cc
@@ -192,6 +192,10 @@
   return QuicStringPiece(data_ + pos_, len_ - pos_);
 }
 
+QuicStringPiece QuicDataReader::FullPayload() const {
+  return QuicStringPiece(data_, len_);
+}
+
 bool QuicDataReader::ReadBytes(void* result, size_t size) {
   // Make sure that we have enough data to read.
   if (!CanRead(size)) {
diff --git a/quic/core/quic_data_reader.h b/quic/core/quic_data_reader.h
index 72e2d13..74ed226 100644
--- a/quic/core/quic_data_reader.h
+++ b/quic/core/quic_data_reader.h
@@ -110,6 +110,14 @@
   // DOES NOT forward the internal iterator.
   QuicStringPiece PeekRemainingPayload() const;
 
+  // Returns the entire payload as a QuicStringPiece.
+  //
+  // NOTE: Does not copy but rather references strings in the underlying buffer.
+  // This should be kept in mind when handling memory management!
+  //
+  // DOES NOT forward the internal iterator.
+  QuicStringPiece FullPayload() const;
+
   // Reads a given number of bytes into the given buffer. The buffer
   // must be of adequate size.
   // Forwards the internal iterator on success.
diff --git a/quic/core/quic_data_writer_test.cc b/quic/core/quic_data_writer_test.cc
index 301bfa6..64ad5dd 100644
--- a/quic/core/quic_data_writer_test.cc
+++ b/quic/core/quic_data_writer_test.cc
@@ -1232,6 +1232,37 @@
   }
 }
 
+TEST_P(QuicDataWriterTest, PayloadReads) {
+  char buffer[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+  char expected_first_read[4] = {1, 2, 3, 4};
+  char expected_remaining[12] = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+  QuicDataReader reader(buffer, sizeof(buffer));
+  char first_read_buffer[4] = {};
+  EXPECT_TRUE(reader.ReadBytes(first_read_buffer, sizeof(first_read_buffer)));
+  test::CompareCharArraysWithHexError(
+      "first read", first_read_buffer, sizeof(first_read_buffer),
+      expected_first_read, sizeof(expected_first_read));
+  QuicStringPiece peeked_remaining_payload = reader.PeekRemainingPayload();
+  test::CompareCharArraysWithHexError(
+      "peeked_remaining_payload", peeked_remaining_payload.data(),
+      peeked_remaining_payload.length(), expected_remaining,
+      sizeof(expected_remaining));
+  QuicStringPiece full_payload = reader.FullPayload();
+  test::CompareCharArraysWithHexError("full_payload", full_payload.data(),
+                                      full_payload.length(), buffer,
+                                      sizeof(buffer));
+  QuicStringPiece read_remaining_payload = reader.ReadRemainingPayload();
+  test::CompareCharArraysWithHexError(
+      "read_remaining_payload", read_remaining_payload.data(),
+      read_remaining_payload.length(), expected_remaining,
+      sizeof(expected_remaining));
+  EXPECT_TRUE(reader.IsDoneReading());
+  QuicStringPiece full_payload2 = reader.FullPayload();
+  test::CompareCharArraysWithHexError("full_payload2", full_payload2.data(),
+                                      full_payload2.length(), buffer,
+                                      sizeof(buffer));
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index 93b8575..08593da 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -1843,6 +1843,16 @@
         return true;
       }
       if (hp_removal_failed) {
+        if (GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall)) {
+          QUIC_RESTART_FLAG_COUNT_N(quic_framer_uses_undecryptable_upcall, 5,
+                                    7);
+          const EncryptionLevel decryption_level = GetEncryptionLevel(*header);
+          const bool has_decryption_key =
+              decrypter_[decryption_level] != nullptr;
+          visitor_->OnUndecryptablePacket(
+              QuicEncryptedPacket(encrypted_reader->FullPayload()),
+              decryption_level, has_decryption_key);
+        }
         set_detailed_error("Unable to decrypt header protection.");
         return RaiseError(QUIC_DECRYPTION_FAILURE);
       }
@@ -1901,6 +1911,15 @@
       visitor_->OnAuthenticatedIetfStatelessResetPacket(packet);
       return true;
     }
+    if (GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall)) {
+      QUIC_RESTART_FLAG_COUNT_N(quic_framer_uses_undecryptable_upcall, 6, 7);
+      const EncryptionLevel decryption_level = GetEncryptionLevel(*header);
+      const bool has_decryption_key = version_.KnowsWhichDecrypterToUse() &&
+                                      decrypter_[decryption_level] != nullptr;
+      visitor_->OnUndecryptablePacket(
+          QuicEncryptedPacket(encrypted_reader->FullPayload()),
+          decryption_level, has_decryption_key);
+    }
     set_detailed_error("Unable to decrypt payload.");
     RecordDroppedPacketReason(DroppedPacketReason::DECRYPTION_FAILURE);
     return RaiseError(QUIC_DECRYPTION_FAILURE);
@@ -1979,6 +1998,16 @@
   EncryptionLevel decrypted_level;
   if (!DecryptPayload(encrypted, associated_data, *header, decrypted_buffer,
                       buffer_length, &decrypted_length, &decrypted_level)) {
+    if (GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall)) {
+      QUIC_RESTART_FLAG_COUNT_N(quic_framer_uses_undecryptable_upcall, 7, 7);
+      const EncryptionLevel decryption_level = decrypter_level_;
+      // This version uses trial decryption so we always report to our visitor
+      // that we are not certain we have the correct decryption key.
+      const bool has_decryption_key = false;
+      visitor_->OnUndecryptablePacket(
+          QuicEncryptedPacket(encrypted_reader->FullPayload()),
+          decryption_level, has_decryption_key);
+    }
     RecordDroppedPacketReason(DroppedPacketReason::DECRYPTION_FAILURE);
     set_detailed_error("Unable to decrypt payload.");
     return RaiseError(QUIC_DECRYPTION_FAILURE);
@@ -4173,6 +4202,9 @@
   DCHECK_EQ(alternative_decrypter_level_, NUM_ENCRYPTION_LEVELS);
   DCHECK_GE(level, decrypter_level_);
   DCHECK(!version_.KnowsWhichDecrypterToUse());
+  QUIC_DVLOG(1) << ENDPOINT << "Setting decrypter from level "
+                << QuicUtils::EncryptionLevelToString(decrypter_level_)
+                << " to " << QuicUtils::EncryptionLevelToString(level);
   decrypter_[decrypter_level_] = nullptr;
   decrypter_[level] = std::move(decrypter);
   decrypter_level_ = level;
@@ -4184,6 +4216,10 @@
     bool latch_once_used) {
   DCHECK_NE(level, decrypter_level_);
   DCHECK(!version_.KnowsWhichDecrypterToUse());
+  QUIC_DVLOG(1) << ENDPOINT << "Setting alternative decrypter from level "
+                << QuicUtils::EncryptionLevelToString(
+                       alternative_decrypter_level_)
+                << " to " << QuicUtils::EncryptionLevelToString(level);
   if (alternative_decrypter_level_ != NUM_ENCRYPTION_LEVELS) {
     decrypter_[alternative_decrypter_level_] = nullptr;
   }
@@ -4195,11 +4231,15 @@
 void QuicFramer::InstallDecrypter(EncryptionLevel level,
                                   std::unique_ptr<QuicDecrypter> decrypter) {
   DCHECK(version_.KnowsWhichDecrypterToUse());
+  QUIC_DVLOG(1) << ENDPOINT << "Installing decrypter at level "
+                << QuicUtils::EncryptionLevelToString(level);
   decrypter_[level] = std::move(decrypter);
 }
 
 void QuicFramer::RemoveDecrypter(EncryptionLevel level) {
   DCHECK(version_.KnowsWhichDecrypterToUse());
+  QUIC_DVLOG(1) << ENDPOINT << "Removing decrypter at level "
+                << QuicUtils::EncryptionLevelToString(level);
   decrypter_[level] = nullptr;
 }
 
@@ -4223,6 +4263,8 @@
                               std::unique_ptr<QuicEncrypter> encrypter) {
   DCHECK_GE(level, 0);
   DCHECK_LT(level, NUM_ENCRYPTION_LEVELS);
+  QUIC_DVLOG(1) << ENDPOINT << "Setting encrypter at level "
+                << QuicUtils::EncryptionLevelToString(level);
   encrypter_[level] = std::move(encrypter);
 }
 
@@ -4359,8 +4401,9 @@
   QuicDecrypter* decrypter = decrypter_[expected_decryption_level].get();
   if (decrypter == nullptr) {
     QUIC_DVLOG(1)
+        << ENDPOINT
         << "No decrypter available for removing header protection at level "
-        << expected_decryption_level;
+        << QuicUtils::EncryptionLevelToString(expected_decryption_level);
     return false;
   }
 
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h
index be5f128..c749f04 100644
--- a/quic/core/quic_framer.h
+++ b/quic/core/quic_framer.h
@@ -15,6 +15,7 @@
 #include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
 #include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
 #include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
 
@@ -127,6 +128,13 @@
   // own its internal buffer, the visitor should make a copy of it.
   virtual void OnCoalescedPacket(const QuicEncryptedPacket& packet) = 0;
 
+  // Called when the packet being processed failed to decrypt.
+  // |has_decryption_key| indicates whether the framer knew which decryption
+  // key to use for this packet and already had a suitable key.
+  virtual void OnUndecryptablePacket(const QuicEncryptedPacket& packet,
+                                     EncryptionLevel decryption_level,
+                                     bool has_decryption_key) = 0;
+
   // Called when a StreamFrame has been parsed.
   virtual bool OnStreamFrame(const QuicStreamFrame& frame) = 0;
 
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index 31b5ca5..4eb20df 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -240,12 +240,15 @@
   }
 
   void OnCoalescedPacket(const QuicEncryptedPacket& packet) override {
-    size_t coalesced_data_length = packet.length();
-    char* coalesced_data = new char[coalesced_data_length];
-    memcpy(coalesced_data, packet.data(), coalesced_data_length);
-    coalesced_packets_.push_back(QuicMakeUnique<QuicEncryptedPacket>(
-        coalesced_data, coalesced_data_length,
-        /*owns_buffer=*/true));
+    coalesced_packets_.push_back(packet.Clone());
+  }
+
+  void OnUndecryptablePacket(const QuicEncryptedPacket& packet,
+                             EncryptionLevel decryption_level,
+                             bool has_decryption_key) override {
+    undecryptable_packets_.push_back(packet.Clone());
+    undecryptable_decryption_levels_.push_back(decryption_level);
+    undecryptable_has_decryption_keys_.push_back(has_decryption_key);
   }
 
   bool OnStreamFrame(const QuicStreamFrame& frame) override {
@@ -421,6 +424,9 @@
   std::vector<std::unique_ptr<QuicPingFrame>> ping_frames_;
   std::vector<std::unique_ptr<QuicMessageFrame>> message_frames_;
   std::vector<std::unique_ptr<QuicEncryptedPacket>> coalesced_packets_;
+  std::vector<std::unique_ptr<QuicEncryptedPacket>> undecryptable_packets_;
+  std::vector<EncryptionLevel> undecryptable_decryption_levels_;
+  std::vector<bool> undecryptable_has_decryption_keys_;
   QuicRstStreamFrame rst_stream_frame_;
   QuicConnectionCloseFrame connection_close_frame_;
   QuicStopSendingFrame stop_sending_frame_;
@@ -13773,6 +13779,344 @@
   CheckStreamFrameData("HELLO_WORLD?", visitor_.stream_frames_[1].get());
 }
 
+TEST_P(QuicFramerTest, UndecryptablePacketWithoutDecrypter) {
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+
+  if (!framer_.version().KnowsWhichDecrypterToUse()) {
+    // We create a bad client decrypter by using initial encryption with a
+    // bogus connection ID; it should fail to decrypt everything.
+    QuicConnectionId bogus_connection_id = TestConnectionId(0xbad);
+    CrypterPair bogus_crypters;
+    CryptoUtils::CreateTlsInitialCrypters(Perspective::IS_CLIENT,
+                                          framer_.transport_version(),
+                                          bogus_connection_id, &bogus_crypters);
+    // This removes all other decrypters.
+    framer_.SetDecrypter(ENCRYPTION_FORWARD_SECURE,
+                         std::move(bogus_crypters.decrypter));
+  }
+
+  const unsigned char type_byte =
+      framer_.transport_version() == QUIC_VERSION_44 ? 0xFC : 0xE3;
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (version included, 8-byte connection ID,
+    // 4-byte packet number)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x00,
+    // padding frames
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  unsigned char packet44[] = {
+    // public flags (long header with packet type HANDSHAKE and
+    // 4-byte packet number)
+    type_byte,
+    // version
+    QUIC_VERSION_BYTES,
+    // connection ID lengths
+    0x05,
+    // source connection ID
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // long header packet length
+    0x05,
+    // packet number
+    0x12, 0x34, 0x56, 0x00,
+    // padding frames
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  unsigned char packet99[] = {
+    // public flags (long header with packet type HANDSHAKE and
+    // 4-byte packet number)
+    0xE3,
+    // version
+    QUIC_VERSION_BYTES,
+    // destination connection ID length
+    0x00,
+    // source connection ID length
+    0x08,
+    // source connection ID
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // long header packet length
+    0x24,
+    // packet number
+    0x12, 0x34, 0x56, 0x00,
+    // padding frames
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  // clang-format on
+  unsigned char* p = packet;
+  size_t p_length = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_length = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() >= QUIC_VERSION_44) {
+    p = packet44;
+    p_length = QUIC_ARRAYSIZE(packet44);
+  }
+  // First attempt decryption without the handshake crypter.
+  EXPECT_FALSE(
+      framer_.ProcessPacket(QuicEncryptedPacket(AsChars(p), p_length, false)));
+  EXPECT_EQ(QUIC_DECRYPTION_FAILURE, framer_.error());
+  if (GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall)) {
+    ASSERT_EQ(1u, visitor_.undecryptable_packets_.size());
+    ASSERT_EQ(1u, visitor_.undecryptable_decryption_levels_.size());
+    ASSERT_EQ(1u, visitor_.undecryptable_has_decryption_keys_.size());
+    CompareCharArraysWithHexError(
+        "undecryptable packet", visitor_.undecryptable_packets_[0]->data(),
+        visitor_.undecryptable_packets_[0]->length(), AsChars(p), p_length);
+    if (framer_.version().KnowsWhichDecrypterToUse()) {
+      EXPECT_EQ(ENCRYPTION_HANDSHAKE,
+                visitor_.undecryptable_decryption_levels_[0]);
+    }
+    EXPECT_FALSE(visitor_.undecryptable_has_decryption_keys_[0]);
+  } else {
+    EXPECT_EQ(0u, visitor_.undecryptable_packets_.size());
+    EXPECT_EQ(0u, visitor_.undecryptable_decryption_levels_.size());
+    EXPECT_EQ(0u, visitor_.undecryptable_has_decryption_keys_.size());
+  }
+}
+
+TEST_P(QuicFramerTest, UndecryptablePacketWithDecrypter) {
+  QuicFramerPeer::SetPerspective(&framer_, Perspective::IS_CLIENT);
+
+  // We create a bad client decrypter by using initial encryption with a
+  // bogus connection ID; it should fail to decrypt everything.
+  QuicConnectionId bogus_connection_id = TestConnectionId(0xbad);
+  CrypterPair bad_handshake_crypters;
+  CryptoUtils::CreateTlsInitialCrypters(
+      Perspective::IS_CLIENT, framer_.transport_version(), bogus_connection_id,
+      &bad_handshake_crypters);
+  if (framer_.version().KnowsWhichDecrypterToUse()) {
+    framer_.InstallDecrypter(ENCRYPTION_HANDSHAKE,
+                             std::move(bad_handshake_crypters.decrypter));
+  } else {
+    framer_.SetDecrypter(ENCRYPTION_HANDSHAKE,
+                         std::move(bad_handshake_crypters.decrypter));
+  }
+
+  const unsigned char type_byte =
+      framer_.transport_version() == QUIC_VERSION_44 ? 0xFC : 0xE3;
+  // clang-format off
+  unsigned char packet[] = {
+    // public flags (version included, 8-byte connection ID,
+    // 4-byte packet number)
+    0x28,
+    // connection_id
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // packet number
+    0x12, 0x34, 0x56, 0x00,
+    // padding frames
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  unsigned char packet44[] = {
+    // public flags (long header with packet type HANDSHAKE and
+    // 4-byte packet number)
+    type_byte,
+    // version
+    QUIC_VERSION_BYTES,
+    // connection ID lengths
+    0x05,
+    // source connection ID
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // long header packet length
+    0x05,
+    // packet number
+    0x12, 0x34, 0x56, 0x00,
+    // padding frames
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  unsigned char packet99[] = {
+    // public flags (long header with packet type HANDSHAKE and
+    // 4-byte packet number)
+    0xE3,
+    // version
+    QUIC_VERSION_BYTES,
+    // destination connection ID length
+    0x00,
+    // source connection ID length
+    0x08,
+    // source connection ID
+    0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+    // long header packet length
+    0x24,
+    // packet number
+    0x12, 0x34, 0x56, 0x00,
+    // padding frames
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  // clang-format on
+  unsigned char* p = packet;
+  size_t p_length = QUIC_ARRAYSIZE(packet);
+  if (framer_.transport_version() == QUIC_VERSION_99) {
+    p = packet99;
+    p_length = QUIC_ARRAYSIZE(packet99);
+  } else if (framer_.transport_version() >= QUIC_VERSION_44) {
+    p = packet44;
+    p_length = QUIC_ARRAYSIZE(packet44);
+  }
+
+  EXPECT_FALSE(
+      framer_.ProcessPacket(QuicEncryptedPacket(AsChars(p), p_length, false)));
+  EXPECT_EQ(QUIC_DECRYPTION_FAILURE, framer_.error());
+  if (GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall)) {
+    ASSERT_EQ(1u, visitor_.undecryptable_packets_.size());
+    ASSERT_EQ(1u, visitor_.undecryptable_decryption_levels_.size());
+    ASSERT_EQ(1u, visitor_.undecryptable_has_decryption_keys_.size());
+    CompareCharArraysWithHexError(
+        "undecryptable packet", visitor_.undecryptable_packets_[0]->data(),
+        visitor_.undecryptable_packets_[0]->length(), AsChars(p), p_length);
+    if (framer_.version().KnowsWhichDecrypterToUse()) {
+      EXPECT_EQ(ENCRYPTION_HANDSHAKE,
+                visitor_.undecryptable_decryption_levels_[0]);
+    }
+    EXPECT_EQ(framer_.version().KnowsWhichDecrypterToUse(),
+              visitor_.undecryptable_has_decryption_keys_[0]);
+  } else {
+    EXPECT_EQ(0u, visitor_.undecryptable_packets_.size());
+    EXPECT_EQ(0u, visitor_.undecryptable_decryption_levels_.size());
+    EXPECT_EQ(0u, visitor_.undecryptable_has_decryption_keys_.size());
+  }
+}
+
+TEST_P(QuicFramerTest, UndecryptableCoalescedPacket) {
+  if (!QuicVersionHasLongHeaderLengths(framer_.transport_version())) {
+    return;
+  }
+  ASSERT_TRUE(framer_.version().KnowsWhichDecrypterToUse());
+  SetDecrypterLevel(ENCRYPTION_ZERO_RTT);
+  // We create a bad client decrypter by using initial encryption with a
+  // bogus connection ID; it should fail to decrypt everything.
+  QuicConnectionId bogus_connection_id = TestConnectionId(0xbad);
+  CrypterPair bad_handshake_crypters;
+  CryptoUtils::CreateTlsInitialCrypters(
+      Perspective::IS_CLIENT, framer_.transport_version(), bogus_connection_id,
+      &bad_handshake_crypters);
+  framer_.InstallDecrypter(ENCRYPTION_HANDSHAKE,
+                           std::move(bad_handshake_crypters.decrypter));
+  // clang-format off
+  unsigned char packet[] = {
+    // first coalesced packet
+      // public flags (long header with packet type HANDSHAKE and
+      // 4-byte packet number)
+      0xE3,
+      // version
+      QUIC_VERSION_BYTES,
+      // destination connection ID length
+      0x08,
+      // destination connection ID
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // source connection ID length
+      0x00,
+      // long header packet length
+      0x1E,
+      // packet number
+      0x12, 0x34, 0x56, 0x78,
+      // frame type (IETF_STREAM frame with FIN, LEN, and OFFSET bits set)
+      0x08 | 0x01 | 0x02 | 0x04,
+      // stream id
+      kVarInt62FourBytes + 0x00, 0x02, 0x03, 0x04,
+      // offset
+      kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+      0x32, 0x10, 0x76, 0x54,
+      // data length
+      kVarInt62OneByte + 0x0c,
+      // data
+      'h',  'e',  'l',  'l',
+      'o',  ' ',  'w',  'o',
+      'r',  'l',  'd',  '!',
+    // second coalesced packet
+      // public flags (long header with packet type ZERO_RTT_PROTECTED and
+      // 4-byte packet number)
+      0xD3,
+      // version
+      QUIC_VERSION_BYTES,
+      // destination connection ID length
+      0x08,
+      // destination connection ID
+      0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+      // source connection ID length
+      0x00,
+      // long header packet length
+      0x1E,
+      // packet number
+      0x12, 0x34, 0x56, 0x79,
+      // frame type (IETF_STREAM frame with FIN, LEN, and OFFSET bits set)
+      0x08 | 0x01 | 0x02 | 0x04,
+      // stream id
+      kVarInt62FourBytes + 0x00, 0x02, 0x03, 0x04,
+      // offset
+      kVarInt62EightBytes + 0x3A, 0x98, 0xFE, 0xDC,
+      0x32, 0x10, 0x76, 0x54,
+      // data length
+      kVarInt62OneByte + 0x0c,
+      // data
+      'H',  'E',  'L',  'L',
+      'O',  '_',  'W',  'O',
+      'R',  'L',  'D',  '?',
+  };
+  // clang-format on
+  const size_t length_of_first_coalesced_packet = 46;
+
+  QuicEncryptedPacket encrypted(AsChars(packet), QUIC_ARRAYSIZE(packet), false);
+  EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+
+  EXPECT_EQ(QUIC_DECRYPTION_FAILURE, framer_.error());
+
+  if (GetQuicRestartFlag(quic_framer_uses_undecryptable_upcall)) {
+    ASSERT_EQ(1u, visitor_.undecryptable_packets_.size());
+    ASSERT_EQ(1u, visitor_.undecryptable_decryption_levels_.size());
+    ASSERT_EQ(1u, visitor_.undecryptable_has_decryption_keys_.size());
+    // Make sure we only receive the first undecryptable packet and not the
+    // full packet including the second coalesced packet.
+    CompareCharArraysWithHexError(
+        "undecryptable packet", visitor_.undecryptable_packets_[0]->data(),
+        visitor_.undecryptable_packets_[0]->length(), AsChars(packet),
+        length_of_first_coalesced_packet);
+    EXPECT_EQ(ENCRYPTION_HANDSHAKE,
+              visitor_.undecryptable_decryption_levels_[0]);
+    EXPECT_TRUE(visitor_.undecryptable_has_decryption_keys_[0]);
+  } else {
+    EXPECT_EQ(0u, visitor_.undecryptable_packets_.size());
+    EXPECT_EQ(0u, visitor_.undecryptable_decryption_levels_.size());
+    EXPECT_EQ(0u, visitor_.undecryptable_has_decryption_keys_.size());
+  }
+
+  // Make sure the second coalesced packet is parsed correctly.
+  ASSERT_EQ(visitor_.coalesced_packets_.size(), 1u);
+  EXPECT_TRUE(framer_.ProcessPacket(*visitor_.coalesced_packets_[0].get()));
+
+  ASSERT_TRUE(visitor_.header_.get());
+
+  ASSERT_EQ(1u, visitor_.stream_frames_.size());
+  EXPECT_EQ(0u, visitor_.ack_frames_.size());
+
+  // Stream ID should be the last 3 bytes of kStreamId.
+  EXPECT_EQ(0x00FFFFFF & kStreamId, visitor_.stream_frames_[0]->stream_id);
+  EXPECT_TRUE(visitor_.stream_frames_[0]->fin);
+  EXPECT_EQ(kStreamOffset, visitor_.stream_frames_[0]->offset);
+  CheckStreamFrameData("HELLO_WORLD?", visitor_.stream_frames_[0].get());
+}
+
 TEST_P(QuicFramerTest, MismatchedCoalescedPacket) {
   if (!QuicVersionHasLongHeaderLengths(framer_.transport_version())) {
     return;
diff --git a/quic/core/quic_ietf_framer_test.cc b/quic/core/quic_ietf_framer_test.cc
index f0872b6..9a2b562 100644
--- a/quic/core/quic_ietf_framer_test.cc
+++ b/quic/core/quic_ietf_framer_test.cc
@@ -122,6 +122,10 @@
 
   void OnCoalescedPacket(const QuicEncryptedPacket& /*packet*/) override {}
 
+  void OnUndecryptablePacket(const QuicEncryptedPacket& /*packet*/,
+                             EncryptionLevel /*decryption_level*/,
+                             bool /*has_decryption_key*/) override {}
+
   bool OnStreamFrame(const QuicStreamFrame& /*frame*/) override { return true; }
 
   bool OnCryptoFrame(const QuicCryptoFrame& /*frame*/) override { return true; }
diff --git a/quic/core/quic_packets.cc b/quic/core/quic_packets.cc
index 03b8420..80336d4 100644
--- a/quic/core/quic_packets.cc
+++ b/quic/core/quic_packets.cc
@@ -276,6 +276,11 @@
 QuicData::QuicData(const char* buffer, size_t length, bool owns_buffer)
     : buffer_(buffer), length_(length), owns_buffer_(owns_buffer) {}
 
+QuicData::QuicData(QuicStringPiece packet_data)
+    : buffer_(packet_data.data()),
+      length_(packet_data.length()),
+      owns_buffer_(false) {}
+
 QuicData::~QuicData() {
   if (owns_buffer_) {
     delete[] const_cast<char*>(buffer_);
@@ -330,6 +335,9 @@
                                          bool owns_buffer)
     : QuicData(buffer, length, owns_buffer) {}
 
+QuicEncryptedPacket::QuicEncryptedPacket(QuicStringPiece data)
+    : QuicData(data) {}
+
 std::unique_ptr<QuicEncryptedPacket> QuicEncryptedPacket::Clone() const {
   char* buffer = new char[this->length()];
   memcpy(buffer, this->data(), this->length());
diff --git a/quic/core/quic_packets.h b/quic/core/quic_packets.h
index 3af15ea..5c34b64 100644
--- a/quic/core/quic_packets.h
+++ b/quic/core/quic_packets.h
@@ -204,8 +204,13 @@
 
 class QUIC_EXPORT_PRIVATE QuicData {
  public:
+  // Creates a QuicData from a buffer and length. Does not own the buffer.
   QuicData(const char* buffer, size_t length);
+  // Creates a QuicData from a buffer and length,
+  // optionally taking ownership of the buffer.
   QuicData(const char* buffer, size_t length, bool owns_buffer);
+  // Creates a QuicData from a QuicStringPiece. Does not own the buffer.
+  QuicData(QuicStringPiece data);
   QuicData(const QuicData&) = delete;
   QuicData& operator=(const QuicData&) = delete;
   virtual ~QuicData();
@@ -263,8 +268,16 @@
 
 class QUIC_EXPORT_PRIVATE QuicEncryptedPacket : public QuicData {
  public:
+  // Creates a QuicEncryptedPacket from a buffer and length.
+  // Does not own the buffer.
   QuicEncryptedPacket(const char* buffer, size_t length);
+  // Creates a QuicEncryptedPacket from a buffer and length,
+  // optionally taking ownership of the buffer.
   QuicEncryptedPacket(const char* buffer, size_t length, bool owns_buffer);
+  // Creates a QuicEncryptedPacket from a QuicStringPiece.
+  // Does not own the buffer.
+  QuicEncryptedPacket(QuicStringPiece data);
+
   QuicEncryptedPacket(const QuicEncryptedPacket&) = delete;
   QuicEncryptedPacket& operator=(const QuicEncryptedPacket&) = delete;