Implement BHTTP Indeterminate-Length request encoder.

This encoder provides methods to encode an Indeterminate-Length Binary HTTP request section by section, ensuring the correct order of control data, headers, body chunks, and trailers.

PiperOrigin-RevId: 834850030
diff --git a/quiche/binary_http/binary_http_message.cc b/quiche/binary_http/binary_http_message.cc
index 6fe9542..37647c7 100644
--- a/quiche/binary_http/binary_http_message.cc
+++ b/quiche/binary_http/binary_http_message.cc
@@ -201,6 +201,44 @@
 uint64_t StringPieceVarInt62Len(absl::string_view s) {
   return quiche::QuicheDataWriter::GetVarInt62Len(s.length()) + s.length();
 }
+
+absl::StatusOr<std::string> EncodeBodyChunksImpl(
+    absl::Span<absl::string_view> body_chunks, bool body_chunks_done) {
+  uint64_t total_length = 0;
+  for (const auto& body_chunk : body_chunks) {
+    uint8_t body_chunk_var_int_length =
+        QuicheDataWriter::GetVarInt62Len(body_chunk.size());
+    if (body_chunk_var_int_length == 0) {
+      return absl::InvalidArgumentError(
+          "Body chunk size exceeds maximum length.");
+    }
+    total_length += body_chunk_var_int_length + body_chunk.size();
+  }
+  if (body_chunks_done) {
+    total_length +=
+        quiche::QuicheDataWriter::GetVarInt62Len(kContentTerminator);
+  }
+
+  std::string data(total_length, '\0');
+  QuicheDataWriter writer(total_length, data.data());
+
+  for (const auto& body_chunk : body_chunks) {
+    if (!writer.WriteStringPieceVarInt62(body_chunk)) {
+      return absl::InternalError("Failed to write body chunk.");
+    }
+  }
+  if (body_chunks_done) {
+    if (!writer.WriteVarInt62(kContentTerminator)) {
+      return absl::InternalError("Failed to write content terminator.");
+    }
+  }
+
+  if (writer.remaining() != 0) {
+    return absl::InternalError("Failed to write all data.");
+  }
+  return data;
+}
+
 }  // namespace
 
 absl::StatusOr<uint64_t> GetFieldSectionLength(
@@ -692,6 +730,122 @@
   return status;
 }
 
+absl::StatusOr<std::string>
+BinaryHttpRequest::IndeterminateLengthEncoder::EncodeFieldSection(
+    absl::Span<FieldView> fields) {
+  absl::StatusOr<uint64_t> field_section_length = GetFieldSectionLength(fields);
+  if (!field_section_length.ok()) {
+    return field_section_length.status();
+  }
+
+  std::string data(*field_section_length, '\0');
+  QuicheDataWriter writer(*field_section_length, data.data());
+
+  absl::Status fields_encoded = EncodeFields(fields, writer);
+  if (!fields_encoded.ok()) {
+    return fields_encoded;
+  }
+  if (writer.remaining() != 0) {
+    return absl::InternalError("Failed to write all fields.");
+  }
+  return data;
+}
+
+absl::StatusOr<std::string>
+BinaryHttpRequest::IndeterminateLengthEncoder::EncodeControlData(
+    const ControlData& control_data) {
+  if (current_section_ != IndeterminateLengthMessageSection::kControlData) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return absl::InvalidArgumentError(
+        "EncodeControlData called in wrong section.");
+  }
+
+  uint64_t total_length = quiche::QuicheDataWriter::GetVarInt62Len(
+                              kIndeterminateLengthRequestFraming) +
+                          StringPieceVarInt62Len(control_data.method) +
+                          StringPieceVarInt62Len(control_data.scheme) +
+                          StringPieceVarInt62Len(control_data.authority) +
+                          StringPieceVarInt62Len(control_data.path);
+
+  std::string data(total_length, '\0');
+  QuicheDataWriter writer(total_length, data.data());
+  if (!writer.WriteVarInt62(kIndeterminateLengthRequestFraming)) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return absl::InternalError("Failed to write framing indicator.");
+  }
+  if (!writer.WriteStringPieceVarInt62(control_data.method)) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return absl::InternalError("Failed to write method.");
+  }
+  if (!writer.WriteStringPieceVarInt62(control_data.scheme)) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return absl::InternalError("Failed to write scheme.");
+  }
+  if (!writer.WriteStringPieceVarInt62(control_data.authority)) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return absl::InternalError("Failed to write authority.");
+  }
+  if (!writer.WriteStringPieceVarInt62(control_data.path)) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return absl::InternalError("Failed to write path.");
+  }
+  if (writer.remaining() != 0) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return absl::InternalError("Failed to write all control data.");
+  }
+
+  current_section_ = IndeterminateLengthMessageSection::kHeader;
+  return data;
+}
+
+absl::StatusOr<std::string>
+BinaryHttpRequest::IndeterminateLengthEncoder::EncodeHeaders(
+    absl::Span<FieldView> headers) {
+  if (current_section_ != IndeterminateLengthMessageSection::kHeader) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return absl::InvalidArgumentError("EncodeHeaders called in wrong section.");
+  }
+  absl::StatusOr<std::string> data = EncodeFieldSection(headers);
+  if (!data.ok()) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return data;
+  }
+  current_section_ = IndeterminateLengthMessageSection::kBody;
+  return data;
+}
+
+absl::StatusOr<std::string>
+BinaryHttpRequest::IndeterminateLengthEncoder::EncodeBodyChunks(
+    absl::Span<absl::string_view> body_chunks, bool body_chunks_done) {
+  if (current_section_ != IndeterminateLengthMessageSection::kBody) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return absl::InvalidArgumentError(
+        "EncodeBodyChunks called in wrong section.");
+  }
+  absl::StatusOr<std::string> result =
+      EncodeBodyChunksImpl(body_chunks, body_chunks_done);
+  if (!result.ok()) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return result.status();
+  }
+  if (body_chunks_done) {
+    current_section_ = IndeterminateLengthMessageSection::kTrailer;
+  }
+  return result;
+}
+
+absl::StatusOr<std::string>
+BinaryHttpRequest::IndeterminateLengthEncoder::EncodeTrailers(
+    absl::Span<FieldView> trailers) {
+  if (current_section_ != IndeterminateLengthMessageSection::kTrailer) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return absl::InvalidArgumentError(
+        "EncodeTrailers called in wrong section.");
+  }
+  current_section_ = IndeterminateLengthMessageSection::kEnd;
+  return EncodeFieldSection(trailers);
+}
+
 absl::StatusOr<BinaryHttpResponse> BinaryHttpResponse::Create(
     absl::string_view data) {
   quiche::QuicheDataReader reader(data);
@@ -819,44 +973,16 @@
         absl::StrCat("EncodeBodyChunks called in incorrect section: ",
                      GetMessageSectionString(current_section_)));
   }
-  uint64_t total_length = 0;
-  for (const auto& body_chunk : body_chunks) {
-    uint8_t body_chunk_var_int_length =
-        QuicheDataWriter::GetVarInt62Len(body_chunk.size());
-    if (body_chunk_var_int_length == 0) {
-      current_section_ = IndeterminateLengthMessageSection::kEnd;
-      return absl::InvalidArgumentError(
-          "Body chunk size exceeds maximum length.");
-    }
-    total_length += body_chunk_var_int_length + body_chunk.size();
+  absl::StatusOr<std::string> result =
+      EncodeBodyChunksImpl(body_chunks, body_chunks_done);
+  if (!result.ok()) {
+    current_section_ = IndeterminateLengthMessageSection::kEnd;
+    return result.status();
   }
   if (body_chunks_done) {
-    total_length +=
-        quiche::QuicheDataWriter::GetVarInt62Len(kContentTerminator);
-  }
-
-  std::string data(total_length, '\0');
-  QuicheDataWriter writer(total_length, data.data());
-
-  for (const auto& body_chunk : body_chunks) {
-    if (!writer.WriteStringPieceVarInt62(body_chunk)) {
-      current_section_ = IndeterminateLengthMessageSection::kEnd;
-      return absl::InternalError("Failed to write body chunk.");
-    }
-  }
-  if (body_chunks_done) {
-    if (!writer.WriteVarInt62(kContentTerminator)) {
-      current_section_ = IndeterminateLengthMessageSection::kEnd;
-      return absl::InternalError("Failed to write content terminator.");
-    }
     current_section_ = IndeterminateLengthMessageSection::kTrailer;
   }
-
-  if (writer.remaining() != 0) {
-    current_section_ = IndeterminateLengthMessageSection::kEnd;
-    return absl::InternalError("Failed to write all data.");
-  }
-  return data;
+  return result;
 }
 
 absl::StatusOr<std::string>
diff --git a/quiche/binary_http/binary_http_message.h b/quiche/binary_http/binary_http_message.h
index ecadfb7..be23ee0 100644
--- a/quiche/binary_http/binary_http_message.h
+++ b/quiche/binary_http/binary_http_message.h
@@ -186,6 +186,12 @@
   // its corresponding section is successfully decoded.
   class QUICHE_EXPORT IndeterminateLengthDecoder;
 
+  // Provides encoding methods for an Indeterminate-Length BHTTP request. The
+  // encoder keeps track of what has been encoded so far to ensure sections are
+  // encoded in the correct order, this means it can only be used for a single
+  // request message.
+  class QUICHE_EXPORT IndeterminateLengthEncoder;
+
  private:
   // The sections of an Indeterminate-Length BHTTP request.
   enum class IndeterminateLengthMessageSection {
@@ -284,6 +290,34 @@
   bool maybe_truncated_ = true;
 };
 
+// Provides encoding methods for an Indeterminate-Length BHTTP request. The
+// encoder keeps track of what has been encoded so far to ensure sections are
+// encoded in the correct order, this means it can only be used for a single
+// request message.
+class QUICHE_EXPORT BinaryHttpRequest::IndeterminateLengthEncoder {
+ public:
+  // Encodes the initial framing indicator and the specified control data.
+  absl::StatusOr<std::string> EncodeControlData(
+      const ControlData& control_data);
+  // Encodes the specified headers and its content terminator.
+  absl::StatusOr<std::string> EncodeHeaders(absl::Span<FieldView> headers);
+  // Encodes the specified body chunks. This can be called multiple times but
+  // it needs to be called exactly once with `body_chunks_done` set to true at
+  // the end to properly set the content terminator. Encoding body chunks is
+  // optional since valid chunked messages can be truncated.
+  absl::StatusOr<std::string> EncodeBodyChunks(
+      absl::Span<absl::string_view> body_chunks, bool body_chunks_done);
+  // Encodes the specified trailers and its content terminator. Encoding
+  // trailers is optional since valid chunked messages can be truncated.
+  absl::StatusOr<std::string> EncodeTrailers(absl::Span<FieldView> trailers);
+
+ private:
+  absl::StatusOr<std::string> EncodeFieldSection(absl::Span<FieldView> fields);
+
+  IndeterminateLengthMessageSection current_section_ =
+      IndeterminateLengthMessageSection::kControlData;
+};
+
 void QUICHE_EXPORT PrintTo(const BinaryHttpRequest& msg, std::ostream* os);
 
 class QUICHE_EXPORT BinaryHttpResponse : public BinaryHttpMessage {
@@ -412,11 +446,14 @@
   // Encodes the specified status code, headers, and its content terminator.
   absl::StatusOr<std::string> EncodeHeaders(uint16_t status_code,
                                             absl::Span<FieldView> headers);
-  // Encodes the specified body chunks. If 'body_chunks_done' is true, the
-  // encoded body chunks are followed by the content terminator.
+  // Encodes the specified body chunks. This can be called multiple times but
+  // it needs to be called exactly once with `body_chunks_done` set to true at
+  // the end to properly set the content terminator. Encoding body chunks is
+  // optional since valid chunked messages can be truncated.
   absl::StatusOr<std::string> EncodeBodyChunks(
       absl::Span<absl::string_view> body_chunks, bool body_chunks_done);
-  // Encodes the specified trailers and its content terminator.
+  // Encodes the specified trailers and its content terminator. Encoding
+  // trailers is optional since valid chunked messages can be truncated.
   absl::StatusOr<std::string> EncodeTrailers(absl::Span<FieldView> trailers);
 
  private:
diff --git a/quiche/binary_http/binary_http_message_test.cc b/quiche/binary_http/binary_http_message_test.cc
index ac8ffb1..489474d 100644
--- a/quiche/binary_http/binary_http_message_test.cc
+++ b/quiche/binary_http/binary_http_message_test.cc
@@ -8,7 +8,6 @@
 #include <utility>
 #include <vector>
 
-#include "absl/container/flat_hash_map.h"
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/escaping.h"
@@ -96,6 +95,8 @@
   MessageData message_data_;
 };
 
+constexpr absl::string_view kFramingIndicator =
+    "02";  // 1-byte framing indicator
 constexpr absl::string_view k2ByteFramingIndicator =
     "4002";  // 2-byte framing indicator
 constexpr absl::string_view k8ByteContentTerminator =
@@ -752,6 +753,231 @@
 
 namespace {
 
+struct RequestIndeterminateLengthEncoderTestData {
+  BinaryHttpRequest::ControlData control_data{"POST", "https", "google.com",
+                                              "/hello"};
+  std::vector<BinaryHttpMessage::FieldView> headers{
+      {"User-Agent", "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3"},
+      {"accept-language", "en, mi"}};
+  std::vector<absl::string_view> body_chunks = {"chunk1", "chunk2", "chunk3"};
+  std::vector<BinaryHttpMessage::FieldView> trailers{{"trailer1", "value1"},
+                                                     {"trailer2", "value2"}};
+};
+
+}  // namespace
+
+TEST(RequestIndeterminateLengthEncoder, FullRequest) {
+  std::string expected;
+  ASSERT_TRUE(absl::HexStringToBytes(
+      absl::StrCat(
+          kFramingIndicator, kIndeterminateLengthEncodedRequestControlData,
+          kIndeterminateLengthEncodedRequestHeaders, kContentTerminator,
+          kIndeterminateLengthEncodedRequestBodyChunks, kContentTerminator,
+          kIndeterminateLengthEncodedRequestTrailers, kContentTerminator),
+      &expected));
+
+  BinaryHttpRequest::IndeterminateLengthEncoder encoder;
+  RequestIndeterminateLengthEncoderTestData test_data;
+  std::string encoded_data;
+
+  absl::StatusOr<std::string> status_or_encoded_data =
+      encoder.EncodeControlData(test_data.control_data);
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+
+  status_or_encoded_data =
+      encoder.EncodeHeaders(absl::MakeSpan(test_data.headers));
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+
+  status_or_encoded_data =
+      encoder.EncodeBodyChunks(absl::MakeSpan(test_data.body_chunks), true);
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+
+  status_or_encoded_data =
+      encoder.EncodeTrailers(absl::MakeSpan(test_data.trailers));
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+
+  EXPECT_EQ(encoded_data, expected);
+}
+
+TEST(RequestIndeterminateLengthEncoder, RequestNoBody) {
+  std::string expected;
+  ASSERT_TRUE(absl::HexStringToBytes(
+      absl::StrCat(
+          kFramingIndicator, kIndeterminateLengthEncodedRequestControlData,
+          kIndeterminateLengthEncodedRequestHeaders, kContentTerminator,
+          kContentTerminator,   // Empty body chunks
+          kContentTerminator),  // Empty trailers
+      &expected));
+
+  BinaryHttpRequest::IndeterminateLengthEncoder encoder;
+  RequestIndeterminateLengthEncoderTestData test_data;
+  std::string encoded_data;
+
+  absl::StatusOr<std::string> status_or_encoded_data =
+      encoder.EncodeControlData(test_data.control_data);
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+
+  status_or_encoded_data =
+      encoder.EncodeHeaders(absl::MakeSpan(test_data.headers));
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+
+  status_or_encoded_data = encoder.EncodeBodyChunks({}, true);
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+
+  status_or_encoded_data = encoder.EncodeTrailers({});
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+
+  EXPECT_EQ(encoded_data, expected);
+}
+
+TEST(RequestIndeterminateLengthEncoder, EncodingChunksMultipleTimes) {
+  std::string expected;
+  ASSERT_TRUE(absl::HexStringToBytes(
+      absl::StrCat(
+          kFramingIndicator, kIndeterminateLengthEncodedRequestControlData,
+          kIndeterminateLengthEncodedRequestHeaders, kContentTerminator,
+          kIndeterminateLengthEncodedRequestBodyChunks, kContentTerminator,
+          kIndeterminateLengthEncodedRequestTrailers, kContentTerminator),
+      &expected));
+
+  BinaryHttpRequest::IndeterminateLengthEncoder encoder;
+  RequestIndeterminateLengthEncoderTestData test_data;
+  std::string encoded_data;
+
+  absl::StatusOr<std::string> status_or_encoded_data =
+      encoder.EncodeControlData(test_data.control_data);
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+
+  status_or_encoded_data =
+      encoder.EncodeHeaders(absl::MakeSpan(test_data.headers));
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+
+  status_or_encoded_data = encoder.EncodeBodyChunks(
+      absl::MakeSpan(test_data.body_chunks.data(), 1), false);
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+  status_or_encoded_data = encoder.EncodeBodyChunks(
+      absl::MakeSpan(test_data.body_chunks.data() + 1, 1), false);
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+  status_or_encoded_data = encoder.EncodeBodyChunks(
+      absl::MakeSpan(test_data.body_chunks.data() + 2, 1), false);
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+  status_or_encoded_data = encoder.EncodeBodyChunks({}, true);
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+
+  status_or_encoded_data =
+      encoder.EncodeTrailers(absl::MakeSpan(test_data.trailers));
+  QUICHE_EXPECT_OK(status_or_encoded_data);
+  if (status_or_encoded_data.ok()) {
+    encoded_data += *status_or_encoded_data;
+  }
+  EXPECT_EQ(encoded_data, expected);
+
+  RequestMessageSectionTestHandler handler;
+  BinaryHttpRequest::IndeterminateLengthDecoder decoder(handler);
+  QUICHE_EXPECT_OK(decoder.Decode(encoded_data, true));
+  ExpectRequestMessageSectionHandler(handler.GetMessageData());
+}
+
+TEST(RequestIndeterminateLengthEncoder, OutOfOrderHeaders) {
+  BinaryHttpRequest::IndeterminateLengthEncoder encoder;
+  EXPECT_THAT(encoder.EncodeHeaders({}),
+              test::StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(RequestIndeterminateLengthEncoder, OutOfOrderBodyChunks) {
+  BinaryHttpRequest::IndeterminateLengthEncoder encoder;
+  EXPECT_THAT(encoder.EncodeBodyChunks({}, true),
+              test::StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(RequestIndeterminateLengthEncoder, OutOfOrderTrailers) {
+  BinaryHttpRequest::IndeterminateLengthEncoder encoder;
+  EXPECT_THAT(encoder.EncodeTrailers({}),
+              test::StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(RequestIndeterminateLengthEncoder, MustNotEncodeControlDataTwice) {
+  BinaryHttpRequest::IndeterminateLengthEncoder encoder;
+  RequestIndeterminateLengthEncoderTestData test_data;
+  QUICHE_EXPECT_OK(encoder.EncodeControlData(test_data.control_data));
+  EXPECT_THAT(encoder.EncodeControlData(test_data.control_data),
+              test::StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(RequestIndeterminateLengthEncoder, MustNotEncodeHeadersTwice) {
+  BinaryHttpRequest::IndeterminateLengthEncoder encoder;
+  RequestIndeterminateLengthEncoderTestData test_data;
+  QUICHE_EXPECT_OK(encoder.EncodeControlData(test_data.control_data));
+  QUICHE_EXPECT_OK(encoder.EncodeHeaders({}));
+  EXPECT_THAT(encoder.EncodeHeaders({}),
+              test::StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(RequestIndeterminateLengthEncoder, MustNotEncodeChunksAfterChunksDone) {
+  BinaryHttpRequest::IndeterminateLengthEncoder encoder;
+  RequestIndeterminateLengthEncoderTestData test_data;
+  QUICHE_EXPECT_OK(encoder.EncodeControlData(test_data.control_data));
+  QUICHE_EXPECT_OK(encoder.EncodeHeaders({}));
+  QUICHE_EXPECT_OK(encoder.EncodeBodyChunks({}, true));
+  EXPECT_THAT(encoder.EncodeBodyChunks({}, true),
+              test::StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+TEST(RequestIndeterminateLengthEncoder, MustNotEncodeTrailersTwice) {
+  BinaryHttpRequest::IndeterminateLengthEncoder encoder;
+  RequestIndeterminateLengthEncoderTestData test_data;
+  QUICHE_EXPECT_OK(encoder.EncodeControlData(test_data.control_data));
+  QUICHE_EXPECT_OK(encoder.EncodeHeaders({}));
+  QUICHE_EXPECT_OK(encoder.EncodeBodyChunks({}, true));
+  QUICHE_EXPECT_OK(encoder.EncodeTrailers({}));
+  EXPECT_THAT(encoder.EncodeTrailers({}),
+              test::StatusIs(absl::StatusCode::kInvalidArgument));
+}
+
+namespace {
+
 struct ResponseIndeterminateLengthEncoderTestData {
   std::vector<quiche::BinaryHttpMessage::FieldView> informationalResponse1{
       {"running", "\"sleep 15\""}};