CVE-2019-15226 (C++, published 2019-10-09T16:15Z)
Description: value: Upon receiving each incoming request header data, Envoy will iterate over existing request headers to verify that the total size of the headers stays below a maximum limit. The implementation in versions 1.10.0 through 1.11.1 for HTTP/1.x traffic and all versions of Envoy for HTTP/2 traffic had O(n^2) performance characteristics. A remote attacker may craft a request that stays below the maximum request header size but consists of many thousands of small headers to consume CPU and result in a denial-of-service attack.
Severity: HIGH (CVSS v3 base score: 7.5)
Weakness: CWE-400 \u2014 Uncontrolled Resource Consumption
The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
Repository: https://github.com/envoyproxy/envoy
Files changed:
docs/root/intro/version_history.rst
include/envoy/access_log/access_log.h
include/envoy/http/header_map.h
source/common/http/conn_manager_impl.cc
source/common/http/header_map_impl.cc
source/common/http/header_map_impl.h
source/common/http/http1/codec_impl.cc
source/common/http/http2/codec_impl.cc
source/common/router/router.cc
source/extensions/access_loggers/common/access_log_base.h
source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc
source/extensions/filters/common/expr/context.cc
source/extensions/filters/http/rbac/rbac_filter.cc
test/common/http/conn_manager_impl_test.cc
test/common/http/header_map_impl_speed_test.cc
test/common/http/header_map_impl_test.cc
test/common/http/http2/codec_impl_test.cc
test/integration/http2_integration_test.cc
test/integration/http_integration.cc
test/integration/http_integration.h
test/integration/protocol_integration_test.cc
Commit message: Track byteSize of HeaderMap internally.

Introduces a cached byte size updated internally in HeaderMap. The value
is stored as an optional, and is cleared whenever a non-const pointer or
reference to a HeaderEntry is accessed. The cached value can be set with
refreshByteSize() which performs an iteration over the HeaderMap to sum
the size of each key and value in the HeaderMap.

Signed-off-by: Asra Ali <asraa@google.com>


Vulnerable code:
  virtual uint64_t byteSize() const PURE;
  ASSERT(connection_manager_.config_.maxRequestHeadersKb() > 0);
  if (request_headers_->byteSize() > (connection_manager_.config_.maxRequestHeadersKb() * 1024)) {
    sendLocalReply(Grpc::Common::hasGrpcContentType(*request_headers_),
                   Code::RequestHeaderFieldsTooLarge, "", nullptr, is_head_request_, absl::nullopt,
                   StreamInfo::ResponseCodeDetails::get().RequestHeadersTooLarge);
    return;
  }

void HeaderMapImpl::appendToHeader(HeaderString& header, absl::string_view data) {
    return;
      appendToHeader((*ref_lookup_response.entry_)->value(), value.getStringView());
    appendToHeader(entry->value(), value.getStringView());
    appendToHeader(entry->value(), buf);
    appendToHeader(entry->value(), value);
uint64_t HeaderMapImpl::byteSize() const {

  headers_.remove_if([&](const HeaderEntryImpl& entry) {
  HeaderEntry* name() override { return inline_headers_.name##_; }                                 \
  static void appendToHeader(HeaderString& header, absl::string_view data);
  uint64_t byteSize() const override;
  const uint32_t total =
      current_header_field_.size() + current_header_value_.size() + current_header_map_->byteSize();

  if (stream->headers_->byteSize() > max_request_headers_kb_ * 1024) {
  request_properties->set_request_headers_bytes(request_headers.byteSize());
  response_properties->set_response_headers_bytes(response_headers.byteSize());
      return CelValue::CreateInt64(info_.bytesReceived() + headers_.value_->byteSize());
TEST_F(HttpConnectionManagerImplTest, OverlyLongHeadersRejected) {
  setup(false, "");

  std::string response_code;
  std::string response_body;
  EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void {
    StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_);
    HeaderMapPtr headers{
        new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}};
    headers->addCopy(LowerCaseString("Foo"), std::string(60 * 1024, 'a'));

    EXPECT_CALL(response_encoder_, encodeHeaders(_, true))
        .WillOnce(Invoke([&response_code](const HeaderMap& headers, bool) -> void {
          response_code = std::string(headers.Status()->value().getStringView());
        }));
    decoder->decodeHeaders(std::move(headers), true);
    conn_manager_->newStream(response_encoder_);
  }));

  Buffer::OwnedImpl fake_input("1234");
  conn_manager_->onData(fake_input, false); // kick off request

  EXPECT_EQ("431", response_code);
  EXPECT_EQ("", response_body);
}

TEST_F(HttpConnectionManagerImplTest, OverlyLongHeadersAcceptedIfConfigured) {
  max_request_headers_kb_ = 62;
  setup(false, "");

  EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void {
    StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_);
    HeaderMapPtr headers{
        new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}};
    headers->addCopy(LowerCaseString("Foo"), std::string(60 * 1024, 'a'));

    EXPECT_CALL(response_encoder_, encodeHeaders(_, _)).Times(0);
    decoder->decodeHeaders(std::move(headers), true);
    conn_manager_->newStream(response_encoder_);
  }));

  Buffer::OwnedImpl fake_input("1234");
  conn_manager_->onData(fake_input, false); // kick off request
}

    size += headers.byteSize();
      codec_limit_kb * 1024 - request_headers.byteSize() - key.length() - head_room;
  ASSERT_EQ(request_headers.byteSize() + head_room, codec_limit_kb * 1024);
    const Http::TestHeaderMapImpl& response_headers, uint32_t response_size, int upstream_index) {
  waitForNextUpstreamRequest(upstream_index);
HttpIntegrationTest::waitForNextUpstreamRequest(const std::vector<uint64_t>& upstream_indices) {
          *dispatcher_, fake_upstream_connection_, TestUtility::DefaultTimeout,
void HttpIntegrationTest::waitForNextUpstreamRequest(uint64_t upstream_index) {
  waitForNextUpstreamRequest(std::vector<uint64_t>({upstream_index}));
  IntegrationStreamDecoderPtr
  sendRequestAndWaitForResponse(const Http::TestHeaderMapImpl& request_headers,
                                uint32_t request_body_size,
                                const Http::TestHeaderMapImpl& response_headers,
                                uint32_t response_body_size, int upstream_index = 0);
  uint64_t waitForNextUpstreamRequest(const std::vector<uint64_t>& upstream_indices);
  void waitForNextUpstreamRequest(uint64_t upstream_index = 0);

Fixed code:
1.11.2 (October 8, 2019)
========================
* http: fixed CVE-2019-15226 by adding a cached byte size in HeaderMap.

   * Prior to logging, call refreshByteSize() on HeaderMaps to ensure that an accurate byte size
   * count is logged.
   * TODO(asraa): Remove refreshByteSize() requirement when entries in HeaderMap can no longer be
   * modified by reference and headerMap holds an accurate internal byte size count.
   * HeaderMap contains an internal byte size count, updated as entries are added, removed, or
   * modified through the HeaderMap interface. However, HeaderEntries can be accessed and modified
   * by reference so that the HeaderMap can no longer accurately update the internal byte size
   * count.
   *
   * Calling byteSize before a HeaderEntry is accessed will return the internal byte size count. The
   * value is cleared when a HeaderEntry is accessed, and the value is updated and set again when
   * refreshByteSize is called.
   *
   * To guarantee an accurate byte size count, call refreshByteSize.
   *
   * @return uint64_t the approximate size of the header map in bytes if valid.
   */
  virtual absl::optional<uint64_t> byteSize() const PURE;

  /**
   * This returns the sum of the byte sizes of the keys and values in the HeaderMap. This also
   * updates and sets the byte size count.
   *
   * To guarantee an accurate byte size count, use this. If it is known HeaderEntries have not been
   * manipulated since a call to refreshByteSize, it is safe to use byteSize.
   *
   * @return uint64_t the approximate size of the header map in bytes.
   */
  virtual uint64_t refreshByteSize() PURE;

  /**
   * This returns the sum of the byte sizes of the keys and values in the HeaderMap.
   *
   * This iterates over the HeaderMap to calculate size and should only be called directly when the
   * user wants an explicit recalculation of the byte size.
   *
  virtual uint64_t byteSizeInternal() const PURE;
  // Refresh byte sizes of the HeaderMaps before logging.
  // TODO(asraa): Remove this when entries in HeaderMap can no longer be modified by reference and
  // HeaderMap holds an accurate internal byte size count.
  if (request_headers_ != nullptr) {
    request_headers_->refreshByteSize();
  }
  if (response_headers_ != nullptr) {
    response_headers_->refreshByteSize();
  }
  if (response_trailers_ != nullptr) {
    response_trailers_->refreshByteSize();
  }
uint64_t HeaderMapImpl::appendToHeader(HeaderString& header, absl::string_view data) {
    return 0;
  uint64_t byte_size = 0;
    byte_size += 1;
  return data.size() + byte_size;
void HeaderMapImpl::addSize(uint64_t size) {
  // Adds size to cached_byte_size_ if it exists.
  if (cached_byte_size_.has_value()) {
    cached_byte_size_.value() += size;
  }
}

void HeaderMapImpl::subtractSize(uint64_t size) {
  if (cached_byte_size_.has_value()) {
    ASSERT(cached_byte_size_ >= size);
    cached_byte_size_.value() -= size;
  }
}

      const uint64_t added_size =
          appendToHeader((*ref_lookup_response.entry_)->value(), value.getStringView());
      addSize(added_size);
    addSize(key.size() + value.size());
    const uint64_t added_size = appendToHeader(entry->value(), value.getStringView());
    addSize(added_size);
    const uint64_t added_size = appendToHeader(entry->value(), buf);
    addSize(added_size);
    const uint64_t added_size = appendToHeader(entry->value(), value);
    addSize(added_size);
absl::optional<uint64_t> HeaderMapImpl::byteSize() const { return cached_byte_size_; }

uint64_t HeaderMapImpl::refreshByteSize() {
  if (!cached_byte_size_.has_value()) {
    // In this case, the cached byte size is not valid, and the byte size is computed via an
    // iteration over the HeaderMap. The cached byte size is updated.
    cached_byte_size_ = byteSizeInternal();
  }
  return cached_byte_size_.value();
}

uint64_t HeaderMapImpl::byteSizeInternal() const {
  // Computes the total byte size by summing the byte size of the keys and values.
      cached_byte_size_.reset();
        subtractSize(i->key().size() + i->value().size());
  headers_.remove_if([&prefix, this](const HeaderEntryImpl& entry) {
          const uint32_t key_value_size = (*ref_lookup_response.entry_)->key().size() +
                                          (*ref_lookup_response.entry_)->value().size();
          subtractSize(key_value_size);
      } else {
        subtractSize(entry.key().size() + entry.value().size());
  cached_byte_size_.reset();
  addSize(key.get().size() + value.size());
  const uint64_t size_to_subtract = entry->entry_->key().size() + entry->entry_->value().size();
  subtractSize(size_to_subtract);
 *
 * When a non-const reference or pointer to a HeaderEntry is returned, the internal byte size count
 * will be cleared, since HeaderMap will no longer be able to accurately update the size of that
 * HeaderEntry.
 * TODO(asraa): Remove functions with a non-const HeaderEntry return value.
  HeaderEntry* name() override {                                                                   \
    cached_byte_size_.reset();                                                                     \
    return inline_headers_.name##_;                                                                \
  }                                                                                                \
    cached_byte_size_.reset();                                                                     \
  static uint64_t appendToHeader(HeaderString& header, absl::string_view data);
  absl::optional<uint64_t> byteSize() const override;
  uint64_t refreshByteSize() override;
  uint64_t byteSizeInternal() const override;
  void addSize(uint64_t size);
  void subtractSize(uint64_t size);
  // When present, this holds the internal byte size of the HeaderMap. The value is removed once an
  // inline header entry is accessed and updated when refreshByteSize() is called.
  absl::optional<uint64_t> cached_byte_size_ = 0;

  // Verify that the cached value in byte size exists.
  ASSERT(current_header_map_->byteSize().has_value());
  const uint32_t total = current_header_field_.size() + current_header_value_.size() +
                         current_header_map_->byteSize().value();
  // Validate that the completed HeaderMap's cached byte size exists and is correct.
  // This assert iterates over the HeaderMap.
  ASSERT(current_header_map_->byteSize().has_value() &&
         current_header_map_->byteSize() == current_header_map_->byteSizeInternal());
    // Verify that the final HeaderMap's byte size is under the limit before decoding headers.
    // This assert iterates over the HeaderMap.
    ASSERT(stream->headers_->byteSize().has_value() &&
           stream->headers_->byteSize().value() == stream->headers_->byteSizeInternal());
    if (stream->headers_) {
      // Verify that the final HeaderMap's byte size is under the limit before sending frames.
      // This assert iterates over the HeaderMap.
      ASSERT(stream->headers_->byteSize().has_value() &&
             stream->headers_->byteSize().value() == stream->headers_->byteSizeInternal());
    }
  // Verify that the cached value in byte size exists.
  ASSERT(stream->headers_->byteSize().has_value());
  if (stream->headers_->byteSize().value() > max_request_headers_kb_ * 1024) {
  // Prior to logging, refresh the byte size of the HeaderMaps.
  // TODO(asraa): Remove this when entries in HeaderMap can no longer be modified by reference and
  // HeaderMap holds an accurate internal byte size count.
  if (upstream_headers_ != nullptr) {
    upstream_headers_->refreshByteSize();
  }
  if (upstream_trailers_ != nullptr) {
    upstream_trailers_->refreshByteSize();
  }
   *
   * Prior to logging, call refreshByteSize() on HeaderMaps to ensure that an accurate byte size
   * count is logged.
   * TODO(asraa): Remove refreshByteSize() requirement when entries in HeaderMap can no longer be
   * modified by reference and HeaderMap holds an accurate internal byte size count.
  request_properties->set_request_headers_bytes(request_headers.byteSize().value());
  response_properties->set_response_headers_bytes(response_headers.byteSize().value());
      return CelValue::CreateInt64(info_.bytesReceived() + headers_.value_->byteSize().value());
    // Refresh headers byte size before checking if allowed.
    // TODO(asraa): Remove this when entries in HeaderMap can no longer be modified by reference and
    // HeaderMap holds an accurate internal byte size count.
    headers.refreshByteSize();
    // Refresh headers byte size before checking if allowed.
    // TODO(asraa): Remove this when entries in HeaderMap can no longer be modified by reference and
    // HeaderMap holds an accurate internal byte size count.
    headers.refreshByteSize();
    size += headers.byteSize().value();
  EXPECT_EQ(headers.byteSize().value(), 0);
// Utility function for testing byteSize() against a manual byte count.
uint64_t countBytesForTest(const HeaderMapImpl& headers) {
  uint64_t byte_size = 0;
  headers.iterate(
      [](const Http::HeaderEntry& header, void* context) -> Http::HeaderMap::Iterate {
        auto* byte_size = static_cast<uint64_t*>(context);
        *byte_size += header.key().getStringView().size() + header.value().getStringView().size();
        return Http::HeaderMap::Iterate::Continue;
      },
      &byte_size);
  return byte_size;
}

  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), 0);
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), 0);
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  headers.refreshByteSize();
  EXPECT_EQ(headers.byteSize().value(), 0);
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), 0);
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
    EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  headers.refreshByteSize();
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  // Test that removing will return 0 byte size.
  headers.refreshByteSize();
  EXPECT_EQ(headers.byteSize().value(), 0);
  headers.refreshByteSize();
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.refreshByteSize(), countBytesForTest(headers));
  EXPECT_EQ(headers.byteSize().value(), countBytesForTest(headers));
    EXPECT_EQ(headers.refreshByteSize(), 0);
  // Refresh byte size after adding default inline headers by reference.
  request_headers.refreshByteSize();
      codec_limit_kb * 1024 - request_headers.byteSize().value() - key.length() - head_room;
  ASSERT_EQ(request_headers.byteSize().value() + head_room, codec_limit_kb * 1024);
TEST_P(Http2IntegrationTest, LargeRequestTrailersRejected) { testLargeRequestTrailers(66, 60); }

    const Http::TestHeaderMapImpl& response_headers, uint32_t response_size, int upstream_index,
    std::chrono::milliseconds time) {
  waitForNextUpstreamRequest(upstream_index, time);
HttpIntegrationTest::waitForNextUpstreamRequest(const std::vector<uint64_t>& upstream_indices,
                                                std::chrono::milliseconds connection_wait_timeout) {
          *dispatcher_, fake_upstream_connection_, connection_wait_timeout,
void HttpIntegrationTest::waitForNextUpstreamRequest(
    uint64_t upstream_index, std::chrono::milliseconds connection_wait_timeout) {
  waitForNextUpstreamRequest(std::vector<uint64_t>({upstream_index}), connection_wait_timeout);
void HttpIntegrationTest::testLargeRequestTrailers(uint32_t size, uint32_t max_size) {
  // `size` parameter is the size of the trailer that will be added to the
  // request. The actual request byte size will exceed `size` due to keys
  // and other headers.

  config_helper_.addConfigModifier(
      [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
          -> void { hcm.mutable_max_request_headers_kb()->set_value(max_size); });
  max_request_headers_kb_ = max_size;
  Http::TestHeaderMapImpl request_trailers{{"trailer", "trailer"}};
  request_trailers.addCopy("big", std::string(size * 1024, 'a'));

  initialize();

  codec_client_ = makeHttpConnection(lookupPort("http"));
  fake_upstreams_[0]->set_allow_unexpected_disconnects(true);

  auto encoder_decoder = codec_client_->startRequest(default_request_headers_);
  request_encoder_ = &encoder_decoder.first;
  auto response = std::move(encoder_decoder.second);
  codec_client_->sendData(*request_encoder_, 10, false);
  codec_client_->sendTrailers(*request_encoder_, request_trailers);

  if (size >= max_size && downstream_protocol_ == Http::CodecClient::Type::HTTP2) {
    // For HTTP/2, expect a stream reset when the size of the trailers is larger than the maximum
    // limit.
    response->waitForReset();
    codec_client_->close();
    EXPECT_FALSE(response->complete());

  } else {
    waitForNextUpstreamRequest();
    upstream_request_->encodeHeaders(default_response_headers_, true);
    response->waitForEndStream();
    EXPECT_TRUE(response->complete());
  }
}

void HttpIntegrationTest::testManyRequestHeaders(std::chrono::milliseconds time) {
  config_helper_.addConfigModifier(
      [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
          -> void { hcm.mutable_max_request_headers_kb()->set_value(96); });
  max_request_headers_kb_ = 96;

  Http::TestHeaderMapImpl big_headers{
      {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}, {":authority", "host"}};

  for (int i = 0; i < 20000; i++) {
    big_headers.addCopy(std::to_string(i), std::string(0, 'a'));
  }
  initialize();

  codec_client_ = makeHttpConnection(lookupPort("http"));

  auto response =
      sendRequestAndWaitForResponse(big_headers, 0, default_response_headers_, 0, 0, time);

  EXPECT_TRUE(response->complete());
  EXPECT_EQ("200", response->headers().Status()->value().getStringView());
}

  IntegrationStreamDecoderPtr sendRequestAndWaitForResponse(
      const Http::TestHeaderMapImpl& request_headers, uint32_t request_body_size,
      const Http::TestHeaderMapImpl& response_headers, uint32_t response_body_size,
      int upstream_index = 0, std::chrono::milliseconds time = TestUtility::DefaultTimeout);
  uint64_t waitForNextUpstreamRequest(
      const std::vector<uint64_t>& upstream_indices,
      std::chrono::milliseconds connection_wait_timeout = TestUtility::DefaultTimeout);
  void waitForNextUpstreamRequest(
      uint64_t upstream_index = 0,
      std::chrono::milliseconds connection_wait_timeout = TestUtility::DefaultTimeout);
  void testLargeHeaders(Http::TestHeaderMapImpl request_headers,
                        Http::TestHeaderMapImpl request_trailers, uint32_t size, uint32_t max_size);
  void testLargeRequestTrailers(uint32_t size, uint32_t max_size = 60);
  void testManyRequestHeaders(std::chrono::milliseconds time = TestUtility::DefaultTimeout);
// Validate that lots of tiny cookies doesn't cause a DoS (single cookie header).
TEST_P(DownstreamProtocolIntegrationTest, LargeCookieParsingConcatenated) {
  initialize();

  codec_client_ = makeHttpConnection(lookupPort("http"));
  Http::TestHeaderMapImpl request_headers{{":method", "POST"},
                                          {":path", "/test/long/url"},
                                          {":scheme", "http"},
                                          {":authority", "host"},
                                          {"content-length", "0"}};
  std::vector<std::string> cookie_pieces;
  for (int i = 0; i < 7000; i++) {
    cookie_pieces.push_back(fmt::sprintf("a%x=b", i));
  }
  request_headers.addCopy("cookie", absl::StrJoin(cookie_pieces, "; "));
  auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0);

  ASSERT_TRUE(response->complete());
  EXPECT_EQ("200", response->headers().Status()->value().getStringView());
}

// Validate that lots of tiny cookies doesn't cause a DoS (many cookie headers).
TEST_P(DownstreamProtocolIntegrationTest, LargeCookieParsingMany) {
  initialize();

  codec_client_ = makeHttpConnection(lookupPort("http"));
  Http::TestHeaderMapImpl request_headers{{":method", "POST"},
                                          {":path", "/test/long/url"},
                                          {":scheme", "http"},
                                          {":authority", "host"},
                                          {"content-length", "0"}};
  for (int i = 0; i < 2000; i++) {
    request_headers.addCopy("cookie", fmt::sprintf("a%x=b", i));
  }
  auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0);

  ASSERT_TRUE(response->complete());
  EXPECT_EQ("200", response->headers().Status()->value().getStringView());
}

TEST_P(DownstreamProtocolIntegrationTest, ManyRequestHeadersTimeout) {
  // Set timeout for 5 seconds, and ensure that a request with 20k+ headers can be sent.
  testManyRequestHeaders(std::chrono::milliseconds(5000));
}

TEST_P(DownstreamProtocolIntegrationTest, LargeRequestTrailersAccepted) {
  testLargeRequestTrailers(60, 96);
}

TEST_P(DownstreamProtocolIntegrationTest, LargeRequestTrailersRejected) {
  testLargeRequestTrailers(66, 60);
}

TEST_P(DownstreamProtocolIntegrationTest, ManyTrailerHeaders) {
  config_helper_.addConfigModifier(
      [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
          -> void { hcm.mutable_max_request_headers_kb()->set_value(96); });

  Http::TestHeaderMapImpl request_trailers{};
  for (int i = 0; i < 20000; i++) {
    request_trailers.addCopy(std::to_string(i), "");
  }

  initialize();
  codec_client_ = makeHttpConnection(lookupPort("http"));
  auto encoder_decoder =
      codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "POST"},
                                                          {":path", "/test/long/url"},
                                                          {":scheme", "http"},
                                                          {":authority", "host"}});
  request_encoder_ = &encoder_decoder.first;
  auto response = std::move(encoder_decoder.second);
  codec_client_->sendTrailers(*request_encoder_, request_trailers);
  waitForNextUpstreamRequest();
  upstream_request_->encodeHeaders(default_response_headers_, true);
  response->waitForEndStream();

  EXPECT_TRUE(upstream_request_->complete());
  EXPECT_TRUE(response->complete());
  EXPECT_EQ("200", response->headers().Status()->value().getStringView());
}