Reduce QUIC load balancer copies by passing the result as an argument.

Code is not in production.

Performance numbers (Unenc/3 pass/1 pass, ns)
Tip of tree: 20.2 / 103 / 33.2
This CL: 21.4 / 101/ 26.3

PiperOrigin-RevId: 599890068
diff --git a/quiche/quic/load_balancer/load_balancer_config.cc b/quiche/quic/load_balancer/load_balancer_config.cc
index 2373e6c..b4b989e 100644
--- a/quiche/quic/load_balancer/load_balancer_config.cc
+++ b/quiche/quic/load_balancer/load_balancer_config.cc
@@ -89,10 +89,13 @@
              : std::optional<LoadBalancerConfig>();
 }
 
-LoadBalancerServerId LoadBalancerConfig::FourPassDecrypt(
-    absl::Span<const uint8_t> ciphertext) const {
+bool LoadBalancerConfig::FourPassDecrypt(
+    absl::Span<const uint8_t> ciphertext,
+    LoadBalancerServerId& server_id) const {
+  QUIC_BUG_IF(quic_bug_599862571_02, ciphertext.size() < plaintext_len())
+      << "Called FourPassDecrypt with a short Connection ID";
   if (!key_.has_value()) {
-    return LoadBalancerServerId();
+    return false;
   }
   // Do 3 or 4 passes. Only 3 are necessary if the server_id is short enough
   // to fit in the first half of the connection ID (the decoder doesn't need
@@ -112,18 +115,22 @@
   if (server_id_len_ < half_len ||
       (server_id_len_ == half_len && !is_length_odd)) {
     // There is no half-byte to handle
-    return LoadBalancerServerId(absl::Span<uint8_t>(&left[2], server_id_len_));
+    memcpy(server_id.mutable_data(), &left[2], server_id_len_);
+    return true;
   }
   if (is_length_odd) {
     right[2] |= left[half_len-- + 1];  // Combine the halves of the odd byte.
   }
-  return LoadBalancerServerId(
-      absl::Span<uint8_t>(&left[2], half_len),
-      absl::Span<uint8_t>(&right[2], server_id_len_ - half_len));
+  memcpy(server_id.mutable_data(), &left[2], half_len);
+  memcpy(server_id.mutable_data() + half_len, &right[2],
+         server_id_len_ - half_len);
+  return true;
 }
 
 QuicConnectionId LoadBalancerConfig::FourPassEncrypt(
     absl::Span<uint8_t> plaintext) const {
+  QUIC_BUG_IF(quic_bug_599862571_03, plaintext.size() < total_len())
+      << "Called FourPassEncrypt with a short Connection ID";
   if (!key_.has_value()) {
     return QuicConnectionId();
   }
diff --git a/quiche/quic/load_balancer/load_balancer_config.h b/quiche/quic/load_balancer/load_balancer_config.h
index 9ad396c..cb4521c 100644
--- a/quiche/quic/load_balancer/load_balancer_config.h
+++ b/quiche/quic/load_balancer/load_balancer_config.h
@@ -31,8 +31,6 @@
 // find the maximum number of configs.
 inline constexpr uint8_t kNumLoadBalancerConfigs = (1 << kConfigIdBits) - 1;
 inline constexpr uint8_t kLoadBalancerKeyLen = 16;
-// Regardless of key length, the AES block size is always 16 Bytes.
-inline constexpr uint8_t kLoadBalancerBlockSize = 16;
 // The spec says nonces can be 18 bytes, but 16 lets it be a uint128.
 inline constexpr uint8_t kLoadBalancerMaxNonceLen = 16;
 inline constexpr uint8_t kLoadBalancerMinNonceLen = 4;
@@ -64,8 +62,13 @@
 
   // Returns an invalid Server ID if ciphertext is too small, or needed keys are
   // missing. |ciphertext| contains the full connection ID minus the first byte.
-  LoadBalancerServerId FourPassDecrypt(
-      absl::Span<const uint8_t> ciphertext) const;
+  //
+  // IMPORTANT: The decoder data path is likely the most performance-sensitive
+  // part of the load balancer design, and this code has been carefully
+  // optimized for performance. Please do not make changes without running the
+  // benchmark tests to ensure there is no regression.
+  bool FourPassDecrypt(absl::Span<const uint8_t> ciphertext,
+                       LoadBalancerServerId& server_id) const;
   // Returns an empty connection ID if the plaintext is too small, or needed
   // keys are missing. |plaintext| contains the full unencrypted connection ID,
   // including the first byte.
diff --git a/quiche/quic/load_balancer/load_balancer_config_test.cc b/quiche/quic/load_balancer/load_balancer_config_test.cc
index fd40412..0e1b2f6 100644
--- a/quiche/quic/load_balancer/load_balancer_config_test.cc
+++ b/quiche/quic/load_balancer/load_balancer_config_test.cc
@@ -193,15 +193,16 @@
 // LoadBalancerDecoderTest, respectively.
 
 TEST_F(LoadBalancerConfigTest, InvalidBlockEncryption) {
-  uint8_t pt[kLoadBalancerBlockSize], ct[kLoadBalancerBlockSize];
+  uint8_t pt[kLoadBalancerBlockSize + 1], ct[kLoadBalancerBlockSize];
   auto pt_config = LoadBalancerConfig::CreateUnencrypted(0, 8, 8);
   ASSERT_TRUE(pt_config.has_value());
   EXPECT_FALSE(pt_config->BlockEncrypt(pt, ct));
   EXPECT_FALSE(pt_config->BlockDecrypt(ct, pt));
   EXPECT_TRUE(pt_config->FourPassEncrypt(absl::Span<uint8_t>(pt, sizeof(pt)))
                   .IsEmpty());
-  EXPECT_FALSE(pt_config->FourPassDecrypt(absl::Span<uint8_t>(pt, sizeof(pt)))
-                   .IsValid());
+  LoadBalancerServerId answer;
+  EXPECT_FALSE(pt_config->FourPassDecrypt(
+      absl::Span<uint8_t>(pt, sizeof(pt) - 1), answer));
   auto small_cid_config =
       LoadBalancerConfig::Create(0, 3, 4, absl::string_view(raw_key, 16));
   ASSERT_TRUE(small_cid_config.has_value());
@@ -247,6 +248,20 @@
   EXPECT_EQ(memcmp(result, ctext, sizeof(ctext)), 0);
 }
 
+TEST_F(LoadBalancerConfigTest, FourPassInputTooShort) {
+  auto config =
+      LoadBalancerConfig::Create(0, 3, 4, absl::string_view(raw_key, 16));
+  uint8_t input[] = {0x0d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9};
+  LoadBalancerServerId answer;
+  EXPECT_QUIC_BUG(
+      config->FourPassDecrypt(
+          absl::Span<const uint8_t>(input, sizeof(input) - 1), answer),
+      "Called FourPassDecrypt with a short Connection ID");
+  EXPECT_QUIC_BUG(
+      config->FourPassEncrypt(absl::Span<uint8_t>(input, sizeof(input))),
+      "Called FourPassEncrypt with a short Connection ID");
+}
+
 }  // namespace
 
 }  // namespace test
diff --git a/quiche/quic/load_balancer/load_balancer_decoder.cc b/quiche/quic/load_balancer/load_balancer_decoder.cc
index 0bbb804..05d8696 100644
--- a/quiche/quic/load_balancer/load_balancer_decoder.cc
+++ b/quiche/quic/load_balancer/load_balancer_decoder.cc
@@ -36,36 +36,34 @@
 
 // This is the core logic to extract a server ID given a valid config and
 // connection ID of sufficient length.
-LoadBalancerServerId LoadBalancerDecoder::GetServerId(
-    const QuicConnectionId& connection_id) const {
+bool LoadBalancerDecoder::GetServerId(const QuicConnectionId& connection_id,
+                                      LoadBalancerServerId& server_id) const {
   std::optional<uint8_t> config_id = GetConfigId(connection_id);
   if (!config_id.has_value()) {
-    return LoadBalancerServerId();
+    return false;
   }
   std::optional<LoadBalancerConfig> config = config_[*config_id];
   if (!config.has_value()) {
-    return LoadBalancerServerId();
+    return false;
   }
   // Benchmark tests show that minimizing the computation inside
   // LoadBalancerConfig saves CPU cycles.
   if (connection_id.length() < config->total_len()) {
-    return LoadBalancerServerId();
+    return false;
   }
   const uint8_t* data =
       reinterpret_cast<const uint8_t*>(connection_id.data()) + 1;
   uint8_t server_id_len = config->server_id_len();
+  server_id.set_length(server_id_len);
   if (!config->IsEncrypted()) {
-    return LoadBalancerServerId(absl::Span<const uint8_t>(data, server_id_len));
+    memcpy(server_id.mutable_data(), connection_id.data() + 1, server_id_len);
+    return true;
   }
   if (config->plaintext_len() == kLoadBalancerBlockSize) {
-    uint8_t scratch[kLoadBalancerBlockSize];
-    if (!config->BlockDecrypt(data, scratch)) {
-      return LoadBalancerServerId();
-    }
-    return LoadBalancerServerId(absl::Span<uint8_t>(scratch, server_id_len));
+    return config->BlockDecrypt(data, server_id.mutable_data());
   }
   return config->FourPassDecrypt(
-      absl::MakeConstSpan(data, connection_id.length() - 1));
+      absl::MakeConstSpan(data, connection_id.length() - 1), server_id);
 }
 
 std::optional<uint8_t> LoadBalancerDecoder::GetConfigId(
diff --git a/quiche/quic/load_balancer/load_balancer_decoder.h b/quiche/quic/load_balancer/load_balancer_decoder.h
index 21eb8b7..a352711 100644
--- a/quiche/quic/load_balancer/load_balancer_decoder.h
+++ b/quiche/quic/load_balancer/load_balancer_decoder.h
@@ -8,14 +8,21 @@
 #include <cstdint>
 #include <optional>
 
+#include "absl/base/attributes.h"
 #include "quiche/quic/core/quic_connection_id.h"
 #include "quiche/quic/load_balancer/load_balancer_config.h"
 #include "quiche/quic/load_balancer/load_balancer_server_id.h"
+#include "quiche/quic/platform/api/quic_export.h"
 
 namespace quic {
 
 // Manages QUIC-LB configurations to extract a server ID from a properly
 // encoded connection ID, usually on behalf of a load balancer.
+//
+// IMPORTANT: The decoder data path is likely the most performance-sensitive
+// part of the load balancer design, and this code has been carefully
+// optimized for performance. Please do not make changes without running the
+// benchmark tests to ensure there is no regression.
 class QUIC_EXPORT_PRIVATE LoadBalancerDecoder {
  public:
   // Returns false if the config_id codepoint is already occupied.
@@ -36,11 +43,13 @@
     return &*config_[config_id];
   }
 
-  // Extract a server ID from |connection_id|. If there is no config for the
-  // codepoint, |connection_id| is too short, or there's a decrypt error,
-  // returns empty. Will accept |connection_id| that is longer than necessary
-  // without error.
-  LoadBalancerServerId GetServerId(const QuicConnectionId& connection_id) const;
+  // Extract a server ID from |connection_id| and write it into |server_id|. If
+  // there is no config for the codepoint, |connection_id| is too short, or
+  // there's a decrypt error, returns false. Will accept |connection_id| that is
+  // longer than necessary without error. If GetServerId() returns false, there
+  // are no guarantees about the properties of |server_id|.
+  ABSL_MUST_USE_RESULT bool GetServerId(const QuicConnectionId& connection_id,
+                                        LoadBalancerServerId& server_id) const;
 
   // Returns the config ID stored in the first two bits of |connection_id|, or
   // empty if |connection_id| is empty, or the first two bits of the first byte
diff --git a/quiche/quic/load_balancer/load_balancer_decoder_test.cc b/quiche/quic/load_balancer/load_balancer_decoder_test.cc
index cc86d25..6e8a399 100644
--- a/quiche/quic/load_balancer/load_balancer_decoder_test.cc
+++ b/quiche/quic/load_balancer/load_balancer_decoder_test.cc
@@ -56,8 +56,10 @@
       }};
   for (const auto& test : test_vectors) {
     LoadBalancerDecoder decoder;
+    LoadBalancerServerId answer;
     EXPECT_TRUE(decoder.AddConfig(test.config));
-    EXPECT_EQ(decoder.GetServerId(test.connection_id), test.server_id);
+    EXPECT_TRUE(decoder.GetServerId(test.connection_id, answer));
+    EXPECT_EQ(answer, test.server_id);
   }
 }
 
@@ -97,21 +99,12 @@
   for (const auto& test : test_vectors) {
     LoadBalancerDecoder decoder;
     EXPECT_TRUE(decoder.AddConfig(test.config));
-    EXPECT_EQ(decoder.GetServerId(test.connection_id), test.server_id);
+    LoadBalancerServerId answer;
+    EXPECT_TRUE(decoder.GetServerId(test.connection_id, answer));
+    EXPECT_EQ(answer, test.server_id);
   }
 }
 
-TEST_F(LoadBalancerDecoderTest, NoServerIdEntry) {
-  LoadBalancerServerId server_id({0x01, 0x02, 0x03});
-  EXPECT_TRUE(server_id.IsValid());
-  LoadBalancerDecoder decoder;
-  EXPECT_TRUE(
-      decoder.AddConfig(*LoadBalancerConfig::CreateUnencrypted(0, 3, 4)));
-  QuicConnectionId no_server_id_entry(
-      {0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08});
-  EXPECT_TRUE(decoder.GetServerId(no_server_id_entry).IsValid());
-}
-
 TEST_F(LoadBalancerDecoderTest, InvalidConfigId) {
   LoadBalancerServerId server_id({0x01, 0x02, 0x03});
   EXPECT_TRUE(server_id.IsValid());
@@ -120,10 +113,10 @@
       decoder.AddConfig(*LoadBalancerConfig::CreateUnencrypted(1, 3, 4)));
   QuicConnectionId wrong_config_id(
       {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07});
-  EXPECT_FALSE(decoder
-                   .GetServerId(QuicConnectionId(
-                       {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}))
-                   .IsValid());
+  LoadBalancerServerId answer;
+  EXPECT_FALSE(decoder.GetServerId(
+      QuicConnectionId({0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}),
+      answer));
 }
 
 TEST_F(LoadBalancerDecoderTest, UnroutableCodepoint) {
@@ -132,10 +125,10 @@
   LoadBalancerDecoder decoder;
   EXPECT_TRUE(
       decoder.AddConfig(*LoadBalancerConfig::CreateUnencrypted(1, 3, 4)));
-  EXPECT_FALSE(decoder
-                   .GetServerId(QuicConnectionId(
-                       {0xe0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}))
-                   .IsValid());
+  LoadBalancerServerId answer;
+  EXPECT_FALSE(decoder.GetServerId(
+      QuicConnectionId({0xe0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}),
+      answer));
 }
 
 TEST_F(LoadBalancerDecoderTest, UnroutableCodepointAnyLength) {
@@ -144,7 +137,8 @@
   LoadBalancerDecoder decoder;
   EXPECT_TRUE(
       decoder.AddConfig(*LoadBalancerConfig::CreateUnencrypted(1, 3, 4)));
-  EXPECT_FALSE(decoder.GetServerId(QuicConnectionId({0xff})).IsValid());
+  LoadBalancerServerId answer;
+  EXPECT_FALSE(decoder.GetServerId(QuicConnectionId({0xff}), answer));
 }
 
 TEST_F(LoadBalancerDecoderTest, ConnectionIdTooShort) {
@@ -153,10 +147,9 @@
   LoadBalancerDecoder decoder;
   EXPECT_TRUE(
       decoder.AddConfig(*LoadBalancerConfig::CreateUnencrypted(0, 3, 4)));
-  EXPECT_FALSE(decoder
-                   .GetServerId(QuicConnectionId(
-                       {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}))
-                   .IsValid());
+  LoadBalancerServerId answer;
+  EXPECT_FALSE(decoder.GetServerId(
+      QuicConnectionId({0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}), answer));
 }
 
 TEST_F(LoadBalancerDecoderTest, ConnectionIdTooLongIsOK) {
@@ -164,10 +157,11 @@
   LoadBalancerDecoder decoder;
   EXPECT_TRUE(
       decoder.AddConfig(*LoadBalancerConfig::CreateUnencrypted(0, 3, 4)));
-  auto server_id_result = decoder.GetServerId(
-      QuicConnectionId({0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}));
-  EXPECT_TRUE(server_id_result.IsValid());
-  EXPECT_EQ(server_id_result, server_id);
+  LoadBalancerServerId answer;
+  EXPECT_TRUE(decoder.GetServerId(
+      QuicConnectionId({0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}),
+      answer));
+  EXPECT_EQ(answer, server_id);
 }
 
 TEST_F(LoadBalancerDecoderTest, DeleteConfigBadId) {
@@ -176,20 +170,20 @@
   decoder.DeleteConfig(0);
   EXPECT_QUIC_BUG(decoder.DeleteConfig(7),
                   "Decoder deleting config with invalid config_id 7");
-  EXPECT_TRUE(decoder
-                  .GetServerId(QuicConnectionId(
-                      {0x40, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}))
-                  .IsValid());
+  LoadBalancerServerId answer;
+  EXPECT_TRUE(decoder.GetServerId(
+      QuicConnectionId({0x40, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}),
+      answer));
 }
 
 TEST_F(LoadBalancerDecoderTest, DeleteConfigGoodId) {
   LoadBalancerDecoder decoder;
   decoder.AddConfig(*LoadBalancerConfig::CreateUnencrypted(2, 3, 4));
   decoder.DeleteConfig(2);
-  EXPECT_FALSE(decoder
-                   .GetServerId(QuicConnectionId(
-                       {0x40, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}))
-                   .IsValid());
+  LoadBalancerServerId answer;
+  EXPECT_FALSE(decoder.GetServerId(
+      QuicConnectionId({0x40, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}),
+      answer));
 }
 
 // Create two server IDs and make sure the decoder decodes the correct one.
@@ -200,12 +194,15 @@
   LoadBalancerDecoder decoder;
   EXPECT_TRUE(
       decoder.AddConfig(*LoadBalancerConfig::CreateUnencrypted(0, 3, 4)));
-  EXPECT_EQ(decoder.GetServerId(QuicConnectionId(
-                {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07})),
-            server_id1);
-  EXPECT_EQ(decoder.GetServerId(QuicConnectionId(
-                {0x00, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a})),
-            server_id2);
+  LoadBalancerServerId answer;
+  EXPECT_TRUE(decoder.GetServerId(
+      QuicConnectionId({0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}),
+      answer));
+  EXPECT_EQ(answer, server_id1);
+  EXPECT_TRUE(decoder.GetServerId(
+      QuicConnectionId({0x00, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}),
+      answer));
+  EXPECT_EQ(answer, server_id2);
 }
 
 TEST_F(LoadBalancerDecoderTest, GetConfigId) {
@@ -250,8 +247,9 @@
       *LoadBalancerConfig::Create(0, 8, 8, absl::string_view(kRawKey, 16)));
   LoadBalancerServerId original_server_id(absl::Span<uint8_t>(&ptext[1], 8));
   QuicConnectionId cid(absl::Span<uint8_t>(ctext, sizeof(ctext)));
-  LoadBalancerServerId server_id = decoder.GetServerId(cid);
-  EXPECT_EQ(server_id, original_server_id);
+  LoadBalancerServerId answer;
+  EXPECT_TRUE(decoder.GetServerId(cid, answer));
+  EXPECT_EQ(answer, original_server_id);
 }
 
 }  // namespace
diff --git a/quiche/quic/load_balancer/load_balancer_server_id.cc b/quiche/quic/load_balancer/load_balancer_server_id.cc
index 9a4685c..4861adb 100644
--- a/quiche/quic/load_balancer/load_balancer_server_id.cc
+++ b/quiche/quic/load_balancer/load_balancer_server_id.cc
@@ -17,17 +17,11 @@
 namespace quic {
 
 LoadBalancerServerId::LoadBalancerServerId(absl::string_view data)
-    : LoadBalancerServerId(
-          absl::MakeSpan(reinterpret_cast<const uint8_t*>(data.data()),
-                         data.length()),
-          absl::Span<const uint8_t>()) {}
+    : LoadBalancerServerId(absl::MakeSpan(
+          reinterpret_cast<const uint8_t*>(data.data()), data.length())) {}
 
 LoadBalancerServerId::LoadBalancerServerId(absl::Span<const uint8_t> data)
-    : LoadBalancerServerId(data, absl::Span<const uint8_t>()) {}
-
-LoadBalancerServerId::LoadBalancerServerId(absl::Span<const uint8_t> data1,
-                                           absl::Span<const uint8_t> data2)
-    : length_(data1.length() + data2.length()) {
+    : length_(data.length()) {
   if (length_ == 0 || length_ > kLoadBalancerMaxServerIdLen) {
     QUIC_BUG(quic_bug_433312504_02)
         << "Attempted to create LoadBalancerServerId with length "
@@ -35,11 +29,15 @@
     length_ = 0;
     return;
   }
-  memcpy(data_.data(), data1.data(), data1.length());
-  if (data2.empty()) {
-    return;
-  }
-  memcpy(data_.data() + data1.length(), data2.data(), data2.length());
+  memcpy(data_.data(), data.data(), data.length());
+}
+
+void LoadBalancerServerId::set_length(uint8_t length) {
+  QUIC_BUG_IF(quic_bug_599862571_01,
+              length == 0 || length > kLoadBalancerMaxServerIdLen)
+      << "Attempted to set LoadBalancerServerId length to "
+      << static_cast<int>(length);
+  length_ = length;
 }
 
 std::string LoadBalancerServerId::ToString() const {
diff --git a/quiche/quic/load_balancer/load_balancer_server_id.h b/quiche/quic/load_balancer/load_balancer_server_id.h
index d60d308..713f3e2 100644
--- a/quiche/quic/load_balancer/load_balancer_server_id.h
+++ b/quiche/quic/load_balancer/load_balancer_server_id.h
@@ -17,6 +17,10 @@
 
 // The maximum number of bytes in a LoadBalancerServerId.
 inline constexpr uint8_t kLoadBalancerMaxServerIdLen = 15;
+// Regardless of key length, the AES block size is always 16 Bytes.
+inline constexpr uint8_t kLoadBalancerBlockSize = 16;
+static_assert(kLoadBalancerMaxServerIdLen <= kLoadBalancerBlockSize,
+              "LoadBalancerServerId array not large enough to hold Server ID");
 
 // LoadBalancerServerId is the globally understood identifier for a given pool
 // member. It is unique to any given QUIC-LB configuration. See
@@ -33,10 +37,6 @@
   // Copies all the bytes from |data| into a new LoadBalancerServerId.
   explicit LoadBalancerServerId(absl::Span<const uint8_t> data);
   explicit LoadBalancerServerId(absl::string_view data);
-  // Concatenates |data1| and |data2| into a single LoadBalancerServerId. This
-  // is useful to reduce copying for certain decoder configurations.
-  explicit LoadBalancerServerId(absl::Span<const uint8_t> data1,
-                                absl::Span<const uint8_t> data2);
 
   // Server IDs are opaque bytes, but defining these operators allows us to sort
   // them into a tree and define ranges.
@@ -57,7 +57,10 @@
   absl::Span<const uint8_t> data() const {
     return absl::MakeConstSpan(data_.data(), length_);
   }
+  uint8_t* mutable_data() { return data_.data(); }
+
   uint8_t length() const { return length_; }
+  void set_length(uint8_t length);
 
   // Returns the server ID in hex format.
   std::string ToString() const;
@@ -66,7 +69,9 @@
   bool IsValid() { return length_ != 0; }
 
  private:
-  std::array<uint8_t, kLoadBalancerMaxServerIdLen> data_;
+  // Make the array large enough to hold an entire decrypt result, to save a
+  // copy from the decrypt result into LoadBalancerServerId.
+  std::array<uint8_t, kLoadBalancerBlockSize> data_;
   uint8_t length_;
 };
 
diff --git a/quiche/quic/load_balancer/load_balancer_server_id_test.cc b/quiche/quic/load_balancer/load_balancer_server_id_test.cc
index 08b5e6d..6b3bc52 100644
--- a/quiche/quic/load_balancer/load_balancer_server_id_test.cc
+++ b/quiche/quic/load_balancer/load_balancer_server_id_test.cc
@@ -30,27 +30,11 @@
                                    absl::Span<const uint8_t>(kRawServerId, 16))
                                    .IsValid()),
                   "Attempted to create LoadBalancerServerId with length 16");
-  EXPECT_QUIC_BUG(EXPECT_FALSE(LoadBalancerServerId(
-                                   absl::Span<const uint8_t>(kRawServerId, 9),
-                                   absl::Span<const uint8_t>(kRawServerId, 7))
-                                   .IsValid()),
-                  "Attempted to create LoadBalancerServerId with length 16");
   EXPECT_QUIC_BUG(
       EXPECT_FALSE(LoadBalancerServerId(absl::Span<const uint8_t>()).IsValid()),
       "Attempted to create LoadBalancerServerId with length 0");
 }
 
-TEST_F(LoadBalancerServerIdTest, TwoPartConstructor) {
-  LoadBalancerServerId server_id1(absl::Span<const uint8_t>(kRawServerId, 15));
-  ASSERT_TRUE(server_id1.IsValid());
-  LoadBalancerServerId server_id2(
-      absl::Span<const uint8_t>(kRawServerId, 8),
-      absl::Span<const uint8_t>(&kRawServerId[8], 7));
-  ASSERT_TRUE(server_id2.IsValid());
-  EXPECT_TRUE(server_id1 == server_id2);
-  ;
-}
-
 TEST_F(LoadBalancerServerIdTest, CompareIdenticalExceptLength) {
   LoadBalancerServerId server_id(absl::Span<const uint8_t>(kRawServerId, 15));
   ASSERT_TRUE(server_id.IsValid());
@@ -111,6 +95,18 @@
   }));
 }
 
+TEST_F(LoadBalancerServerIdTest, SetLengthInvalid) {
+  LoadBalancerServerId server_id;
+  EXPECT_QUIC_BUG(server_id.set_length(16),
+                  "Attempted to set LoadBalancerServerId length to 16");
+  EXPECT_QUIC_BUG(server_id.set_length(0),
+                  "Attempted to set LoadBalancerServerId length to 0");
+  server_id.set_length(1);
+  EXPECT_EQ(server_id.length(), 1);
+  server_id.set_length(15);
+  EXPECT_EQ(server_id.length(), 15);
+}
+
 }  // namespace
 
 }  // namespace test