text: #include "test/integration/http2_integration_test.h"
  
  #include <string>
  
  #include "common/buffer/buffer_impl.h"
  #include "common/http/header_map_impl.h"
  
  #include "test/integration/utility.h"
  #include "test/mocks/http/mocks.h"
  #include "test/test_common/network_utility.h"
  #include "test/test_common/printers.h"
  #include "test/test_common/utility.h"
  
  #include "gtest/gtest.h"
  
  using ::testing::HasSubstr;
  using ::testing::MatchesRegex;
  
  namespace Envoy {
  
  INSTANTIATE_TEST_CASE_P(IpVersions, Http2IntegrationTest,
                          testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
                          TestUtility::ipTestParamsToString);
  
  TEST_P(Http2IntegrationTest, RouterNotFound) { testRouterNotFound(); }
  
  TEST_P(Http2IntegrationTest, RouterNotFoundBodyNoBuffer) { testRouterNotFoundWithBody(); }
  
  TEST_P(Http2IntegrationTest, RouterClusterNotFound404) { testRouterClusterNotFound404(); }
  
  TEST_P(Http2IntegrationTest, RouterClusterNotFound503) { testRouterClusterNotFound503(); }
  
  TEST_P(Http2IntegrationTest, RouterRedirect) { testRouterRedirect(); }
  
  TEST_P(Http2IntegrationTest, ValidZeroLengthContent) { testValidZeroLengthContent(); }
  
  TEST_P(Http2IntegrationTest, InvalidContentLength) { testInvalidContentLength(); }
  
  TEST_P(Http2IntegrationTest, MultipleContentLengths) { testMultipleContentLengths(); }
  
  TEST_P(Http2IntegrationTest, ComputedHealthCheck) { testComputedHealthCheck(); }
  
  TEST_P(Http2IntegrationTest, AddEncodedTrailers) { testAddEncodedTrailers(); }
  
  TEST_P(Http2IntegrationTest, DrainClose) { testDrainClose(); }
  
  TEST_P(Http2IntegrationTest, RouterRequestAndResponseWithBodyNoBuffer) {
    testRouterRequestAndResponseWithBody(1024, 512, false);
  }
  
  TEST_P(Http2IntegrationTest, FlowControlOnAndGiantBody) {
    config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream.
    testRouterRequestAndResponseWithBody(1024 * 1024, 1024 * 1024, false);
  }
  
  TEST_P(Http2IntegrationTest, RouterHeaderOnlyRequestAndResponseNoBuffer) {
    testRouterHeaderOnlyRequestAndResponse(true);
  }
  
  TEST_P(Http2IntegrationTest, RouterRequestAndResponseLargeHeaderNoBuffer) {
    testRouterRequestAndResponseWithBody(1024, 512, true);
  }
  
  TEST_P(Http2IntegrationTest, ShutdownWithActiveConnPoolConnections) {
    testRouterHeaderOnlyRequestAndResponse(false);
  }
  
  TEST_P(Http2IntegrationTest, RouterUpstreamDisconnectBeforeRequestcomplete) {
    testRouterUpstreamDisconnectBeforeRequestComplete();
  }
  
  TEST_P(Http2IntegrationTest, RouterUpstreamDisconnectBeforeResponseComplete) {
    testRouterUpstreamDisconnectBeforeResponseComplete();
  }
  
  TEST_P(Http2IntegrationTest, RouterDownstreamDisconnectBeforeRequestComplete) {
    testRouterDownstreamDisconnectBeforeRequestComplete();
  }
  
  TEST_P(Http2IntegrationTest, RouterDownstreamDisconnectBeforeResponseComplete) {
    testRouterDownstreamDisconnectBeforeResponseComplete();
  }
  
  TEST_P(Http2IntegrationTest, RouterUpstreamResponseBeforeRequestComplete) {
    testRouterUpstreamResponseBeforeRequestComplete();
  }
  
  TEST_P(Http2IntegrationTest, TwoRequests) { testTwoRequests(); }
  
  TEST_P(Http2IntegrationTest, TwoRequestsWithForcedBackup) { testTwoRequests(true); }
  
  TEST_P(Http2IntegrationTest, Retry) { testRetry(); }
  
  TEST_P(Http2IntegrationTest, RetryAttemptCount) { testRetryAttemptCountHeader(); }
  
  TEST_P(Http2IntegrationTest, EnvoyHandling100Continue) { testEnvoyHandling100Continue(); }
  
  TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) {
    testEnvoyProxyMetadataInResponse();
  }
  
  TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadata) { testEnvoyProxyMultipleMetadata(); }
  
  TEST_P(Http2MetadataIntegrationTest, ProxyInvalidMetadata) { testEnvoyProxyInvalidMetadata(); }
  
  TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadataReachSizeLimit) {
    testEnvoyMultipleMetadataReachSizeLimit();
  }
  
  TEST_P(Http2IntegrationTest, EnvoyHandlingDuplicate100Continue) {
    testEnvoyHandling100Continue(true);
  }
  
  TEST_P(Http2IntegrationTest, EnvoyProxyingEarly100Continue) { testEnvoyProxying100Continue(true); }
  
  TEST_P(Http2IntegrationTest, EnvoyProxyingLate100Continue) { testEnvoyProxying100Continue(false); }
  
  TEST_P(Http2IntegrationTest, RetryHittingBufferLimit) { testRetryHittingBufferLimit(); }
  
  TEST_P(Http2IntegrationTest, HittingDecoderFilterLimit) { testHittingDecoderFilterLimit(); }
  
  TEST_P(Http2IntegrationTest, HittingEncoderFilterLimit) { testHittingEncoderFilterLimit(); }
  
  TEST_P(Http2IntegrationTest, GrpcRouterNotFound) { testGrpcRouterNotFound(); }
  
  TEST_P(Http2IntegrationTest, RetryHostPredicateFilter) { testRetryHostPredicateFilter(); }
  
  TEST_P(Http2IntegrationTest, RetryPriority) { testRetryPriority(); }
  
  TEST_P(Http2IntegrationTest, GrpcRetry) { testGrpcRetry(); }
  
  // Send a request with overly large headers, and ensure it results in stream reset.
  TEST_P(Http2IntegrationTest, MaxHeadersInCodec) {
    Http::TestHeaderMapImpl big_headers{
        {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}, {":authority", "host"}};
  
    big_headers.addCopy("big", std::string(63 * 1024, 'a'));
  
    initialize();
    codec_client_ = makeHttpConnection(lookupPort("http"));
    auto encoder_decoder = codec_client_->startRequest(big_headers);
    auto response = std::move(encoder_decoder.second);
    response->waitForReset();
    codec_client_->close();
  }
  
  TEST_P(Http2IntegrationTest, EncodingHeaderOnlyResponse) { testHeadersOnlyFilterEncoding(); }
  
  TEST_P(Http2IntegrationTest, DecodingHeaderOnlyResponse) { testHeadersOnlyFilterDecoding(); }
  
  TEST_P(Http2IntegrationTest, DecodingHeaderOnlyInterleaved) { testHeadersOnlyFilterInterleaved(); }
  
  TEST_P(Http2IntegrationTest, DownstreamResetBeforeResponseComplete) {
    testDownstreamResetBeforeResponseComplete();
  }
  
  TEST_P(Http2IntegrationTest, BadMagic) {
    initialize();
    Buffer::OwnedImpl buffer("hello");
    std::string response;
    RawConnectionDriver connection(
        lookupPort("http"), buffer,
        [&](Network::ClientConnection&, const Buffer::Instance& data) -> void {
          response.append(data.toString());
        },
        version_);
  
    connection.run();
    EXPECT_EQ("", response);
  }
  
  TEST_P(Http2IntegrationTest, BadFrame) {
    initialize();
    Buffer::OwnedImpl buffer("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\nhelloworldcauseanerror");
    std::string response;
    RawConnectionDriver connection(
        lookupPort("http"), buffer,
        [&](Network::ClientConnection&, const Buffer::Instance& data) -> void {
          response.append(data.toString());
        },
        version_);
  
    connection.run();
    EXPECT_TRUE(response.find("SETTINGS expected") != std::string::npos);
  }
  
  // Send client headers, a GoAway and then a body and ensure the full request and
  // response are received.
  TEST_P(Http2IntegrationTest, GoAway) {
    config_helper_.addFilter(ConfigHelper::DEFAULT_HEALTH_CHECK_FILTER);
    initialize();
  
    codec_client_ = makeHttpConnection(lookupPort("http"));
    auto encoder_decoder = codec_client_->startRequest(Http::TestHeaderMapImpl{
        {":method", "GET"}, {":path", "/healthcheck"}, {":scheme", "http"}, {":authority", "host"}});
    request_encoder_ = &encoder_decoder.first;
    auto response = std::move(encoder_decoder.second);
    codec_client_->goAway();
    codec_client_->sendData(*request_encoder_, 0, true);
    response->waitForEndStream();
    codec_client_->close();
  
    EXPECT_TRUE(response->complete());
    EXPECT_STREQ("200", response->headers().Status()->value().c_str());
  }
  
  TEST_P(Http2IntegrationTest, Trailers) { testTrailers(1024, 2048); }
  
  TEST_P(Http2IntegrationTest, TrailersGiantBody) { testTrailers(1024 * 1024, 1024 * 1024); }
  
  TEST_P(Http2IntegrationTest, GrpcRequestTimeout) {
    config_helper_.addConfigModifier(
        [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
            -> void {
          auto* route_config = hcm.mutable_route_config();
          auto* virtual_host = route_config->mutable_virtual_hosts(0);
          auto* route = virtual_host->mutable_routes(0);
          route->mutable_route()->mutable_max_grpc_timeout()->set_seconds(60 * 60);
        });
    initialize();
  
    // Envoy will close some number of connections when request times out.
    // Make sure they don't cause assertion failures when we ignore them.
    fake_upstreams_[0]->set_allow_unexpected_disconnects(true);
    codec_client_ = makeHttpConnection(lookupPort("http"));
    // With upstream request timeout Envoy should send a gRPC-Status "DEADLINE EXCEEDED".
    // TODO: Properly map request timeout to "DEADLINE EXCEEDED" instead of "SERVICE UNAVAILABLE".
    auto response = codec_client_->makeHeaderOnlyRequest(
        Http::TestHeaderMapImpl{{":method", "POST"},
                                {":path", "/test/long/url"},
                                {":scheme", "http"},
                                {":authority", "host"},
                                {"te", "trailers"},
                                {"grpc-timeout", "1S"}, // 1 Second
                                {"content-type", "application/grpc"}});
    response->waitForEndStream();
    EXPECT_TRUE(response->complete());
    EXPECT_STREQ("200", response->headers().Status()->value().c_str());
    EXPECT_NE(response->headers().GrpcStatus(), nullptr);
    EXPECT_STREQ("14", response->headers().GrpcStatus()->value().c_str()); // Service Unavailable
    EXPECT_LT(0, test_server_->counter("cluster.cluster_0.upstream_rq_timeout")->value());
  }
  
  // Tests idle timeout behaviour with single request and validates that idle timer kicks in
  // after given timeout.
  TEST_P(Http2IntegrationTest, IdleTimoutBasic) { testIdleTimeoutBasic(); }
  
  // Tests idle timeout behaviour with multiple requests and validates that idle timer kicks in
  // after both the requests are done.
  TEST_P(Http2IntegrationTest, IdleTimeoutWithTwoRequests) { testIdleTimeoutWithTwoRequests(); }
  
  // Interleave two requests and responses and make sure that idle timeout is handled correctly.
  TEST_P(Http2IntegrationTest, IdleTimeoutWithSimultaneousRequests) {
    FakeHttpConnectionPtr fake_upstream_connection1;
    FakeHttpConnectionPtr fake_upstream_connection2;
    Http::StreamEncoder* encoder1;
    Http::StreamEncoder* encoder2;
    FakeStreamPtr upstream_request1;
    FakeStreamPtr upstream_request2;
    int32_t request1_bytes = 1024;
    int32_t request2_bytes = 512;
  
    config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) {
      auto* static_resources = bootstrap.mutable_static_resources();
      auto* cluster = static_resources->mutable_clusters(0);
      auto* http_protocol_options = cluster->mutable_common_http_protocol_options();
      auto* idle_time_out = http_protocol_options->mutable_idle_timeout();
      std::chrono::milliseconds timeout(1000);
      auto seconds = std::chrono::duration_cast<std::chrono::seconds>(timeout);
      idle_time_out->set_seconds(seconds.count());
    });
  
    initialize();
  
    codec_client_ = makeHttpConnection(lookupPort("http"));
  
    // Start request 1
    auto encoder_decoder =
        codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "POST"},
                                                            {":path", "/test/long/url"},
                                                            {":scheme", "http"},
                                                            {":authority", "host"}});
    encoder1 = &encoder_decoder.first;
    auto response1 = std::move(encoder_decoder.second);
  
    ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection1));
    ASSERT_TRUE(fake_upstream_connection1->waitForNewStream(*dispatcher_, upstream_request1));
  
    // Start request 2
    auto encoder_decoder2 =
        codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "POST"},
                                                            {":path", "/test/long/url"},
                                                            {":scheme", "http"},
                                                            {":authority", "host"}});
    encoder2 = &encoder_decoder2.first;
    auto response2 = std::move(encoder_decoder2.second);
    ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection2));
    ASSERT_TRUE(fake_upstream_connection2->waitForNewStream(*dispatcher_, upstream_request2));
  
    // Finish request 1
    codec_client_->sendData(*encoder1, request1_bytes, true);
    ASSERT_TRUE(upstream_request1->waitForEndStream(*dispatcher_));
  
    // Finish request i2
    codec_client_->sendData(*encoder2, request2_bytes, true);
    ASSERT_TRUE(upstream_request2->waitForEndStream(*dispatcher_));
  
    // Respond to request 2
    upstream_request2->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, false);
    upstream_request2->encodeData(request2_bytes, true);
    response2->waitForEndStream();
    EXPECT_TRUE(upstream_request2->complete());
    EXPECT_EQ(request2_bytes, upstream_request2->bodyLength());
    EXPECT_TRUE(response2->complete());
    EXPECT_STREQ("200", response2->headers().Status()->value().c_str());
    EXPECT_EQ(request2_bytes, response2->body().size());
  
    // Validate that idle time is not kicked in.
    EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.upstream_cx_idle_timeout")->value());
    EXPECT_NE(0, test_server_->counter("cluster.cluster_0.upstream_cx_total")->value());
  
    // Respond to request 1
    upstream_request1->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, false);
    upstream_request1->encodeData(request1_bytes, true);
    response1->waitForEndStream();
    EXPECT_TRUE(upstream_request1->complete());
    EXPECT_EQ(request1_bytes, upstream_request1->bodyLength());
    EXPECT_TRUE(response1->complete());
    EXPECT_STREQ("200", response1->headers().Status()->value().c_str());
    EXPECT_EQ(request1_bytes, response1->body().size());
  
    // Do not send any requests and validate idle timeout kicks in after both the requests are done.
    ASSERT_TRUE(fake_upstream_connection1->waitForDisconnect());
    ASSERT_TRUE(fake_upstream_connection2->waitForDisconnect());
    test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_idle_timeout", 2);
  }
  
  // Interleave two requests and responses and make sure the HTTP2 stack handles this correctly.
  void Http2IntegrationTest::simultaneousRequest(int32_t request1_bytes, int32_t request2_bytes) {
    FakeHttpConnectionPtr fake_upstream_connection1;
    FakeHttpConnectionPtr fake_upstream_connection2;
    Http::StreamEncoder* encoder1;
    Http::StreamEncoder* encoder2;
    FakeStreamPtr upstream_request1;
    FakeStreamPtr upstream_request2;
    initialize();
    codec_client_ = makeHttpConnection(lookupPort("http"));
  
    // Start request 1
    auto encoder_decoder =
        codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "POST"},
                                                            {":path", "/test/long/url"},
                                                            {":scheme", "http"},
                                                            {":authority", "host"}});
    encoder1 = &encoder_decoder.first;
    auto response1 = std::move(encoder_decoder.second);
  
    ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection1));
    ASSERT_TRUE(fake_upstream_connection1->waitForNewStream(*dispatcher_, upstream_request1));
  
    // Start request 2
    auto encoder_decoder2 =
        codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "POST"},
                                                            {":path", "/test/long/url"},
                                                            {":scheme", "http"},
                                                            {":authority", "host"}});
    encoder2 = &encoder_decoder2.first;
    auto response2 = std::move(encoder_decoder2.second);
    ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection2));
    ASSERT_TRUE(fake_upstream_connection2->waitForNewStream(*dispatcher_, upstream_request2));
  
    // Finish request 1
    codec_client_->sendData(*encoder1, request1_bytes, true);
    ASSERT_TRUE(upstream_request1->waitForEndStream(*dispatcher_));
  
    // Finish request 2
    codec_client_->sendData(*encoder2, request2_bytes, true);
    ASSERT_TRUE(upstream_request2->waitForEndStream(*dispatcher_));
  
    // Respond to request 2
    upstream_request2->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, false);
    upstream_request2->encodeData(request2_bytes, true);
    response2->waitForEndStream();
    EXPECT_TRUE(upstream_request2->complete());
    EXPECT_EQ(request2_bytes, upstream_request2->bodyLength());
    EXPECT_TRUE(response2->complete());
    EXPECT_STREQ("200", response2->headers().Status()->value().c_str());
    EXPECT_EQ(request2_bytes, response2->body().size());
  
    // Respond to request 1
    upstream_request1->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, false);
    upstream_request1->encodeData(request2_bytes, true);
    response1->waitForEndStream();
    EXPECT_TRUE(upstream_request1->complete());
    EXPECT_EQ(request1_bytes, upstream_request1->bodyLength());
    EXPECT_TRUE(response1->complete());
    EXPECT_STREQ("200", response1->headers().Status()->value().c_str());
    EXPECT_EQ(request2_bytes, response1->body().size());
  
    // Cleanup both downstream and upstream
    ASSERT_TRUE(fake_upstream_connection1->close());
    ASSERT_TRUE(fake_upstream_connection1->waitForDisconnect());
    ASSERT_TRUE(fake_upstream_connection2->close());
    ASSERT_TRUE(fake_upstream_connection2->waitForDisconnect());
    codec_client_->close();
  }
  
  TEST_P(Http2IntegrationTest, SimultaneousRequest) { simultaneousRequest(1024, 512); }
  
  TEST_P(Http2IntegrationTest, SimultaneousRequestWithBufferLimits) {
    config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream.
    simultaneousRequest(1024 * 32, 1024 * 16);
  }
  
  // Test downstream connection delayed close processing.
  TEST_P(Http2IntegrationTest, DelayedCloseAfterBadFrame) {
    initialize();
    Buffer::OwnedImpl buffer("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\nhelloworldcauseanerror");
    std::string response;
    RawConnectionDriver connection(
        lookupPort("http"), buffer,
        [&](Network::ClientConnection& connection, const Buffer::Instance& data) -> void {
          response.append(data.toString());
          connection.dispatcher().exit();
        },
        version_);
  
    connection.run();
    EXPECT_THAT(response, HasSubstr("SETTINGS expected"));
    // Due to the multiple dispatchers involved (one for the RawConnectionDriver and another for the
    // Envoy server), it's possible the delayed close timer could fire and close the server socket
    // prior to the data callback above firing. Therefore, we may either still be connected, or have
    // received a remote close.
    if (connection.last_connection_event() == Network::ConnectionEvent::Connected) {
      connection.run();
    }
    EXPECT_EQ(connection.last_connection_event(), Network::ConnectionEvent::RemoteClose);
    EXPECT_EQ(test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value(),
              1);
  }
  
  // Test disablement of delayed close processing on downstream connections.
  TEST_P(Http2IntegrationTest, DelayedCloseDisabled) {
    config_helper_.addConfigModifier(
        [](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) {
          hcm.mutable_delayed_close_timeout()->set_seconds(0);
        });
    initialize();
    Buffer::OwnedImpl buffer("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\nhelloworldcauseanerror");
    std::string response;
    RawConnectionDriver connection(
        lookupPort("http"), buffer,
        [&](Network::ClientConnection& connection, const Buffer::Instance& data) -> void {
          response.append(data.toString());
          connection.dispatcher().exit();
        },
        version_);
  
    connection.run();
    EXPECT_THAT(response, HasSubstr("SETTINGS expected"));
    // Due to the multiple dispatchers involved (one for the RawConnectionDriver and another for the
    // Envoy server), it's possible for the 'connection' to receive the data and exit the dispatcher
    // prior to the FIN being received from the server.
    if (connection.last_connection_event() == Network::ConnectionEvent::Connected) {
      connection.run();
    }
    EXPECT_EQ(connection.last_connection_event(), Network::ConnectionEvent::RemoteClose);
    EXPECT_EQ(test_server_->counter("http.config_test.downstream_cx_delayed_close_timeout")->value(),
              0);
  }
  
  Http2RingHashIntegrationTest::Http2RingHashIntegrationTest() {
    config_helper_.addConfigModifier([&](envoy::config::bootstrap::v2::Bootstrap& bootstrap) -> void {
      auto* cluster = bootstrap.mutable_static_resources()->mutable_clusters(0);
      cluster->clear_hosts();
      cluster->set_lb_policy(envoy::api::v2::Cluster_LbPolicy_RING_HASH);
      for (int i = 0; i < num_upstreams_; i++) {
        auto* socket = cluster->add_hosts()->mutable_socket_address();
        socket->set_address(Network::Test::getLoopbackAddressString(version_));
      }
    });
  }
  
  Http2RingHashIntegrationTest::~Http2RingHashIntegrationTest() {
    if (codec_client_) {
      codec_client_->close();
      codec_client_ = nullptr;
    }
    for (auto it = fake_upstream_connections_.begin(); it != fake_upstream_connections_.end(); ++it) {
      AssertionResult result = (*it)->close();
      RELEASE_ASSERT(result, result.message());
      result = (*it)->waitForDisconnect();
      RELEASE_ASSERT(result, result.message());
    }
  }
  
  void Http2RingHashIntegrationTest::createUpstreams() {
    for (int i = 0; i < num_upstreams_; i++) {
      fake_upstreams_.emplace_back(
          new FakeUpstream(0, FakeHttpConnection::Type::HTTP1, version_, timeSystem()));
    }
  }
  
  INSTANTIATE_TEST_CASE_P(IpVersions, Http2RingHashIntegrationTest,
                          testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
                          TestUtility::ipTestParamsToString);
  
  INSTANTIATE_TEST_CASE_P(IpVersions, Http2MetadataIntegrationTest,
                          testing::ValuesIn(TestEnvironment::getIpVersionsForTest()),
                          TestUtility::ipTestParamsToString);
  
  void Http2RingHashIntegrationTest::sendMultipleRequests(
      int request_bytes, Http::TestHeaderMapImpl headers,
      std::function<void(IntegrationStreamDecoder&)> cb) {
    TestRandomGenerator rand;
    const uint32_t num_requests = 50;
    std::vector<Http::StreamEncoder*> encoders;
    std::vector<IntegrationStreamDecoderPtr> responses;
    std::vector<FakeStreamPtr> upstream_requests;
  
    initialize();
  
    codec_client_ = makeHttpConnection(lookupPort("http"));
    for (uint32_t i = 0; i < num_requests; ++i) {
      auto encoder_decoder = codec_client_->startRequest(headers);
      encoders.push_back(&encoder_decoder.first);
      responses.push_back(std::move(encoder_decoder.second));
      codec_client_->sendData(*encoders[i], request_bytes, true);
    }
  
    for (uint32_t i = 0; i < num_requests; ++i) {
      FakeHttpConnectionPtr fake_upstream_connection;
      ASSERT_TRUE(FakeUpstream::waitForHttpConnection(*dispatcher_, fake_upstreams_,
                                                      fake_upstream_connection));
      // As data and streams are interwoven, make sure waitForNewStream()
      // ignores incoming data and waits for actual stream establishment.
      upstream_requests.emplace_back();
      ASSERT_TRUE(
          fake_upstream_connection->waitForNewStream(*dispatcher_, upstream_requests.back(), true));
      upstream_requests.back()->setAddServedByHeader(true);
      fake_upstream_connections_.push_back(std::move(fake_upstream_connection));
    }
  
    for (uint32_t i = 0; i < num_requests; ++i) {
      ASSERT_TRUE(upstream_requests[i]->waitForEndStream(*dispatcher_));
      upstream_requests[i]->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, false);
      upstream_requests[i]->encodeData(rand.random() % (1024 * 2), true);
    }
  
    for (uint32_t i = 0; i < num_requests; ++i) {
      responses[i]->waitForEndStream();
      EXPECT_TRUE(upstream_requests[i]->complete());
      EXPECT_EQ(request_bytes, upstream_requests[i]->bodyLength());
  
      EXPECT_TRUE(responses[i]->complete());
      cb(*responses[i]);
    }
  }
  
  TEST_P(Http2RingHashIntegrationTest, CookieRoutingNoCookieNoTtl) {
    config_helper_.addConfigModifier(
        [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
            -> void {
          auto* hash_policy = hcm.mutable_route_config()
                                  ->mutable_virtual_hosts(0)
                                  ->mutable_routes(0)
                                  ->mutable_route()
                                  ->add_hash_policy();
          auto* cookie = hash_policy->mutable_cookie();
          cookie->set_name("foo");
        });
  
    // This test is non-deterministic, so make it extremely unlikely that not all
    // upstreams get hit.
    num_upstreams_ = 2;
    std::set<std::string> served_by;
    sendMultipleRequests(
        1024,
        Http::TestHeaderMapImpl{{":method", "POST"},
                                {":path", "/test/long/url"},
                                {":scheme", "http"},
                                {":authority", "host"}},
        [&](IntegrationStreamDecoder& response) {
          EXPECT_STREQ("200", response.headers().Status()->value().c_str());
          EXPECT_TRUE(response.headers().get(Http::Headers::get().SetCookie) == nullptr);
          served_by.insert(
              response.headers().get(Http::LowerCaseString("x-served-by"))->value().c_str());
        });
    EXPECT_EQ(served_by.size(), num_upstreams_);
  }
  
  TEST_P(Http2RingHashIntegrationTest, CookieRoutingNoCookieWithNonzeroTtlSet) {
    config_helper_.addConfigModifier(
        [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
            -> void {
          auto* hash_policy = hcm.mutable_route_config()
                                  ->mutable_virtual_hosts(0)
                                  ->mutable_routes(0)
                                  ->mutable_route()
                                  ->add_hash_policy();
          auto* cookie = hash_policy->mutable_cookie();
          cookie->set_name("foo");
          cookie->mutable_ttl()->set_seconds(15);
        });
  
    std::set<std::string> set_cookies;
    sendMultipleRequests(
        1024,
        Http::TestHeaderMapImpl{{":method", "POST"},
                                {":path", "/test/long/url"},
                                {":scheme", "http"},
                                {":authority", "host"}},
        [&](IntegrationStreamDecoder& response) {
          EXPECT_STREQ("200", response.headers().Status()->value().c_str());
          std::string value = response.headers().get(Http::Headers::get().SetCookie)->value().c_str();
          set_cookies.insert(value);
          EXPECT_THAT(value, MatchesRegex("foo=.*; Max-Age=15; HttpOnly"));
        });
    EXPECT_EQ(set_cookies.size(), 1);
  }
  
  TEST_P(Http2RingHashIntegrationTest, CookieRoutingNoCookieWithZeroTtlSet) {
    config_helper_.addConfigModifier(
        [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
            -> void {
          auto* hash_policy = hcm.mutable_route_config()
                                  ->mutable_virtual_hosts(0)
                                  ->mutable_routes(0)
                                  ->mutable_route()
                                  ->add_hash_policy();
          auto* cookie = hash_policy->mutable_cookie();
          cookie->set_name("foo");
          cookie->mutable_ttl();
        });
  
    std::set<std::string> set_cookies;
    sendMultipleRequests(
        1024,
        Http::TestHeaderMapImpl{{":method", "POST"},
                                {":path", "/test/long/url"},
                                {":scheme", "http"},
                                {":authority", "host"}},
        [&](IntegrationStreamDecoder& response) {
          EXPECT_STREQ("200", response.headers().Status()->value().c_str());
          std::string value = response.headers().get(Http::Headers::get().SetCookie)->value().c_str();
          set_cookies.insert(value);
          EXPECT_THAT(value, MatchesRegex("^foo=.*$"));
        });
    EXPECT_EQ(set_cookies.size(), 1);
  }
  
  TEST_P(Http2RingHashIntegrationTest, CookieRoutingWithCookieNoTtl) {
    config_helper_.addConfigModifier(
        [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
            -> void {
          auto* hash_policy = hcm.mutable_route_config()
                                  ->mutable_virtual_hosts(0)
                                  ->mutable_routes(0)
                                  ->mutable_route()
                                  ->add_hash_policy();
          auto* cookie = hash_policy->mutable_cookie();
          cookie->set_name("foo");
        });
  
    std::set<std::string> served_by;
    sendMultipleRequests(
        1024,
        Http::TestHeaderMapImpl{{":method", "POST"},
                                {"cookie", "foo=bar"},
                                {":path", "/test/long/url"},
                                {":scheme", "http"},
                                {":authority", "host"}},
        [&](IntegrationStreamDecoder& response) {
          EXPECT_STREQ("200", response.headers().Status()->value().c_str());
          EXPECT_TRUE(response.headers().get(Http::Headers::get().SetCookie) == nullptr);
          served_by.insert(
              response.headers().get(Http::LowerCaseString("x-served-by"))->value().c_str());
        });
    EXPECT_EQ(served_by.size(), 1);
  }
  
  TEST_P(Http2RingHashIntegrationTest, CookieRoutingWithCookieWithTtlSet) {
    config_helper_.addConfigModifier(
        [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm)
            -> void {
          auto* hash_policy = hcm.mutable_route_config()
                                  ->mutable_virtual_hosts(0)
                                  ->mutable_routes(0)
                                  ->mutable_route()
                                  ->add_hash_policy();
          auto* cookie = hash_policy->mutable_cookie();
          cookie->set_name("foo");
          cookie->mutable_ttl()->set_seconds(15);
        });
  
    std::set<std::string> served_by;
    sendMultipleRequests(
        1024,
        Http::TestHeaderMapImpl{{":method", "POST"},
                                {"cookie", "foo=bar"},
                                {":path", "/test/long/url"},
                                {":scheme", "http"},
                                {":authority", "host"}},
        [&](IntegrationStreamDecoder& response) {
          EXPECT_STREQ("200", response.headers().Status()->value().c_str());
          EXPECT_TRUE(response.headers().get(Http::Headers::get().SetCookie) == nullptr);
          served_by.insert(
              response.headers().get(Http::LowerCaseString("x-served-by"))->value().c_str());
        });
    EXPECT_EQ(served_by.size(), 1);
  }
  
  } // namespace Envoy
  