Eliminate heap allocations from initial obfuscator generation in QUICHE. Each crypter still invokes 16 malloc operations due to OpenSSL HKDF libraries.

When creating initial obfuscators, the Crypters themselves are allocated from the heap, but all operations to populate those crypters are conducted entirely on the stack.

Additional #includes in *_session fix compiler failures in the benchmark test.

Result of benchmark run:
blaze run -c opt --dynamic_mode=off --copt=-gmlt third_party/quic/core/crypto/crypto_utils_benchmark -- --benchmark_filter=all

CPU: Intel Icelake with HyperThreading (24 cores) dL1:48KB dL2:1280KB dL3:54MB
Benchmark                              Time(ns)        CPU(ns)     Iterations
-----------------------------------------------------------------------------
BM_InitialObfuscators                      6506           6583           7290
BM_InitialObfuscators                      6507           6583           7292
BM_InitialObfuscators                      6497           6588           7285
BM_InitialObfuscators                      6525           6585           7296
BM_InitialObfuscators                      6527           6587           7282
BM_InitialObfuscators                      6514           6579           7306
BM_InitialObfuscators                      6504           6616           7260
BM_InitialObfuscators                      6482           6584           7299
BM_InitialObfuscators                      6488           6589           7286
BM_InitialObfuscators                      6489           6042           7291
BM_InitialObfuscators                      6533           6592           7290
BM_InitialObfuscators                      6563           6799          10000
BM_InitialObfuscators_mean                 6513           6568          90177
BM_InitialObfuscators_stddev                 23.5          169          90177

BM_InitialObfuscatorsNoMalloc              6267           6414          10000
BM_InitialObfuscatorsNoMalloc              6316           6405          10000
BM_InitialObfuscatorsNoMalloc              6251           6050           7286
BM_InitialObfuscatorsNoMalloc              6250           6405          10000
BM_InitialObfuscatorsNoMalloc              6266           6045           7294
BM_InitialObfuscatorsNoMalloc              6262           6039           7286
BM_InitialObfuscatorsNoMalloc              6250           6028           7300
BM_InitialObfuscatorsNoMalloc              6232           6040           7292
BM_InitialObfuscatorsNoMalloc              6250           6457          10000
BM_InitialObfuscatorsNoMalloc              6230           6409          10000
BM_InitialObfuscatorsNoMalloc              6243           6405          10000
BM_InitialObfuscatorsNoMalloc              6248           6069           7259
BM_InitialObfuscatorsNoMalloc_mean         6256           6260         103717
BM_InitialObfuscatorsNoMalloc_stddev         22.4          184         103717

Protected by FLAGS_quic_reloadable_flag_quic_heapless_obfuscator.

PiperOrigin-RevId: 751001626
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h
index 1ae5624..e24c493 100755
--- a/quiche/common/quiche_feature_flags_list.h
+++ b/quiche/common/quiche_feature_flags_list.h
@@ -35,6 +35,7 @@
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_enable_version_rfcv2, false, false, "When true, support RFC9369.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_fin_before_completed_http_headers, false, true, "If true, close the connection with error if FIN is received before finish receiving the whole HTTP headers.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_fix_timeouts, true, true, "If true, postpone setting handshake timeout to infinite to handshake complete.")
+QUICHE_FLAG(bool, quiche_reloadable_flag_quic_heapless_obfuscator, false, false, "If true, generates QUIC initial obfuscators with no heap allocations.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_heapless_static_parser, false, false, "If true, stops parsing immediately on unknown version, to avoid a potential malloc when parsing the connection ID")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_ignore_gquic_probing, true, true, "If true, QUIC server will not respond to gQUIC probing packet(PING + PADDING) but treat it as a regular packet.")
 QUICHE_FLAG(bool, quiche_reloadable_flag_quic_limit_new_streams_per_loop_2, true, true, "If true, when the peer sends connection options \\\'SLP1\\\', \\\'SLP2\\\' and \\\'SLPF\\\', internet facing GFEs will only allow a limited number of new requests to be processed per event loop, and postpone the rest to the following event loops. Also guard QuicConnection to iterate through all decrypters at each encryption level to get cipher id for a request.")
diff --git a/quiche/quic/core/crypto/crypto_utils.cc b/quiche/quic/core/crypto/crypto_utils.cc
index 2cbdb83..51ee57b 100644
--- a/quiche/quic/core/crypto/crypto_utils.cc
+++ b/quiche/quic/core/crypto/crypto_utils.cc
@@ -4,30 +4,30 @@
 
 #include "quiche/quic/core/crypto/crypto_utils.h"
 
-#include <algorithm>
 #include <cstddef>
+#include <cstdint>
+#include <cstring>
 #include <memory>
 #include <optional>
 #include <string>
-#include <utility>
 #include <vector>
 
 #include "absl/base/macros.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "absl/types/span.h"
 #include "openssl/bytestring.h"
 #include "openssl/err.h"
 #include "openssl/hkdf.h"
 #include "openssl/mem.h"
 #include "openssl/sha.h"
-#include "quiche/quic/core/crypto/aes_128_gcm_12_decrypter.h"
-#include "quiche/quic/core/crypto/aes_128_gcm_12_encrypter.h"
 #include "quiche/quic/core/crypto/aes_128_gcm_decrypter.h"
 #include "quiche/quic/core/crypto/aes_128_gcm_encrypter.h"
 #include "quiche/quic/core/crypto/crypto_handshake.h"
 #include "quiche/quic/core/crypto/crypto_protocol.h"
 #include "quiche/quic/core/crypto/null_decrypter.h"
 #include "quiche/quic/core/crypto/null_encrypter.h"
+#include "quiche/quic/core/crypto/quic_crypter.h"
 #include "quiche/quic/core/crypto/quic_decrypter.h"
 #include "quiche/quic/core/crypto/quic_encrypter.h"
 #include "quiche/quic/core/crypto/quic_hkdf.h"
@@ -36,11 +36,15 @@
 #include "quiche/quic/core/quic_constants.h"
 #include "quiche/quic/core/quic_data_writer.h"
 #include "quiche/quic/core/quic_time.h"
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/core/quic_versions.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_flag_utils.h"
+#include "quiche/quic/platform/api/quic_flags.h"
 #include "quiche/quic/platform/api/quic_logging.h"
 #include "quiche/common/quiche_endian.h"
+#include "quiche/common/wire_serialization.h"
 
 namespace quic {
 
@@ -59,6 +63,37 @@
 // The implicit PRF is explicitly passed into HkdfExpandLabel as |prf|; the
 // Secret, Label, and Length are passed in as |secret|, |label|, and
 // |out_len|, respectively. The resulting expanded secret is returned.
+bool HkdfExpandLabel(const EVP_MD* prf, absl::Span<const uint8_t> secret,
+                     absl::string_view label, absl::Span<uint8_t> out) {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_heapless_obfuscator, 2, 7);
+  constexpr absl::string_view kLabelPrefix = "tls13 ";
+  constexpr size_t kMaxLabelLength = 10;  // "quicv2 key" is the longest
+  QUICHE_DCHECK_LE(label.length(), kMaxLabelLength);
+  char quic_hkdf_label[sizeof(uint16_t) + sizeof(uint8_t) +
+                       kLabelPrefix.length() + kMaxLabelLength +
+                       sizeof(uint8_t)];
+  QuicDataWriter writer(ABSL_ARRAYSIZE(quic_hkdf_label), quic_hkdf_label);
+  if (!quiche::SerializeIntoWriter(
+           writer, quiche::WireUint16(static_cast<uint16_t>(out.length())),
+           quiche::WireUint8(
+               static_cast<uint8_t>(kLabelPrefix.length() + label.length())),
+           quiche::WireBytes(kLabelPrefix), quiche::WireBytes(label),
+           quiche::WireUint8(0))
+           .ok()) {
+    QUIC_LOG(ERROR) << "Failed to serialize label";
+    return false;
+  }
+  if (!HKDF_expand(out.data(), out.size(), prf, secret.data(), secret.size(),
+                   reinterpret_cast<const uint8_t*>(quic_hkdf_label),
+                   writer.length())) {
+    QUIC_LOG(ERROR) << "Running HKDF-Expand-Label failed";
+    return false;
+  }
+  return true;
+}
+// Legacy overloaded version that accepts string and returns vector, which leads
+// to heap allocations.
+// TODO(martinduke): Delete this.
 std::vector<uint8_t> HkdfExpandLabel(const EVP_MD* prf,
                                      absl::Span<const uint8_t> secret,
                                      const std::string& label, size_t out_len) {
@@ -95,10 +130,34 @@
   return out;
 }
 
+// "quicv2 key" is the longest string for a version label.
+constexpr size_t kMaxVersionLabelLength = 10;
+
 }  // namespace
 
-const std::string getLabelForVersion(const ParsedQuicVersion& version,
-                                     const absl::string_view& predicate) {
+// Populates |out| with the label for |version| and |predicate|, returning a
+// string_view of the correct length.
+absl::string_view GetLabelForVersion(const ParsedQuicVersion& version,
+                                     absl::string_view predicate,
+                                     absl::Span<char> out) {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_heapless_obfuscator, 3, 7);
+  static_assert(SupportedVersions().size() == 4u,
+                "Supported versions out of sync with HKDF labels");
+  QuicDataWriter writer(out.size(), out.data());
+  constexpr absl::string_view kV2Prefix = "quicv2 ";
+  constexpr absl::string_view kV1Prefix = "quic ";
+  if (version == ParsedQuicVersion::RFCv2()) {
+    writer.WriteStringPiece(kV2Prefix);
+  } else {
+    writer.WriteStringPiece(kV1Prefix);
+  }
+  writer.WriteStringPiece(predicate);
+  return absl::string_view(out.data(), writer.length());
+}
+// Legacy overloaded version that returns string, leading to a heap allocation.
+// TODO(martinduke): Delete this.
+std::string getLabelForVersion(const ParsedQuicVersion& version,
+                               const absl::string_view& predicate) {
   static_assert(SupportedVersions().size() == 4u,
                 "Supported versions out of sync with HKDF labels");
   if (version == ParsedQuicVersion::RFCv2()) {
@@ -108,6 +167,25 @@
   }
 }
 
+// static
+void CryptoUtils::InitializeCrypterSecrets(const EVP_MD* prf,
+                                           absl::Span<const uint8_t> pp_secret,
+                                           const ParsedQuicVersion& version,
+                                           QuicCrypter* crypter) {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_heapless_obfuscator, 4, 7);
+  SetKeyAndIVHeapless(prf, pp_secret, version, crypter);
+  uint8_t header_protection_key[16];
+  QUIC_BUG_IF(quic_bug_hp_length_mismatch,
+              crypter->GetKeySize() > sizeof(header_protection_key))
+      << "HP length does not match crypter";
+  GenerateHeaderProtectionKey(
+      prf, pp_secret, version,
+      absl::Span<uint8_t>(header_protection_key, crypter->GetKeySize()));
+  crypter->SetHeaderProtectionKey(absl::string_view(
+      reinterpret_cast<char*>(header_protection_key), crypter->GetKeySize()));
+}
+// Version that uses the heap.
+// TODO(martinduke): Delete this.
 void CryptoUtils::InitializeCrypterSecrets(
     const EVP_MD* prf, const std::vector<uint8_t>& pp_secret,
     const ParsedQuicVersion& version, QuicCrypter* crypter) {
@@ -119,6 +197,38 @@
                         header_protection_key.size()));
 }
 
+// static
+void CryptoUtils::SetKeyAndIVHeapless(const EVP_MD* prf,
+                                      absl::Span<const uint8_t> pp_secret,
+                                      const ParsedQuicVersion& version,
+                                      QuicCrypter* crypter) {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_heapless_obfuscator, 5, 7);
+  uint8_t key[16];
+  QUIC_BUG_IF(quic_bug_key_length_mismatch, crypter->GetKeySize() > sizeof(key))
+      << "Key length does not match crypter";
+
+  char version_label_raw[kMaxVersionLabelLength];
+  constexpr absl::string_view kKeyPredicate = "key";
+  absl::string_view version_label = GetLabelForVersion(
+      version, kKeyPredicate,
+      absl::Span<char>(version_label_raw, kMaxVersionLabelLength));
+  HkdfExpandLabel(prf, pp_secret, version_label,
+                  absl::Span<uint8_t>(key, crypter->GetKeySize()));
+  uint8_t iv[12];
+  QUIC_BUG_IF(quic_bug_iv_length_mismatch, crypter->GetIVSize() > sizeof(iv))
+      << "IV length does not match crypter";
+  constexpr absl::string_view kIvPredicate = "iv";
+  version_label = GetLabelForVersion(
+      version, kIvPredicate,
+      absl::Span<char>(version_label_raw, kMaxVersionLabelLength));
+  HkdfExpandLabel(prf, pp_secret, version_label,
+                  absl::Span<uint8_t>(iv, crypter->GetIVSize()));
+  crypter->SetKey(
+      absl::string_view(reinterpret_cast<char*>(key), crypter->GetKeySize()));
+  crypter->SetIV(
+      absl::string_view(reinterpret_cast<char*>(iv), crypter->GetIVSize()));
+}
+// TODO(martinduke): Delete this.
 void CryptoUtils::SetKeyAndIV(const EVP_MD* prf,
                               absl::Span<const uint8_t> pp_secret,
                               const ParsedQuicVersion& version,
@@ -134,6 +244,19 @@
       absl::string_view(reinterpret_cast<char*>(iv.data()), iv.size()));
 }
 
+// static
+bool CryptoUtils::GenerateHeaderProtectionKey(
+    const EVP_MD* prf, absl::Span<const uint8_t> pp_secret,
+    const ParsedQuicVersion& version, absl::Span<uint8_t> out) {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_heapless_obfuscator, 6, 7);
+  char version_label_raw[kMaxVersionLabelLength];
+  constexpr absl::string_view kHeaderProtectionPredicate = "hp";
+  absl::string_view version_label = GetLabelForVersion(
+      version, kHeaderProtectionPredicate,
+      absl::Span<char>(version_label_raw, kMaxVersionLabelLength));
+  return HkdfExpandLabel(prf, pp_secret, version_label, out);
+}
+// TODO(martinduke): Delete this.
 std::vector<uint8_t> CryptoUtils::GenerateHeaderProtectionKey(
     const EVP_MD* prf, absl::Span<const uint8_t> pp_secret,
     const ParsedQuicVersion& version, size_t out_len) {
@@ -141,6 +264,19 @@
                          out_len);
 }
 
+// static
+bool CryptoUtils::GenerateNextKeyPhaseSecret(
+    const EVP_MD* prf, const ParsedQuicVersion& version,
+    const absl::Span<const uint8_t> current_secret, absl::Span<uint8_t> out) {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_heapless_obfuscator, 7, 7);
+  char version_label_raw[kMaxVersionLabelLength];
+  constexpr absl::string_view kKeyUpdatePredicate = "ku";
+  absl::string_view version_label = GetLabelForVersion(
+      version, kKeyUpdatePredicate,
+      absl::Span<char>(version_label_raw, kMaxVersionLabelLength));
+  return HkdfExpandLabel(prf, current_secret, version_label, out);
+}
+// TODO(martinduke): Delete this.
 std::vector<uint8_t> CryptoUtils::GenerateNextKeyPhaseSecret(
     const EVP_MD* prf, const ParsedQuicVersion& version,
     const std::vector<uint8_t>& current_secret) {
@@ -277,10 +413,89 @@
 }  // namespace
 
 // static
+void CryptoUtils::PopulateInitialObfuscators(Perspective perspective,
+                                             const ParsedQuicVersion& version,
+                                             QuicConnectionId& connection_id,
+                                             QuicCrypter* encrypter,
+                                             QuicCrypter* decrypter) {
+  QUIC_BUG_IF(quic_bug_12871_1, !QuicUtils::IsConnectionIdValidForVersion(
+                                    connection_id, version.transport_version))
+      << "CreateTlsInitialCrypters: attempted to use connection ID "
+      << connection_id << " which is invalid with version " << version;
+  const EVP_MD* hash = EVP_sha256();
+
+  size_t salt_len;
+  const uint8_t* salt = InitialSaltForVersion(version, &salt_len);
+  uint8_t handshake_secret_raw[EVP_MAX_MD_SIZE];
+  size_t handshake_secret_len;
+  const bool hkdf_extract_success =
+      HKDF_extract(handshake_secret_raw, &handshake_secret_len, hash,
+                   reinterpret_cast<const uint8_t*>(connection_id.data()),
+                   connection_id.length(), salt, salt_len);
+  QUIC_BUG_IF(quic_bug_12871_2, !hkdf_extract_success)
+      << "HKDF_extract failed when creating initial crypters";
+  absl::Span<const uint8_t> handshake_secret(handshake_secret_raw,
+                                             handshake_secret_len);
+
+  constexpr absl::string_view kClientLabel = "client in";
+  constexpr absl::string_view kServerLabel = "server in";
+  absl::string_view encryption_label, decryption_label;
+  if (perspective == Perspective::IS_CLIENT) {
+    encryption_label = absl::string_view(kClientLabel);
+    decryption_label = absl::string_view(kServerLabel);
+  } else {
+    encryption_label = absl::string_view(kServerLabel);
+    decryption_label = absl::string_view(kClientLabel);
+  }
+  if (encrypter != nullptr) {
+    uint8_t encryption_secret[32];
+    QUIC_BUG_IF(quic_bug_digest_mismatch,
+                EVP_MD_size(hash) != sizeof(encryption_secret))
+        << "EVP_MD_size(hash) != sizeof(encryption_secret)";
+    HkdfExpandLabel(
+        hash, handshake_secret, encryption_label,
+        absl::Span<uint8_t>(encryption_secret, sizeof(encryption_secret)));
+    InitializeCrypterSecrets(hash, encryption_secret, version, encrypter);
+  }
+  if (decrypter != nullptr) {
+    uint8_t decryption_secret[32];
+    QUIC_BUG_IF(quic_bug_digest_mismatch,
+                EVP_MD_size(hash) != sizeof(decryption_secret))
+        << "EVP_MD_size(hash) != sizeof(decryption_secret)";
+    HkdfExpandLabel(
+        hash, handshake_secret, decryption_label,
+        absl::Span<uint8_t>(decryption_secret, sizeof(decryption_secret)));
+    InitializeCrypterSecrets(hash, decryption_secret, version, decrypter);
+  }
+}
+
+// static
+void CryptoUtils::CreateInitialObfuscatorsNew(Perspective perspective,
+                                              ParsedQuicVersion version,
+                                              QuicConnectionId connection_id,
+                                              CrypterPair* crypters) {
+  if (!version.UsesInitialObfuscators()) {
+    crypters->encrypter = std::make_unique<NullEncrypter>(perspective);
+    crypters->decrypter = std::make_unique<NullDecrypter>(perspective);
+    return;
+  }
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_heapless_obfuscator, 1, 7);
+  crypters->encrypter = std::make_unique<Aes128GcmEncrypter>();
+  crypters->decrypter = std::make_unique<Aes128GcmDecrypter>();
+
+  PopulateInitialObfuscators(perspective, version, connection_id,
+                             crypters->encrypter.get(),
+                             crypters->decrypter.get());
+}
+// static
 void CryptoUtils::CreateInitialObfuscators(Perspective perspective,
                                            ParsedQuicVersion version,
                                            QuicConnectionId connection_id,
                                            CrypterPair* crypters) {
+  if (GetQuicReloadableFlag(quic_heapless_obfuscator)) {
+    CreateInitialObfuscatorsNew(perspective, version, connection_id, crypters);
+    return;
+  }
   QUIC_DLOG(INFO) << "Creating "
                   << (perspective == Perspective::IS_CLIENT ? "client"
                                                             : "server")
diff --git a/quiche/quic/core/crypto/crypto_utils.h b/quiche/quic/core/crypto/crypto_utils.h
index 31ec380..f1541ab 100644
--- a/quiche/quic/core/crypto/crypto_utils.h
+++ b/quiche/quic/core/crypto/crypto_utils.h
@@ -10,20 +10,20 @@
 #include <cstddef>
 #include <cstdint>
 #include <string>
+#include <vector>
 
 #include "absl/strings/string_view.h"
+#include "absl/types/span.h"
 #include "openssl/evp.h"
 #include "openssl/ssl.h"
 #include "quiche/quic/core/crypto/crypto_handshake.h"
 #include "quiche/quic/core/crypto/crypto_handshake_message.h"
-#include "quiche/quic/core/crypto/crypto_protocol.h"
 #include "quiche/quic/core/crypto/quic_crypter.h"
 #include "quiche/quic/core/crypto/quic_random.h"
 #include "quiche/quic/core/quic_connection_id.h"
-#include "quiche/quic/core/quic_packets.h"
 #include "quiche/quic/core/quic_time.h"
+#include "quiche/quic/core/quic_types.h"
 #include "quiche/quic/core/quic_versions.h"
-#include "quiche/quic/platform/api/quic_export.h"
 
 namespace quic {
 
@@ -81,6 +81,12 @@
   // as described in draft-ietf-quic-tls-14, section 5.1, or "quicv2 " as
   // described in draft-ietf-quic-v2-01.
   static void InitializeCrypterSecrets(const EVP_MD* prf,
+                                       absl::Span<const uint8_t> pp_secret,
+                                       const ParsedQuicVersion& version,
+                                       QuicCrypter* crypter);
+  // Overloaded legacy version that takes a vector.
+  // TODO(martinduke): Delete this.
+  static void InitializeCrypterSecrets(const EVP_MD* prf,
                                        const std::vector<uint8_t>& pp_secret,
                                        const ParsedQuicVersion& version,
                                        QuicCrypter* crypter);
@@ -89,21 +95,48 @@
   // fields on the given QuicCrypter |*crypter|, but does not set the header
   // protection key. GenerateHeaderProtectionKey/SetHeaderProtectionKey must be
   // called before using |crypter|.
+  static void SetKeyAndIVHeapless(const EVP_MD* prf,
+                                  absl::Span<const uint8_t> pp_secret,
+                                  const ParsedQuicVersion& version,
+                                  QuicCrypter* crypter);
+  // TODO(martinduke): Delete this legacy version that allocates more from the
+  // heap.
   static void SetKeyAndIV(const EVP_MD* prf,
                           absl::Span<const uint8_t> pp_secret,
                           const ParsedQuicVersion& version,
                           QuicCrypter* crypter);
 
-  // Derives the header protection key from the packet protection secret.
+  // Derives the header protection key from the packet protection secret. Writes
+  // the result to |out|, limited by the size of the provided span. Returns true
+  // if the derivation was successful, false otherwise.
+  static bool GenerateHeaderProtectionKey(const EVP_MD* prf,
+                                          absl::Span<const uint8_t> pp_secret,
+                                          const ParsedQuicVersion& version,
+                                          absl::Span<uint8_t> out);
+  // Overloaded legacy version that allocates the vector.
   static std::vector<uint8_t> GenerateHeaderProtectionKey(
       const EVP_MD* prf, absl::Span<const uint8_t> pp_secret,
       const ParsedQuicVersion& version, size_t out_len);
 
-  // Given a secret for key phase n, return the secret for phase n+1.
+  // Given a secret for key phase n, return the secret for phase n+1 in |out|.
+  // Returns true if the derivation was successful, false otherwise.
+  static bool GenerateNextKeyPhaseSecret(
+      const EVP_MD* prf, const ParsedQuicVersion& version,
+      absl::Span<const uint8_t> current_secret, absl::Span<uint8_t> out);
+  // Overloaded legacy version that allocates the vector.
   static std::vector<uint8_t> GenerateNextKeyPhaseSecret(
       const EVP_MD* prf, const ParsedQuicVersion& version,
       const std::vector<uint8_t>& current_secret);
 
+  // Assumes Initial crypters have already been allocated, to create a path
+  // with heap allocations limited to those inherited from OpenSSL. |encrypter|
+  // or |decrypter| may be nullptr.
+  static void PopulateInitialObfuscators(Perspective perspective,
+                                         const ParsedQuicVersion& version,
+                                         QuicConnectionId& connection_id,
+                                         QuicCrypter* encrypter,
+                                         QuicCrypter* decrypter);
+
   // IETF QUIC encrypts ENCRYPTION_INITIAL messages with a version-specific key
   // (to prevent network observers that are not aware of that QUIC version from
   // making decisions based on the TLS handshake). This packet protection secret
@@ -114,6 +147,10 @@
   // as setting the key and IV on those crypters. For older versions of QUIC
   // that do not use the new IETF style ENCRYPTION_INITIAL obfuscators, this
   // function puts a NullEncrypter and NullDecrypter in |*crypters|.
+  static void CreateInitialObfuscatorsNew(Perspective perspective,
+                                          ParsedQuicVersion version,
+                                          QuicConnectionId connection_id,
+                                          CrypterPair* crypters);
   static void CreateInitialObfuscators(Perspective perspective,
                                        ParsedQuicVersion version,
                                        QuicConnectionId connection_id,
diff --git a/quiche/quic/core/crypto/crypto_utils_test.cc b/quiche/quic/core/crypto/crypto_utils_test.cc
index 6ddc522..e585d57 100644
--- a/quiche/quic/core/crypto/crypto_utils_test.cc
+++ b/quiche/quic/core/crypto/crypto_utils_test.cc
@@ -176,7 +176,97 @@
 // Test that the library is using the correct labels for each version, and
 // therefore generating correct obfuscators, using the test vectors in appendix
 // A of each RFC or internet-draft.
-TEST_F(CryptoUtilsTest, ValidateCryptoLabels) {
+TEST_F(CryptoUtilsTest, ValidateCryptoLabelsHeapless) {
+  SetQuicReloadableFlag(quic_heapless_obfuscator, true);
+  // if the number of HTTP/3 QUIC versions has changed, we need to change the
+  // expected_keys hardcoded into this test. Regrettably, this is not a
+  // compile-time constant.
+  EXPECT_EQ(AllSupportedVersionsWithTls().size(), 3u);
+  const char draft_29_key[] = {// test vector from draft-ietf-quic-tls-29, A.1
+                               0x14,
+                               static_cast<char>(0x9d),
+                               0x0b,
+                               0x16,
+                               0x62,
+                               static_cast<char>(0xab),
+                               static_cast<char>(0x87),
+                               0x1f,
+                               static_cast<char>(0xbe),
+                               0x63,
+                               static_cast<char>(0xc4),
+                               static_cast<char>(0x9b),
+                               0x5e,
+                               0x65,
+                               0x5a,
+                               0x5d};
+  const char v1_key[] = {// test vector from RFC 9001, A.1
+                         static_cast<char>(0xcf),
+                         0x3a,
+                         0x53,
+                         0x31,
+                         0x65,
+                         0x3c,
+                         0x36,
+                         0x4c,
+                         static_cast<char>(0x88),
+                         static_cast<char>(0xf0),
+                         static_cast<char>(0xf3),
+                         0x79,
+                         static_cast<char>(0xb6),
+                         0x06,
+                         0x7e,
+                         0x37};
+  const char v2_08_key[] = {// test vector from draft-ietf-quic-v2-08
+                            static_cast<char>(0x82),
+                            static_cast<char>(0xdb),
+                            static_cast<char>(0x63),
+                            static_cast<char>(0x78),
+                            static_cast<char>(0x61),
+                            static_cast<char>(0xd5),
+                            static_cast<char>(0x5e),
+                            0x1d,
+                            static_cast<char>(0x01),
+                            static_cast<char>(0x1f),
+                            0x19,
+                            static_cast<char>(0xea),
+                            0x71,
+                            static_cast<char>(0xd5),
+                            static_cast<char>(0xd2),
+                            static_cast<char>(0xa7)};
+  const char connection_id[] =  // test vector from both docs
+      {static_cast<char>(0x83),
+       static_cast<char>(0x94),
+       static_cast<char>(0xc8),
+       static_cast<char>(0xf0),
+       0x3e,
+       0x51,
+       0x57,
+       0x08};
+  const QuicConnectionId cid(connection_id, sizeof(connection_id));
+  const char* key_str;
+  size_t key_size;
+  for (const ParsedQuicVersion& version : AllSupportedVersionsWithTls()) {
+    if (version == ParsedQuicVersion::Draft29()) {
+      key_str = draft_29_key;
+      key_size = sizeof(draft_29_key);
+    } else if (version == ParsedQuicVersion::RFCv1()) {
+      key_str = v1_key;
+      key_size = sizeof(v1_key);
+    } else {  // draft-ietf-quic-v2-01
+      key_str = v2_08_key;
+      key_size = sizeof(v2_08_key);
+    }
+    const absl::string_view expected_key{key_str, key_size};
+
+    CrypterPair crypters;
+    CryptoUtils::CreateInitialObfuscators(Perspective::IS_SERVER, version, cid,
+                                          &crypters);
+    EXPECT_EQ(crypters.encrypter->GetKey(), expected_key);
+  }
+}
+
+TEST_F(CryptoUtilsTest, ValidateCryptoLabelsHeap) {
+  SetQuicReloadableFlag(quic_heapless_obfuscator, false);
   // if the number of HTTP/3 QUIC versions has changed, we need to change the
   // expected_keys hardcoded into this test. Regrettably, this is not a
   // compile-time constant.
diff --git a/quiche/quic/core/http/quic_spdy_session.cc b/quiche/quic/core/http/quic_spdy_session.cc
index de560d4..d322e27 100644
--- a/quiche/quic/core/http/quic_spdy_session.cc
+++ b/quiche/quic/core/http/quic_spdy_session.cc
@@ -19,6 +19,7 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "quiche/http2/core/http2_frame_decoder_adapter.h"
+#include "quiche/quic/core/crypto/crypto_protocol.h"
 #include "quiche/quic/core/http/http_constants.h"
 #include "quiche/quic/core/http/http_decoder.h"
 #include "quiche/quic/core/http/http_frames.h"
diff --git a/quiche/quic/core/quic_session.cc b/quiche/quic/core/quic_session.cc
index 6e79b25..be6aa0a 100644
--- a/quiche/quic/core/quic_session.cc
+++ b/quiche/quic/core/quic_session.cc
@@ -18,6 +18,7 @@
 #include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/crypto_protocol.h"
 #include "quiche/quic/core/frames/quic_ack_frequency_frame.h"
 #include "quiche/quic/core/frames/quic_reset_stream_at_frame.h"
 #include "quiche/quic/core/frames/quic_window_update_frame.h"
@@ -38,7 +39,6 @@
 #include "quiche/quic/platform/api/quic_flags.h"
 #include "quiche/quic/platform/api/quic_logging.h"
 #include "quiche/quic/platform/api/quic_server_stats.h"
-#include "quiche/quic/platform/api/quic_stack_trace.h"
 #include "quiche/common/platform/api/quiche_logging.h"
 #include "quiche/common/quiche_callbacks.h"
 #include "quiche/common/quiche_text_utils.h"