text: // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "net/spdy/spdy_session_pool.h"
  
  #include <cstddef>
  #include <utility>
  
  #include "base/bind.h"
  #include "base/memory/ref_counted.h"
  #include "base/run_loop.h"
  #include "base/stl_util.h"
  #include "base/test/bind_test_util.h"
  #include "base/test/metrics/histogram_tester.h"
  #include "base/trace_event/memory_allocator_dump.h"
  #include "base/trace_event/process_memory_dump.h"
  #include "base/trace_event/traced_value.h"
  #include "build/build_config.h"
  #include "net/dns/host_cache.h"
  #include "net/http/http_network_session.h"
  #include "net/log/net_log_with_source.h"
  #include "net/log/test_net_log.h"
  #include "net/socket/client_socket_handle.h"
  #include "net/socket/socket_tag.h"
  #include "net/socket/transport_client_socket_pool.h"
  #include "net/spdy/spdy_session.h"
  #include "net/spdy/spdy_stream_test_util.h"
  #include "net/spdy/spdy_test_util_common.h"
  #include "net/test/cert_test_util.h"
  #include "net/test/gtest_util.h"
  #include "net/test/test_certificate_data.h"
  #include "net/test/test_data_directory.h"
  #include "net/test/test_with_task_environment.h"
  #include "testing/gmock/include/gmock/gmock.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  using base::trace_event::MemoryAllocatorDump;
  using net::test::IsError;
  using net::test::IsOk;
  using testing::Contains;
  using testing::Eq;
  using testing::Contains;
  using testing::ByRef;
  
  namespace net {
  
  class SpdySessionPoolTest : public TestWithTaskEnvironment {
   protected:
    // Used by RunIPPoolingTest().
    enum SpdyPoolCloseSessionsType {
      SPDY_POOL_CLOSE_SESSIONS_MANUALLY,
      SPDY_POOL_CLOSE_CURRENT_SESSIONS,
      SPDY_POOL_CLOSE_IDLE_SESSIONS,
    };
  
    SpdySessionPoolTest() : spdy_session_pool_(nullptr) {}
  
    void CreateNetworkSession() {
      http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
      spdy_session_pool_ = http_session_->spdy_session_pool();
    }
  
    void AddSSLSocketData() {
      auto ssl = std::make_unique<SSLSocketDataProvider>(SYNCHRONOUS, OK);
      ssl->ssl_info.cert =
          ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
      ASSERT_TRUE(ssl->ssl_info.cert);
      session_deps_.socket_factory->AddSSLSocketDataProvider(ssl.get());
      ssl_data_vector_.push_back(std::move(ssl));
    }
  
    void RunIPPoolingTest(SpdyPoolCloseSessionsType close_sessions_type);
    void RunIPPoolingDisabledTest(SSLSocketDataProvider* ssl);
  
    size_t num_active_streams(base::WeakPtr<SpdySession> session) {
      return session->active_streams_.size();
    }
  
    SpdySessionDependencies session_deps_;
    std::unique_ptr<HttpNetworkSession> http_session_;
    SpdySessionPool* spdy_session_pool_;
    std::vector<std::unique_ptr<SSLSocketDataProvider>> ssl_data_vector_;
  };
  
  class SpdySessionRequestDelegate
      : public SpdySessionPool::SpdySessionRequest::Delegate {
   public:
    SpdySessionRequestDelegate() = default;
    ~SpdySessionRequestDelegate() override = default;
  
    void OnSpdySessionAvailable(
        base::WeakPtr<SpdySession> spdy_session) override {
      EXPECT_FALSE(callback_invoked_);
      callback_invoked_ = true;
      spdy_session_ = spdy_session;
    }
  
    bool callback_invoked() const { return callback_invoked_; }
  
    SpdySession* spdy_session() { return spdy_session_.get(); }
  
   private:
    bool callback_invoked_ = false;
    base::WeakPtr<SpdySession> spdy_session_;
  
    DISALLOW_COPY_AND_ASSIGN(SpdySessionRequestDelegate);
  };
  
  // Attempts to set up an alias for |key| using an already existing session in
  // |pool|. To do this, simulates a host resolution that returns
  // |ip_address_list|.
  bool TryCreateAliasedSpdySession(SpdySessionPool* pool,
                                   const SpdySessionKey& key,
                                   const std::string& ip_address_list,
                                   bool enable_ip_based_pooling = true,
                                   bool is_websocket = false) {
    // The requested session must not already exist.
    EXPECT_FALSE(pool->FindAvailableSession(key, enable_ip_based_pooling,
                                            is_websocket, NetLogWithSource()));
  
    // Create a request for the session. There should be no matching session
    // (aliased or otherwise) yet. A pending request is necessary for the session
    // to create an alias on host resolution completion.
    std::unique_ptr<SpdySessionPool::SpdySessionRequest> request;
    bool is_blocking_request_for_session = false;
    SpdySessionRequestDelegate request_delegate;
    EXPECT_FALSE(pool->RequestSession(
        key, enable_ip_based_pooling, is_websocket, NetLogWithSource(),
        /* on_blocking_request_destroyed_callback = */ base::RepeatingClosure(),
        &request_delegate, &request, &is_blocking_request_for_session));
    EXPECT_TRUE(request);
    EXPECT_TRUE(is_blocking_request_for_session);
  
    AddressList address_list;
    EXPECT_THAT(
        ParseAddressList(ip_address_list, /* canonical_name = */ std::string(),
                         &address_list),
        IsOk());
    address_list = AddressList::CopyWithPort(address_list, 443);
  
    // Simulate a host resolution completing.
    OnHostResolutionCallbackResult result =
        pool->OnHostResolutionComplete(key, is_websocket, address_list);
  
    // Spin the message loop and see if it creates an H2 session.
    base::RunLoop().RunUntilIdle();
    EXPECT_EQ(request_delegate.callback_invoked(),
              result == OnHostResolutionCallbackResult::kMayBeDeletedAsync);
    EXPECT_EQ(request_delegate.callback_invoked(),
              request_delegate.spdy_session() != nullptr);
    request.reset();
  
    // Calling RequestSession again should return request_delegate.spdy_session()
    // (i.e. the newly created session, if a session was created, or nullptr, if
    // one was not.)
    EXPECT_EQ(request_delegate.spdy_session(),
              pool->RequestSession(key, enable_ip_based_pooling, is_websocket,
                                   NetLogWithSource(),
                                   /* on_blocking_request_destroyed_callback = */
                                   base::RepeatingClosure(), &request_delegate,
                                   &request, &is_blocking_request_for_session)
                  .get());
  
    return request_delegate.spdy_session() != nullptr;
  }
  
  // A delegate that opens a new session when it is closed.
  class SessionOpeningDelegate : public SpdyStream::Delegate {
   public:
    SessionOpeningDelegate(SpdySessionPool* spdy_session_pool,
                           const SpdySessionKey& key)
        : spdy_session_pool_(spdy_session_pool),
          key_(key) {}
  
    ~SessionOpeningDelegate() override = default;
  
    void OnHeadersSent() override {}
  
    void OnHeadersReceived(
        const spdy::SpdyHeaderBlock& response_headers,
        const spdy::SpdyHeaderBlock* pushed_request_headers) override {}
  
    void OnDataReceived(std::unique_ptr<SpdyBuffer> buffer) override {}
  
    void OnDataSent() override {}
  
    void OnTrailers(const spdy::SpdyHeaderBlock& trailers) override {}
  
    void OnClose(int status) override {
      ignore_result(CreateFakeSpdySession(spdy_session_pool_, key_));
    }
  
    NetLogSource source_dependency() const override { return NetLogSource(); }
  
   private:
    SpdySessionPool* const spdy_session_pool_;
    const SpdySessionKey key_;
  };
  
  // Set up a SpdyStream to create a new session when it is closed.
  // CloseCurrentSessions should not close the newly-created session.
  TEST_F(SpdySessionPoolTest, CloseCurrentSessions) {
    const char kTestHost[] = "www.foo.com";
    const int kTestPort = 80;
  
    HostPortPair test_host_port_pair(kTestHost, kTestPort);
    SpdySessionKey test_key = SpdySessionKey(
        test_host_port_pair, ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
        SpdySessionKey::IsProxySession::kFalse, SocketTag(),
        NetworkIsolationKey(), false /* disable_secure_dns */);
  
    MockConnect connect_data(SYNCHRONOUS, OK);
    MockRead reads[] = {
      MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
    };
  
    StaticSocketDataProvider data(reads, base::span<MockWrite>());
    data.set_connect_data(connect_data);
    session_deps_.socket_factory->AddSocketDataProvider(&data);
  
    SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
    session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
  
    CreateNetworkSession();
  
    // Setup the first session to the first host.
    base::WeakPtr<SpdySession> session =
        CreateSpdySession(http_session_.get(), test_key, NetLogWithSource());
  
    // Flush the SpdySession::OnReadComplete() task.
    base::RunLoop().RunUntilIdle();
  
    // Verify that we have sessions for everything.
    EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key));
  
    // Set the stream to create a new session when it is closed.
    base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
        SPDY_BIDIRECTIONAL_STREAM, session, GURL("http://www.foo.com"), MEDIUM,
        NetLogWithSource());
    SessionOpeningDelegate delegate(spdy_session_pool_, test_key);
    spdy_stream->SetDelegate(&delegate);
  
    // Close the current session.
    spdy_session_pool_->CloseCurrentSessions(ERR_ABORTED);
  
    EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key));
  }
  
  TEST_F(SpdySessionPoolTest, CloseCurrentIdleSessions) {
    MockConnect connect_data(SYNCHRONOUS, OK);
    MockRead reads[] = {
        MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
    };
  
    StaticSocketDataProvider data1(reads, base::span<MockWrite>());
    data1.set_connect_data(connect_data);
    session_deps_.socket_factory->AddSocketDataProvider(&data1);
  
    AddSSLSocketData();
    AddSSLSocketData();
    AddSSLSocketData();
  
    CreateNetworkSession();
  
    // Set up session 1
    const GURL url1("https://www.example.org");
    HostPortPair test_host_port_pair1(HostPortPair::FromURL(url1));
    SpdySessionKey key1(test_host_port_pair1, ProxyServer::Direct(),
                        PRIVACY_MODE_DISABLED,
                        SpdySessionKey::IsProxySession::kFalse, SocketTag(),
                        NetworkIsolationKey(), false /* disable_secure_dns */);
    base::WeakPtr<SpdySession> session1 =
        CreateSpdySession(http_session_.get(), key1, NetLogWithSource());
    base::WeakPtr<SpdyStream> spdy_stream1 = CreateStreamSynchronously(
        SPDY_BIDIRECTIONAL_STREAM, session1, url1, MEDIUM, NetLogWithSource());
    ASSERT_TRUE(spdy_stream1);
  
    // Set up session 2
    StaticSocketDataProvider data2(reads, base::span<MockWrite>());
    session_deps_.socket_factory->AddSocketDataProvider(&data2);
    const GURL url2("https://mail.example.org");
    HostPortPair test_host_port_pair2(HostPortPair::FromURL(url2));
    SpdySessionKey key2(test_host_port_pair2, ProxyServer::Direct(),
                        PRIVACY_MODE_DISABLED,
                        SpdySessionKey::IsProxySession::kFalse, SocketTag(),
                        NetworkIsolationKey(), false /* disable_secure_dns */);
    base::WeakPtr<SpdySession> session2 =
        CreateSpdySession(http_session_.get(), key2, NetLogWithSource());
    base::WeakPtr<SpdyStream> spdy_stream2 = CreateStreamSynchronously(
        SPDY_BIDIRECTIONAL_STREAM, session2, url2, MEDIUM, NetLogWithSource());
    ASSERT_TRUE(spdy_stream2);
  
    // Set up session 3
    StaticSocketDataProvider data3(reads, base::span<MockWrite>());
    data3.set_connect_data(connect_data);
    session_deps_.socket_factory->AddSocketDataProvider(&data3);
    const GURL url3("https://mail.example.com");
    HostPortPair test_host_port_pair3(HostPortPair::FromURL(url3));
    SpdySessionKey key3(test_host_port_pair3, ProxyServer::Direct(),
                        PRIVACY_MODE_DISABLED,
                        SpdySessionKey::IsProxySession::kFalse, SocketTag(),
                        NetworkIsolationKey(), false /* disable_secure_dns */);
    base::WeakPtr<SpdySession> session3 =
        CreateSpdySession(http_session_.get(), key3, NetLogWithSource());
    base::WeakPtr<SpdyStream> spdy_stream3 = CreateStreamSynchronously(
        SPDY_BIDIRECTIONAL_STREAM, session3, url3, MEDIUM, NetLogWithSource());
    ASSERT_TRUE(spdy_stream3);
  
    // All sessions are active and not closed
    EXPECT_TRUE(session1->is_active());
    EXPECT_TRUE(session1->IsAvailable());
    EXPECT_TRUE(session2->is_active());
    EXPECT_TRUE(session2->IsAvailable());
    EXPECT_TRUE(session3->is_active());
    EXPECT_TRUE(session3->IsAvailable());
  
    // Should not do anything, all are active
    spdy_session_pool_->CloseCurrentIdleSessions();
    EXPECT_TRUE(session1->is_active());
    EXPECT_TRUE(session1->IsAvailable());
    EXPECT_TRUE(session2->is_active());
    EXPECT_TRUE(session2->IsAvailable());
    EXPECT_TRUE(session3->is_active());
    EXPECT_TRUE(session3->IsAvailable());
  
    // Make sessions 1 and 3 inactive, but keep them open.
    // Session 2 still open and active
    session1->CloseCreatedStream(spdy_stream1, OK);
    EXPECT_FALSE(spdy_stream1);
    session3->CloseCreatedStream(spdy_stream3, OK);
    EXPECT_FALSE(spdy_stream3);
    EXPECT_FALSE(session1->is_active());
    EXPECT_TRUE(session1->IsAvailable());
    EXPECT_TRUE(session2->is_active());
    EXPECT_TRUE(session2->IsAvailable());
    EXPECT_FALSE(session3->is_active());
    EXPECT_TRUE(session3->IsAvailable());
  
    // Should close session 1 and 3, 2 should be left open
    spdy_session_pool_->CloseCurrentIdleSessions();
    base::RunLoop().RunUntilIdle();
  
    EXPECT_FALSE(session1);
    EXPECT_TRUE(session2->is_active());
    EXPECT_TRUE(session2->IsAvailable());
    EXPECT_FALSE(session3);
  
    // Should not do anything
    spdy_session_pool_->CloseCurrentIdleSessions();
    base::RunLoop().RunUntilIdle();
  
    EXPECT_TRUE(session2->is_active());
    EXPECT_TRUE(session2->IsAvailable());
  
    // Make 2 not active
    session2->CloseCreatedStream(spdy_stream2, OK);
    base::RunLoop().RunUntilIdle();
  
    EXPECT_FALSE(spdy_stream2);
    EXPECT_FALSE(session2->is_active());
    EXPECT_TRUE(session2->IsAvailable());
  
    // This should close session 2
    spdy_session_pool_->CloseCurrentIdleSessions();
    base::RunLoop().RunUntilIdle();
  
    EXPECT_FALSE(session2);
  }
  
  // Set up a SpdyStream to create a new session when it is closed.
  // CloseAllSessions should close the newly-created session.
  TEST_F(SpdySessionPoolTest, CloseAllSessions) {
    const char kTestHost[] = "www.foo.com";
    const int kTestPort = 80;
  
    HostPortPair test_host_port_pair(kTestHost, kTestPort);
    SpdySessionKey test_key = SpdySessionKey(
        test_host_port_pair, ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
        SpdySessionKey::IsProxySession::kFalse, SocketTag(),
        NetworkIsolationKey(), false /* disable_secure_dns */);
  
    MockConnect connect_data(SYNCHRONOUS, OK);
    MockRead reads[] = {
      MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
    };
  
    StaticSocketDataProvider data(reads, base::span<MockWrite>());
    data.set_connect_data(connect_data);
    session_deps_.socket_factory->AddSocketDataProvider(&data);
  
    SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
    session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
  
    CreateNetworkSession();
  
    // Setup the first session to the first host.
    base::WeakPtr<SpdySession> session =
        CreateSpdySession(http_session_.get(), test_key, NetLogWithSource());
  
    // Flush the SpdySession::OnReadComplete() task.
    base::RunLoop().RunUntilIdle();
  
    // Verify that we have sessions for everything.
    EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key));
  
    // Set the stream to create a new session when it is closed.
    base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
        SPDY_BIDIRECTIONAL_STREAM, session, GURL("http://www.foo.com"), MEDIUM,
        NetLogWithSource());
    SessionOpeningDelegate delegate(spdy_session_pool_, test_key);
    spdy_stream->SetDelegate(&delegate);
  
    // Close the current session.
    spdy_session_pool_->CloseAllSessions();
  
    EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_key));
  }
  
  // This test has three variants, one for each style of closing the connection.
  // If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_SESSIONS_MANUALLY,
  // the sessions are closed manually, calling SpdySessionPool::Remove() directly.
  // If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_CURRENT_SESSIONS,
  // sessions are closed with SpdySessionPool::CloseCurrentSessions().
  // If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_IDLE_SESSIONS,
  // sessions are closed with SpdySessionPool::CloseIdleSessions().
  void SpdySessionPoolTest::RunIPPoolingTest(
      SpdyPoolCloseSessionsType close_sessions_type) {
    const int kTestPort = 443;
    struct TestHosts {
      std::string url;
      std::string name;
      std::string iplist;
      SpdySessionKey key;
    } test_hosts[] = {
        {"http://www.example.org", "www.example.org",
         "192.0.2.33,192.168.0.1,192.168.0.5"},
        {"http://mail.example.org", "mail.example.org",
         "192.168.0.2,192.168.0.3,192.168.0.5,192.0.2.33"},
        {"http://mail.example.com", "mail.example.com",
         "192.168.0.4,192.168.0.3"},
    };
  
    for (size_t i = 0; i < base::size(test_hosts); i++) {
      session_deps_.host_resolver->rules()->AddIPLiteralRule(
          test_hosts[i].name, test_hosts[i].iplist, std::string());
  
      test_hosts[i].key = SpdySessionKey(
          HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(),
          PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
          SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */);
    }
  
    MockConnect connect_data(SYNCHRONOUS, OK);
    MockRead reads[] = {
      MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
    };
  
    StaticSocketDataProvider data1(reads, base::span<MockWrite>());
    data1.set_connect_data(connect_data);
    session_deps_.socket_factory->AddSocketDataProvider(&data1);
  
    AddSSLSocketData();
  
    CreateNetworkSession();
  
    // Setup the first session to the first host.
    base::WeakPtr<SpdySession> session = CreateSpdySession(
        http_session_.get(), test_hosts[0].key, NetLogWithSource());
  
    // Flush the SpdySession::OnReadComplete() task.
    base::RunLoop().RunUntilIdle();
  
    // The third host has no overlap with the first, so it can't pool IPs.
    EXPECT_FALSE(TryCreateAliasedSpdySession(
        spdy_session_pool_, test_hosts[2].key, test_hosts[2].iplist));
  
    // The second host overlaps with the first, and should IP pool.
    EXPECT_TRUE(TryCreateAliasedSpdySession(spdy_session_pool_, test_hosts[1].key,
                                            test_hosts[1].iplist));
  
    // However, if IP pooling is disabled, FindAvailableSession() should not find
    // |session| for the second host.
    base::WeakPtr<SpdySession> session1 =
        spdy_session_pool_->FindAvailableSession(
            test_hosts[1].key, /* enable_ip_based_pooling = */ false,
            /* is_websocket = */ false, NetLogWithSource());
    EXPECT_FALSE(session1);
  
    // Verify that the second host, through a proxy, won't share the IP, even if
    // the IP list matches.
    SpdySessionKey proxy_key(
        test_hosts[1].key.host_port_pair(),
        ProxyServer::FromPacString("HTTP http://proxy.foo.com/"),
        PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
        SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */);
    EXPECT_FALSE(TryCreateAliasedSpdySession(spdy_session_pool_, proxy_key,
                                             test_hosts[1].iplist));
  
    // Verify that the second host, with a different disable_secure_dns value,
    // won't share the IP, even if the IP list matches.
    SpdySessionKey disable_secure_dns_key(
        test_hosts[1].key.host_port_pair(), ProxyServer::Direct(),
        PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
        SocketTag(), NetworkIsolationKey(), true /* disable_secure_dns */);
    EXPECT_FALSE(TryCreateAliasedSpdySession(
        spdy_session_pool_, disable_secure_dns_key, test_hosts[1].iplist));
  
    // Overlap between 2 and 3 is not transitive to 1.
    EXPECT_FALSE(TryCreateAliasedSpdySession(
        spdy_session_pool_, test_hosts[2].key, test_hosts[2].iplist));
  
    // Create a new session to host 2.
    StaticSocketDataProvider data2(reads, base::span<MockWrite>());
    data2.set_connect_data(connect_data);
    session_deps_.socket_factory->AddSocketDataProvider(&data2);
  
    AddSSLSocketData();
  
    base::WeakPtr<SpdySession> session2 = CreateSpdySession(
        http_session_.get(), test_hosts[2].key, NetLogWithSource());
  
    // Verify that we have sessions for everything.
    EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[0].key));
    EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key));
    EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[2].key));
  
    // Grab the session to host 1 and verify that it is the same session
    // we got with host 0, and that is a different from host 2's session.
    session1 = spdy_session_pool_->FindAvailableSession(
        test_hosts[1].key, /* enable_ip_based_pooling = */ true,
        /* is_websocket = */ false, NetLogWithSource());
    EXPECT_EQ(session.get(), session1.get());
    EXPECT_NE(session2.get(), session1.get());
  
    // Remove the aliases and observe that we still have a session for host1.
    SpdySessionPoolPeer pool_peer(spdy_session_pool_);
    pool_peer.RemoveAliases(test_hosts[0].key);
    pool_peer.RemoveAliases(test_hosts[1].key);
    EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key));
  
    // Cleanup the sessions.
    switch (close_sessions_type) {
      case SPDY_POOL_CLOSE_SESSIONS_MANUALLY:
        session->CloseSessionOnError(ERR_ABORTED, std::string());
        session2->CloseSessionOnError(ERR_ABORTED, std::string());
        base::RunLoop().RunUntilIdle();
        EXPECT_FALSE(session);
        EXPECT_FALSE(session2);
        break;
      case SPDY_POOL_CLOSE_CURRENT_SESSIONS:
        spdy_session_pool_->CloseCurrentSessions(ERR_ABORTED);
        break;
      case SPDY_POOL_CLOSE_IDLE_SESSIONS:
        GURL url(test_hosts[0].url);
        base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
            SPDY_BIDIRECTIONAL_STREAM, session, url, MEDIUM, NetLogWithSource());
        GURL url1(test_hosts[1].url);
        base::WeakPtr<SpdyStream> spdy_stream1 =
            CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session1, url1,
                                      MEDIUM, NetLogWithSource());
        GURL url2(test_hosts[2].url);
        base::WeakPtr<SpdyStream> spdy_stream2 =
            CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session2, url2,
                                      MEDIUM, NetLogWithSource());
  
        // Close streams to make spdy_session and spdy_session1 inactive.
        session->CloseCreatedStream(spdy_stream, OK);
        EXPECT_FALSE(spdy_stream);
        session1->CloseCreatedStream(spdy_stream1, OK);
        EXPECT_FALSE(spdy_stream1);
  
        // Check spdy_session and spdy_session1 are not closed.
        EXPECT_FALSE(session->is_active());
        EXPECT_TRUE(session->IsAvailable());
        EXPECT_FALSE(session1->is_active());
        EXPECT_TRUE(session1->IsAvailable());
        EXPECT_TRUE(session2->is_active());
        EXPECT_TRUE(session2->IsAvailable());
  
        // Test that calling CloseIdleSessions, does not cause a crash.
        // http://crbug.com/181400
        spdy_session_pool_->CloseCurrentIdleSessions();
        base::RunLoop().RunUntilIdle();
  
        // Verify spdy_session and spdy_session1 are closed.
        EXPECT_FALSE(session);
        EXPECT_FALSE(session1);
        EXPECT_TRUE(session2->is_active());
        EXPECT_TRUE(session2->IsAvailable());
  
        spdy_stream2->Cancel(ERR_ABORTED);
        EXPECT_FALSE(spdy_stream);
        EXPECT_FALSE(spdy_stream1);
        EXPECT_FALSE(spdy_stream2);
  
        session2->CloseSessionOnError(ERR_ABORTED, std::string());
        base::RunLoop().RunUntilIdle();
        EXPECT_FALSE(session2);
        break;
    }
  
    // Verify that the map is all cleaned up.
    EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[0].key));
    EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[1].key));
    EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[2].key));
    EXPECT_FALSE(TryCreateAliasedSpdySession(
        spdy_session_pool_, test_hosts[0].key, test_hosts[0].iplist));
    EXPECT_FALSE(TryCreateAliasedSpdySession(
        spdy_session_pool_, test_hosts[1].key, test_hosts[1].iplist));
    EXPECT_FALSE(TryCreateAliasedSpdySession(
        spdy_session_pool_, test_hosts[2].key, test_hosts[2].iplist));
  }
  
  void SpdySessionPoolTest::RunIPPoolingDisabledTest(SSLSocketDataProvider* ssl) {
    const int kTestPort = 443;
    struct TestHosts {
      std::string name;
      std::string iplist;
      SpdySessionKey key;
    } test_hosts[] = {
        {"www.webkit.org", "192.0.2.33,192.168.0.1,192.168.0.5"},
        {"js.webkit.com", "192.168.0.4,192.168.0.1,192.0.2.33"},
    };
  
    session_deps_.host_resolver->set_synchronous_mode(true);
    for (size_t i = 0; i < base::size(test_hosts); i++) {
      session_deps_.host_resolver->rules()->AddIPLiteralRule(
          test_hosts[i].name, test_hosts[i].iplist, std::string());
  
      // Setup a SpdySessionKey
      test_hosts[i].key = SpdySessionKey(
          HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(),
          PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
          SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */);
    }
  
    MockRead reads[] = {
        MockRead(ASYNC, ERR_IO_PENDING),
    };
    StaticSocketDataProvider data(reads, base::span<MockWrite>());
    session_deps_.socket_factory->AddSocketDataProvider(&data);
    session_deps_.socket_factory->AddSSLSocketDataProvider(ssl);
  
    CreateNetworkSession();
  
    base::WeakPtr<SpdySession> spdy_session = CreateSpdySession(
        http_session_.get(), test_hosts[0].key, NetLogWithSource());
    EXPECT_TRUE(
        HasSpdySession(http_session_->spdy_session_pool(), test_hosts[0].key));
    EXPECT_FALSE(TryCreateAliasedSpdySession(
        spdy_session_pool_, test_hosts[1].key, test_hosts[1].iplist,
        /* enable_ip_based_pooling = */ false));
  
    http_session_->spdy_session_pool()->CloseAllSessions();
  }
  
  TEST_F(SpdySessionPoolTest, IPPooling) {
    RunIPPoolingTest(SPDY_POOL_CLOSE_SESSIONS_MANUALLY);
  }
  
  TEST_F(SpdySessionPoolTest, IPPoolingCloseCurrentSessions) {
    RunIPPoolingTest(SPDY_POOL_CLOSE_CURRENT_SESSIONS);
  }
  
  TEST_F(SpdySessionPoolTest, IPPoolingCloseIdleSessions) {
    RunIPPoolingTest(SPDY_POOL_CLOSE_IDLE_SESSIONS);
  }
  
  // Regression test for https://crbug.com/643025.
  TEST_F(SpdySessionPoolTest, IPPoolingNetLog) {
    // Define two hosts with identical IP address.
    const int kTestPort = 443;
    struct TestHosts {
      std::string name;
      std::string iplist;
      SpdySessionKey key;
    } test_hosts[] = {
        {"www.example.org", "192.168.0.1"}, {"mail.example.org", "192.168.0.1"},
    };
  
    // Populate the HostResolver cache.
    session_deps_.host_resolver->set_synchronous_mode(true);
    for (size_t i = 0; i < base::size(test_hosts); i++) {
      session_deps_.host_resolver->rules()->AddIPLiteralRule(
          test_hosts[i].name, test_hosts[i].iplist, std::string());
  
      test_hosts[i].key = SpdySessionKey(
          HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(),
          PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
          SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */);
    }
  
    MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
    StaticSocketDataProvider data(reads, base::span<MockWrite>());
    MockConnect connect_data(SYNCHRONOUS, OK);
    data.set_connect_data(connect_data);
  
    session_deps_.socket_factory->AddSocketDataProvider(&data);
    AddSSLSocketData();
  
    CreateNetworkSession();
  
    // Open SpdySession to the first host.
    base::WeakPtr<SpdySession> session0 = CreateSpdySession(
        http_session_.get(), test_hosts[0].key, NetLogWithSource());
  
    // The second host should pool to the existing connection.
    BoundTestNetLog net_log;
    base::HistogramTester histogram_tester;
    EXPECT_TRUE(TryCreateAliasedSpdySession(spdy_session_pool_, test_hosts[1].key,
                                            test_hosts[1].iplist));
    histogram_tester.ExpectTotalCount("Net.SpdySessionGet", 1);
  
    base::WeakPtr<SpdySession> session1 =
        spdy_session_pool_->FindAvailableSession(
            test_hosts[1].key, /* enable_ip_based_pooling = */ true,
            /* is_websocket = */ false, net_log.bound());
    EXPECT_EQ(session0.get(), session1.get());
  
    ASSERT_EQ(1u, net_log.GetSize());
    histogram_tester.ExpectTotalCount("Net.SpdySessionGet", 2);
  
    // FindAvailableSession() should have logged a netlog event indicating IP
    // pooling.
    auto entry_list = net_log.GetEntries();
    EXPECT_EQ(
        NetLogEventType::HTTP2_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL,
        entry_list[0].type);
  
    // Both FindAvailableSession() calls (including one from
    // TryCreateAliasedSpdySession) should log histogram entries indicating IP
    // pooling.
    histogram_tester.ExpectUniqueSample("Net.SpdySessionGet", 2, 2);
  }
  
  TEST_F(SpdySessionPoolTest, IPPoolingDisabled) {
    // Define two hosts with identical IP address.
    const int kTestPort = 443;
    struct TestHosts {
      std::string name;
      std::string iplist;
      SpdySessionKey key;
    } test_hosts[] = {
        {"www.example.org", "192.168.0.1"}, {"mail.example.org", "192.168.0.1"},
    };
  
    // Populate the HostResolver cache.
    session_deps_.host_resolver->set_synchronous_mode(true);
    for (size_t i = 0; i < base::size(test_hosts); i++) {
      session_deps_.host_resolver->rules()->AddIPLiteralRule(
          test_hosts[i].name, test_hosts[i].iplist, std::string());
  
      test_hosts[i].key = SpdySessionKey(
          HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(),
          PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
          SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */);
    }
  
    MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
    StaticSocketDataProvider data(reads, base::span<MockWrite>());
    MockConnect connect_data(SYNCHRONOUS, OK);
    data.set_connect_data(connect_data);
    session_deps_.socket_factory->AddSocketDataProvider(&data);
    AddSSLSocketData();
  
    MockRead reads1[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
    StaticSocketDataProvider data1(reads1, base::span<MockWrite>());
    MockConnect connect_data1(SYNCHRONOUS, OK);
    data1.set_connect_data(connect_data1);
    session_deps_.socket_factory->AddSocketDataProvider(&data1);
    AddSSLSocketData();
  
    CreateNetworkSession();
  
    // Open SpdySession to the first host.
    base::WeakPtr<SpdySession> session0 = CreateSpdySession(
        http_session_.get(), test_hosts[0].key, NetLogWithSource());
  
    // |test_hosts[1]| should pool to the existing connection.
    EXPECT_TRUE(TryCreateAliasedSpdySession(spdy_session_pool_, test_hosts[1].key,
                                            test_hosts[1].iplist));
    base::WeakPtr<SpdySession> session1 =
        spdy_session_pool_->FindAvailableSession(
            test_hosts[1].key, /* enable_ip_based_pooling = */ true,
            /* is_websocket = */ false, NetLogWithSource());
    EXPECT_EQ(session0.get(), session1.get());
  
    // A request to the second host should not pool to the existing connection if
    // IP based pooling is disabled.
    session1 = spdy_session_pool_->FindAvailableSession(
        test_hosts[1].key, /* enable_ip_based_pooling = */ false,
        /* is_websocket = */ false, NetLogWithSource());
    EXPECT_FALSE(session1);
  
    // It should be possible to open a new SpdySession, even if a previous call to
    // FindAvailableSession() linked the second key to the first connection in the
    // IP pooled bucket of SpdySessionPool::available_session_map_.
    session1 = CreateSpdySessionWithIpBasedPoolingDisabled(
        http_session_.get(), test_hosts[1].key, NetLogWithSource());
    EXPECT_TRUE(session1);
    EXPECT_NE(session0.get(), session1.get());
  }
  
  // Verifies that an SSL connection with client authentication disables SPDY IP
  // pooling.
  TEST_F(SpdySessionPoolTest, IPPoolingClientCert) {
    SSLSocketDataProvider ssl(ASYNC, OK);
    ssl.ssl_info.cert = X509Certificate::CreateFromBytes(
        reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der));
    ASSERT_TRUE(ssl.ssl_info.cert);
    ssl.ssl_info.client_cert_sent = true;
    ssl.next_proto = kProtoHTTP2;
    RunIPPoolingDisabledTest(&ssl);
  }
  
  // Construct a Pool with SpdySessions in various availability states. Simulate
  // an IP address change. Ensure sessions gracefully shut down. Regression test
  // for crbug.com/379469.
  TEST_F(SpdySessionPoolTest, IPAddressChanged) {
    MockConnect connect_data(SYNCHRONOUS, OK);
    session_deps_.host_resolver->set_synchronous_mode(true);
  
    // This isn't testing anything having to do with SPDY frames; we
    // can ignore issues of how dependencies are set.  We default to
    // setting them (when doing the appropriate protocol) since that's
    // where we're eventually headed for all HTTP/2 connections.
    SpdyTestUtil spdy_util;
  
    MockRead reads[] = {
        MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
    };
    spdy::SpdySerializedFrame req(
        spdy_util.ConstructSpdyGet("http://www.example.org", 1, MEDIUM));
    MockWrite writes[] = {CreateMockWrite(req, 1)};
  
    StaticSocketDataProvider dataA(reads, writes);
    dataA.set_connect_data(connect_data);
    session_deps_.socket_factory->AddSocketDataProvider(&dataA);
  
    AddSSLSocketData();
  
    CreateNetworkSession();
  
    // Set up session A: Going away, but with an active stream.
    const std::string kTestHostA("www.example.org");
    HostPortPair test_host_port_pairA(kTestHostA, 80);
    SpdySessionKey keyA(test_host_port_pairA, ProxyServer::Direct(),
                        PRIVACY_MODE_DISABLED,
                        SpdySessionKey::IsProxySession::kFalse, SocketTag(),
                        NetworkIsolationKey(), false /* disable_secure_dns */);
    base::WeakPtr<SpdySession> sessionA =
        CreateSpdySession(http_session_.get(), keyA, NetLogWithSource());
  
    GURL urlA("http://www.example.org");
    base::WeakPtr<SpdyStream> spdy_streamA = CreateStreamSynchronously(
        SPDY_BIDIRECTIONAL_STREAM, sessionA, urlA, MEDIUM, NetLogWithSource());
    test::StreamDelegateDoNothing delegateA(spdy_streamA);
    spdy_streamA->SetDelegate(&delegateA);
  
    spdy::SpdyHeaderBlock headers(spdy_util.ConstructGetHeaderBlock(urlA.spec()));
    spdy_streamA->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND);
  
    base::RunLoop().RunUntilIdle();  // Allow headers to write.
    EXPECT_TRUE(delegateA.send_headers_completed());
  
    sessionA->MakeUnavailable();
    EXPECT_TRUE(sessionA->IsGoingAway());
    EXPECT_FALSE(delegateA.StreamIsClosed());
  
    // Set up session B: Available, with a created stream.
    StaticSocketDataProvider dataB(reads, writes);
    dataB.set_connect_data(connect_data);
    session_deps_.socket_factory->AddSocketDataProvider(&dataB);
  
    AddSSLSocketData();
  
    const std::string kTestHostB("mail.example.org");
    HostPortPair test_host_port_pairB(kTestHostB, 80);
    SpdySessionKey keyB(test_host_port_pairB, ProxyServer::Direct(),
                        PRIVACY_MODE_DISABLED,
                        SpdySessionKey::IsProxySession::kFalse, SocketTag(),
                        NetworkIsolationKey(), false /* disable_secure_dns */);
    base::WeakPtr<SpdySession> sessionB =
        CreateSpdySession(http_session_.get(), keyB, NetLogWithSource());
    EXPECT_TRUE(sessionB->IsAvailable());
  
    GURL urlB("http://mail.example.org");
    base::WeakPtr<SpdyStream> spdy_streamB = CreateStreamSynchronously(
        SPDY_BIDIRECTIONAL_STREAM, sessionB, urlB, MEDIUM, NetLogWithSource());
    test::StreamDelegateDoNothing delegateB(spdy_streamB);
    spdy_streamB->SetDelegate(&delegateB);
  
    // Set up session C: Draining.
    StaticSocketDataProvider dataC(reads, writes);
    dataC.set_connect_data(connect_data);
    session_deps_.socket_factory->AddSocketDataProvider(&dataC);
  
    AddSSLSocketData();
  
    const std::string kTestHostC("mail.example.com");
    HostPortPair test_host_port_pairC(kTestHostC, 80);
    SpdySessionKey keyC(test_host_port_pairC, ProxyServer::Direct(),
                        PRIVACY_MODE_DISABLED,
                        SpdySessionKey::IsProxySession::kFalse, SocketTag(),
                        NetworkIsolationKey(), false /* disable_secure_dns */);
    base::WeakPtr<SpdySession> sessionC =
        CreateSpdySession(http_session_.get(), keyC, NetLogWithSource());
  
    sessionC->CloseSessionOnError(ERR_HTTP2_PROTOCOL_ERROR, "Error!");
    EXPECT_TRUE(sessionC->IsDraining());
  
    spdy_session_pool_->OnIPAddressChanged();
  
  #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS)
    EXPECT_TRUE(sessionA->IsGoingAway());
    EXPECT_TRUE(sessionB->IsDraining());
    EXPECT_TRUE(sessionC->IsDraining());
  
    EXPECT_EQ(1u,
              num_active_streams(sessionA));  // Active stream is still active.
    EXPECT_FALSE(delegateA.StreamIsClosed());
  
    EXPECT_TRUE(delegateB.StreamIsClosed());  // Created stream was closed.
    EXPECT_THAT(delegateB.WaitForClose(), IsError(ERR_NETWORK_CHANGED));
  
    sessionA->CloseSessionOnError(ERR_ABORTED, "Closing");
    sessionB->CloseSessionOnError(ERR_ABORTED, "Closing");
  
    EXPECT_TRUE(delegateA.StreamIsClosed());
    EXPECT_THAT(delegateA.WaitForClose(), IsError(ERR_ABORTED));
  #else
    EXPECT_TRUE(sessionA->IsDraining());
    EXPECT_TRUE(sessionB->IsDraining());
    EXPECT_TRUE(sessionC->IsDraining());
  
    // Both streams were closed with an error.
    EXPECT_TRUE(delegateA.StreamIsClosed());
    EXPECT_THAT(delegateA.WaitForClose(), IsError(ERR_NETWORK_CHANGED));
    EXPECT_TRUE(delegateB.StreamIsClosed());
    EXPECT_THAT(delegateB.WaitForClose(), IsError(ERR_NETWORK_CHANGED));
  #endif  // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS)
  }
  
  // Regression test for https://crbug.com/789791.
  TEST_F(SpdySessionPoolTest, HandleIPAddressChangeThenShutdown) {
    MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
    SpdyTestUtil spdy_util;
    spdy::SpdySerializedFrame req(
        spdy_util.ConstructSpdyGet(kDefaultUrl, 1, MEDIUM));
    MockWrite writes[] = {CreateMockWrite(req, 1)};
    StaticSocketDataProvider data(reads, writes);
  
    MockConnect connect_data(SYNCHRONOUS, OK);
    data.set_connect_data(connect_data);
  
    session_deps_.socket_factory->AddSocketDataProvider(&data);
    AddSSLSocketData();
  
    CreateNetworkSession();
  
    const GURL url(kDefaultUrl);
    SpdySessionKey key(HostPortPair::FromURL(url), ProxyServer::Direct(),
                       PRIVACY_MODE_DISABLED,
                       SpdySessionKey::IsProxySession::kFalse, SocketTag(),
                       NetworkIsolationKey(), false /* disable_secure_dns */);
    base::WeakPtr<SpdySession> session =
        CreateSpdySession(http_session_.get(), key, NetLogWithSource());
  
    base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
        SPDY_BIDIRECTIONAL_STREAM, session, url, MEDIUM, NetLogWithSource());
    test::StreamDelegateDoNothing delegate(spdy_stream);
    spdy_stream->SetDelegate(&delegate);
  
    spdy::SpdyHeaderBlock headers(spdy_util.ConstructGetHeaderBlock(url.spec()));
    spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND);
  
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(delegate.send_headers_completed());
  
    spdy_session_pool_->OnIPAddressChanged();
  
  #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS)
    EXPECT_EQ(1u, num_active_streams(session));
    EXPECT_TRUE(session->IsGoingAway());
    EXPECT_FALSE(session->IsDraining());
  #else
    EXPECT_EQ(0u, num_active_streams(session));
    EXPECT_FALSE(session->IsGoingAway());
    EXPECT_TRUE(session->IsDraining());
  #endif  // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS)
  
    http_session_.reset();
  
    data.AllReadDataConsumed();
    data.AllWriteDataConsumed();
  }
  
  // Regression test for https://crbug.com/789791.
  TEST_F(SpdySessionPoolTest, HandleGracefulGoawayThenShutdown) {
    SpdyTestUtil spdy_util;
    spdy::SpdySerializedFrame goaway(spdy_util.ConstructSpdyGoAway(
        0x7fffffff, spdy::ERROR_CODE_NO_ERROR, "Graceful shutdown."));
    MockRead reads[] = {
        MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(goaway, 2),
        MockRead(ASYNC, ERR_IO_PENDING, 3), MockRead(ASYNC, OK, 4)};
    spdy::SpdySerializedFrame req(
        spdy_util.ConstructSpdyGet(kDefaultUrl, 1, MEDIUM));
    MockWrite writes[] = {CreateMockWrite(req, 0)};
    SequencedSocketData data(reads, writes);
  
    MockConnect connect_data(SYNCHRONOUS, OK);
    data.set_connect_data(connect_data);
  
    session_deps_.socket_factory->AddSocketDataProvider(&data);
    AddSSLSocketData();
  
    CreateNetworkSession();
  
    const GURL url(kDefaultUrl);
    SpdySessionKey key(HostPortPair::FromURL(url), ProxyServer::Direct(),
                       PRIVACY_MODE_DISABLED,
                       SpdySessionKey::IsProxySession::kFalse, SocketTag(),
                       NetworkIsolationKey(), false /* disable_secure_dns */);
    base::WeakPtr<SpdySession> session =
        CreateSpdySession(http_session_.get(), key, NetLogWithSource());
  
    base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
        SPDY_BIDIRECTIONAL_STREAM, session, url, MEDIUM, NetLogWithSource());
    test::StreamDelegateDoNothing delegate(spdy_stream);
    spdy_stream->SetDelegate(&delegate);
  
    spdy::SpdyHeaderBlock headers(spdy_util.ConstructGetHeaderBlock(url.spec()));
    spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND);
  
    // Send headers.
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(delegate.send_headers_completed());
  
    EXPECT_EQ(1u, num_active_streams(session));
    EXPECT_FALSE(session->IsGoingAway());
    EXPECT_FALSE(session->IsDraining());
  
    // Read GOAWAY.
    data.Resume();
    base::RunLoop().RunUntilIdle();
  
    EXPECT_EQ(1u, num_active_streams(session));
    EXPECT_TRUE(session->IsGoingAway());
    EXPECT_FALSE(session->IsDraining());
  
    http_session_.reset();
  
    data.AllReadDataConsumed();
    data.AllWriteDataConsumed();
  }
  
  class SpdySessionMemoryDumpTest
      : public SpdySessionPoolTest,
        public testing::WithParamInterface<
            base::trace_event::MemoryDumpLevelOfDetail> {};
  
  INSTANTIATE_TEST_SUITE_P(
      /* no prefix */,
      SpdySessionMemoryDumpTest,
      ::testing::Values(base::trace_event::MemoryDumpLevelOfDetail::DETAILED,
                        base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND));
  
  TEST_P(SpdySessionMemoryDumpTest, DumpMemoryStats) {
    SpdySessionKey key(HostPortPair("www.example.org", 443),
                       ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
                       SpdySessionKey::IsProxySession::kFalse, SocketTag(),
                       NetworkIsolationKey(), false /* disable_secure_dns */);
  
    MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
    StaticSocketDataProvider data(reads, base::span<MockWrite>());
    data.set_connect_data(MockConnect(SYNCHRONOUS, OK));
    session_deps_.socket_factory->AddSocketDataProvider(&data);
  
    SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
    session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
  
    CreateNetworkSession();
  
    base::WeakPtr<SpdySession> session =
        CreateSpdySession(http_session_.get(), key, NetLogWithSource());
  
    // Flush the SpdySession::OnReadComplete() task.
    base::RunLoop().RunUntilIdle();
  
    EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key));
    base::trace_event::MemoryDumpArgs dump_args = {GetParam()};
    auto process_memory_dump =
        std::make_unique<base::trace_event::ProcessMemoryDump>(dump_args);
    base::trace_event::MemoryAllocatorDump* parent_dump =
        process_memory_dump->CreateAllocatorDump(
            "net/http_network_session_0x123");
    spdy_session_pool_->DumpMemoryStats(process_memory_dump.get(),
                                        parent_dump->absolute_name());
  
    // Whether SpdySession::DumpMemoryStats() is invoked.
    bool did_dump = false;
    const base::trace_event::ProcessMemoryDump::AllocatorDumpsMap&
        allocator_dumps = process_memory_dump->allocator_dumps();
    for (const auto& pair : allocator_dumps) {
      const std::string& dump_name = pair.first;
      if (dump_name.find("spdy_session_pool") == std::string::npos)
        continue;
      MemoryAllocatorDump::Entry expected("active_session_count",
                                          MemoryAllocatorDump::kUnitsObjects, 0);
      ASSERT_THAT(pair.second->entries(), Contains(Eq(ByRef(expected))));
      did_dump = true;
    }
    EXPECT_TRUE(did_dump);
    spdy_session_pool_->CloseCurrentSessions(ERR_ABORTED);
  }
  
  TEST_F(SpdySessionPoolTest, IPConnectionPoolingWithWebSockets) {
    // Define two hosts with identical IP address.
    const int kTestPort = 443;
    struct TestHosts {
      std::string name;
      std::string iplist;
      SpdySessionKey key;
    } test_hosts[] = {
        {"www.example.org", "192.168.0.1"}, {"mail.example.org", "192.168.0.1"},
    };
  
    // Populate the HostResolver cache.
    session_deps_.host_resolver->set_synchronous_mode(true);
    for (size_t i = 0; i < base::size(test_hosts); i++) {
      session_deps_.host_resolver->rules()->AddIPLiteralRule(
          test_hosts[i].name, test_hosts[i].iplist, std::string());
  
      test_hosts[i].key = SpdySessionKey(
          HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(),
          PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
          SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */);
    }
  
    SpdyTestUtil spdy_util;
  
    spdy::SpdySerializedFrame req(
        spdy_util.ConstructSpdyGet(nullptr, 0, 1, LOWEST));
    spdy::SpdySerializedFrame settings_ack(spdy_util.ConstructSpdySettingsAck());
    MockWrite writes[] = {CreateMockWrite(req, 0),
                          CreateMockWrite(settings_ack, 2)};
  
    spdy::SettingsMap settings;
    settings[spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL] = 1;
    spdy::SpdySerializedFrame settings_frame(
        spdy_util.ConstructSpdySettings(settings));
    spdy::SpdySerializedFrame resp(
        spdy_util.ConstructSpdyGetReply(nullptr, 0, 1));
    spdy::SpdySerializedFrame body(spdy_util.ConstructSpdyDataFrame(1, true));
    MockRead reads[] = {CreateMockRead(settings_frame, 1),
                        CreateMockRead(resp, 3), CreateMockRead(body, 4),
                        MockRead(ASYNC, ERR_IO_PENDING, 5),
                        MockRead(ASYNC, 0, 6)};
  
    SequencedSocketData data(reads, writes);
    session_deps_.socket_factory->AddSocketDataProvider(&data);
    AddSSLSocketData();
    CreateNetworkSession();
  
    // Create a connection to the first host.
    base::WeakPtr<SpdySession> session = CreateSpdySession(
        http_session_.get(), test_hosts[0].key, NetLogWithSource());
  
    // SpdySession does not support Websocket before SETTINGS frame is read.
    EXPECT_FALSE(session->support_websocket());
    BoundTestNetLog net_log;
    // TryCreateAliasedSpdySession should not find |session| for either
    // SpdySessionKeys if |is_websocket| argument is set.
    EXPECT_FALSE(TryCreateAliasedSpdySession(
        spdy_session_pool_, test_hosts[0].key, test_hosts[0].iplist,
        /* enable_ip_based_pooling = */ true,
        /* is_websocket = */ true));
    EXPECT_FALSE(TryCreateAliasedSpdySession(
        spdy_session_pool_, test_hosts[1].key, test_hosts[1].iplist,
        /* enable_ip_based_pooling = */ true,
        /* is_websocket = */ true));
  
    // Start request that triggers reading the SETTINGS frame.
    const GURL url(kDefaultUrl);
    base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
        SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, NetLogWithSource());
    test::StreamDelegateDoNothing delegate(spdy_stream);
    spdy_stream->SetDelegate(&delegate);
  
    spdy::SpdyHeaderBlock headers(spdy_util.ConstructGetHeaderBlock(url.spec()));
    spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND);
  
    base::RunLoop().RunUntilIdle();
  
    // Now SpdySession has read the SETTINGS frame and thus supports Websocket.
    EXPECT_TRUE(session->support_websocket());
  
    // FindAvailableSession() on the first host should now find the existing
    // session with websockets enabled, and TryCreateAliasedSpdySession() should
    // now set up aliases for |session| for the second one.
    base::WeakPtr<SpdySession> result = spdy_session_pool_->FindAvailableSession(
        test_hosts[0].key, /* enable_ip_based_pooling = */ true,
        /* is_websocket = */ true, net_log.bound());
    EXPECT_EQ(session.get(), result.get());
    EXPECT_TRUE(TryCreateAliasedSpdySession(spdy_session_pool_, test_hosts[1].key,
                                            test_hosts[1].iplist,
                                            /* enable_ip_based_pooling = */ true,
                                            /* is_websocket = */ true));
  
    // FindAvailableSession() should return |session| for either SpdySessionKeys
    // when IP based pooling is enabled.
    result = spdy_session_pool_->FindAvailableSession(
        test_hosts[0].key, /* enable_ip_based_pooling = */ true,
        /* is_websocket = */ true, net_log.bound());
    EXPECT_EQ(session.get(), result.get());
    result = spdy_session_pool_->FindAvailableSession(
        test_hosts[1].key, /* enable_ip_based_pooling = */ true,
        /* is_websocket = */ true, net_log.bound());
    EXPECT_EQ(session.get(), result.get());
  
    // FindAvailableSession() should only return |session| for the first
    // SpdySessionKey when IP based pooling is disabled.
    result = spdy_session_pool_->FindAvailableSession(
        test_hosts[0].key, /* enable_ip_based_pooling = */ false,
        /* is_websocket = */ true, net_log.bound());
    EXPECT_EQ(session.get(), result.get());
    result = spdy_session_pool_->FindAvailableSession(
        test_hosts[1].key, /* enable_ip_based_pooling = */ false,
        /* is_websocket = */ true, net_log.bound());
    EXPECT_FALSE(result);
  
    // Read EOF.
    data.Resume();
    base::RunLoop().RunUntilIdle();
  
    EXPECT_TRUE(data.AllReadDataConsumed());
    EXPECT_TRUE(data.AllWriteDataConsumed());
  }
  
  class TestOnRequestDeletedCallback {
   public:
    TestOnRequestDeletedCallback() = default;
    ~TestOnRequestDeletedCallback() = default;
  
    base::RepeatingClosure Callback() {
      return base::BindRepeating(&TestOnRequestDeletedCallback::OnRequestDeleted,
                                 base::Unretained(this));
    }
  
    bool invoked() const { return invoked_; }
  
    void WaitUntilInvoked() { run_loop_.Run(); }
  
    void SetRequestDeletedCallback(base::OnceClosure request_deleted_callback) {
      DCHECK(!request_deleted_callback_);
      request_deleted_callback_ = std::move(request_deleted_callback);
    }
  
   private:
    void OnRequestDeleted() {
      EXPECT_FALSE(invoked_);
      invoked_ = true;
      if (request_deleted_callback_)
        std::move(request_deleted_callback_).Run();
      run_loop_.Quit();
    }
  
    bool invoked_ = false;
    base::RunLoop run_loop_;
  
    base::OnceClosure request_deleted_callback_;
  
    DISALLOW_COPY_AND_ASSIGN(TestOnRequestDeletedCallback);
  };
  
  class TestRequestDelegate
      : public SpdySessionPool::SpdySessionRequest::Delegate {
   public:
    TestRequestDelegate() = default;
    ~TestRequestDelegate() override = default;
  
    // SpdySessionPool::SpdySessionRequest::Delegate implementation:
    void OnSpdySessionAvailable(
        base::WeakPtr<SpdySession> spdy_session) override {}
  
   private:
    DISALLOW_COPY_AND_ASSIGN(TestRequestDelegate);
  };
  
  TEST_F(SpdySessionPoolTest, RequestSessionWithNoSessions) {
    const SpdySessionKey kSessionKey(
        HostPortPair("foo.test", 443), ProxyServer::Direct(),
        PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
        SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */);
  
    CreateNetworkSession();
  
    // First request. Its request deleted callback should never be invoked.
    TestOnRequestDeletedCallback request_deleted_callback1;
    TestRequestDelegate request_delegate1;
    std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request1;
    bool is_first_request_for_session;
    EXPECT_FALSE(spdy_session_pool_->RequestSession(
        kSessionKey, /* enable_ip_based_pooling = */ false,
        /* is_websocket = */ false, NetLogWithSource(),
        request_deleted_callback1.Callback(), &request_delegate1,
        &spdy_session_request1, &is_first_request_for_session));
    EXPECT_TRUE(is_first_request_for_session);
  
    // Second request.
    TestOnRequestDeletedCallback request_deleted_callback2;
    TestRequestDelegate request_delegate2;
    std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request2;
    EXPECT_FALSE(spdy_session_pool_->RequestSession(
        kSessionKey, /* enable_ip_based_pooling = */ false,
        /* is_websocket = */ false, NetLogWithSource(),
        request_deleted_callback2.Callback(), &request_delegate2,
        &spdy_session_request2, &is_first_request_for_session));
    EXPECT_FALSE(is_first_request_for_session);
  
    // Third request.
    TestOnRequestDeletedCallback request_deleted_callback3;
    TestRequestDelegate request_delegate3;
    std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request3;
    EXPECT_FALSE(spdy_session_pool_->RequestSession(
        kSessionKey, /* enable_ip_based_pooling = */ false,
        /* is_websocket = */ false, NetLogWithSource(),
        request_deleted_callback3.Callback(), &request_delegate3,
        &spdy_session_request3, &is_first_request_for_session));
    EXPECT_FALSE(is_first_request_for_session);
  
    // Destroying the second request shouldn't cause anything to happen.
    spdy_session_request2.reset();
    base::RunLoop().RunUntilIdle();
    EXPECT_FALSE(request_deleted_callback1.invoked());
    EXPECT_FALSE(request_deleted_callback2.invoked());
    EXPECT_FALSE(request_deleted_callback3.invoked());
  
    // But destroying the first request should cause the second and third
    // callbacks to be invoked.
    spdy_session_request1.reset();
    request_deleted_callback2.WaitUntilInvoked();
    request_deleted_callback3.WaitUntilInvoked();
    EXPECT_FALSE(request_deleted_callback1.invoked());
  
    // Nothing should happen when the third request is destroyed.
    spdy_session_request3.reset();
    base::RunLoop().RunUntilIdle();
    EXPECT_FALSE(request_deleted_callback1.invoked());
  }
  
  TEST_F(SpdySessionPoolTest, RequestSessionDuringNotification) {
    const SpdySessionKey kSessionKey(
        HostPortPair("foo.test", 443), ProxyServer::Direct(),
        PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
        SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */);
  
    CreateNetworkSession();
  
    // First request. Its request deleted callback should never be invoked.
    TestOnRequestDeletedCallback request_deleted_callback1;
    TestRequestDelegate request_delegate1;
    std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request1;
    bool is_first_request_for_session;
    EXPECT_FALSE(spdy_session_pool_->RequestSession(
        kSessionKey, /* enable_ip_based_pooling = */ false,
        /* is_websocket = */ false, NetLogWithSource(),
        request_deleted_callback1.Callback(), &request_delegate1,
        &spdy_session_request1, &is_first_request_for_session));
    EXPECT_TRUE(is_first_request_for_session);
  
    // Second request.
    TestOnRequestDeletedCallback request_deleted_callback2;
    TestRequestDelegate request_delegate2;
    std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request2;
    EXPECT_FALSE(spdy_session_pool_->RequestSession(
        kSessionKey, /* enable_ip_based_pooling = */ false,
        /* is_websocket = */ false, NetLogWithSource(),
        request_deleted_callback2.Callback(), &request_delegate2,
        &spdy_session_request2, &is_first_request_for_session));
    EXPECT_FALSE(is_first_request_for_session);
  
    TestOnRequestDeletedCallback request_deleted_callback3;
    TestRequestDelegate request_delegate3;
    std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request3;
    TestOnRequestDeletedCallback request_deleted_callback4;
    TestRequestDelegate request_delegate4;
    std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request4;
    request_deleted_callback2.SetRequestDeletedCallback(
        base::BindLambdaForTesting([&]() {
          // Third request. It should again be marked as the first request for the
          // session, since it's only created after the original two have been
          // removed.
          bool is_first_request_for_session;
          EXPECT_FALSE(spdy_session_pool_->RequestSession(
              kSessionKey, /* enable_ip_based_pooling = */ false,
              /* is_websocket = */ false, NetLogWithSource(),
              request_deleted_callback3.Callback(), &request_delegate3,
              &spdy_session_request3, &is_first_request_for_session));
          EXPECT_TRUE(is_first_request_for_session);
  
          // Fourth request.
          EXPECT_FALSE(spdy_session_pool_->RequestSession(
              kSessionKey, /* enable_ip_based_pooling = */ false,
              /* is_websocket = */ false, NetLogWithSource(),
              request_deleted_callback4.Callback(), &request_delegate4,
              &spdy_session_request4, &is_first_request_for_session));
          EXPECT_FALSE(is_first_request_for_session);
        }));
  
    // Destroying the first request should cause the second callback to be
    // invoked, and the third and fourth request to be made.
    spdy_session_request1.reset();
    request_deleted_callback2.WaitUntilInvoked();
    base::RunLoop().RunUntilIdle();
    EXPECT_FALSE(request_deleted_callback1.invoked());
    EXPECT_FALSE(request_deleted_callback3.invoked());
    EXPECT_FALSE(request_deleted_callback4.invoked());
    EXPECT_TRUE(spdy_session_request3);
    EXPECT_TRUE(spdy_session_request4);
  
    // Destroying the third request should cause the fourth callback to be
    // invoked.
    spdy_session_request3.reset();
    request_deleted_callback4.WaitUntilInvoked();
    EXPECT_FALSE(request_deleted_callback1.invoked());
    EXPECT_FALSE(request_deleted_callback3.invoked());
  }
  
  static const char kSSLServerTestHost[] = "config-changed.test";
  
  static const struct {
    const char* url;
    const char* proxy_pac_string;
    bool expect_invalidated;
  } kSSLServerTests[] = {
      // If the host and port match, the session should be invalidated.
      {"https://config-changed.test", "DIRECT", true},
      // If host and port do not match, the session should not be invalidated.
      {"https://mail.config-changed.test", "DIRECT", false},
      {"https://config-changed.test:444", "DIRECT", false},
      // If the proxy matches, the session should be invalidated independent of
      // the host.
      {"https://config-changed.test", "HTTPS config-changed.test:443", true},
      {"https://mail.config-changed.test", "HTTPS config-changed.test:443", true},
      // HTTP and SOCKS proxies do not have client certificates.
      {"https://mail.config-changed.test", "PROXY config-changed.test:443",
       false},
      {"https://mail.config-changed.test", "SOCKS5 config-changed.test:443",
       false},
      // The proxy host and port must match.
      {"https://mail.config-changed.test", "HTTPS mail.config-changed.test:443",
       false},
      {"https://mail.config-changed.test", "HTTPS config-changed.test:444",
       false},
  };
  
  // Tests the OnSSLConfigForServerChanged() method when there are no streams
  // active.
  TEST_F(SpdySessionPoolTest, SSLConfigForServerChanged) {
    const MockConnect connect_data(SYNCHRONOUS, OK);
    const MockRead reads[] = {
        MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
    };
  
    std::vector<std::unique_ptr<StaticSocketDataProvider>> socket_data;
    size_t num_tests = base::size(kSSLServerTests);
    for (size_t i = 0; i < num_tests; i++) {
      socket_data.push_back(std::make_unique<StaticSocketDataProvider>(
          reads, base::span<MockWrite>()));
      socket_data.back()->set_connect_data(connect_data);
      session_deps_.socket_factory->AddSocketDataProvider(
          socket_data.back().get());
      AddSSLSocketData();
    }
  
    CreateNetworkSession();
  
    std::vector<base::WeakPtr<SpdySession>> sessions;
    for (size_t i = 0; i < num_tests; i++) {
      SpdySessionKey key(
          HostPortPair::FromURL(GURL(kSSLServerTests[i].url)),
          ProxyServer::FromPacString(kSSLServerTests[i].proxy_pac_string),
          PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
          SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */);
      sessions.push_back(
          CreateSpdySession(http_session_.get(), key, NetLogWithSource()));
    }
  
    // All sessions are available.
    for (size_t i = 0; i < num_tests; i++) {
      SCOPED_TRACE(i);
      EXPECT_TRUE(sessions[i]->IsAvailable());
    }
  
    spdy_session_pool_->OnSSLConfigForServerChanged(
        HostPortPair(kSSLServerTestHost, 443));
    base::RunLoop().RunUntilIdle();
  
    // Sessions were inactive, so the unavailable sessions are closed.
    for (size_t i = 0; i < num_tests; i++) {
      SCOPED_TRACE(i);
      if (kSSLServerTests[i].expect_invalidated) {
        EXPECT_FALSE(sessions[i]);
      } else {
        ASSERT_TRUE(sessions[i]);
        EXPECT_TRUE(sessions[i]->IsAvailable());
      }
    }
  }
  
  // Tests the OnSSLConfigForServerChanged() method when there are streams active.
  TEST_F(SpdySessionPoolTest, SSLConfigForServerChangedWithStreams) {
    const MockConnect connect_data(SYNCHRONOUS, OK);
    const MockRead reads[] = {
        MockRead(SYNCHRONOUS, ERR_IO_PENDING)  // Stall forever.
    };
  
    std::vector<std::unique_ptr<StaticSocketDataProvider>> socket_data;
    size_t num_tests = base::size(kSSLServerTests);
    for (size_t i = 0; i < num_tests; i++) {
      socket_data.push_back(std::make_unique<StaticSocketDataProvider>(
          reads, base::span<MockWrite>()));
      socket_data.back()->set_connect_data(connect_data);
      session_deps_.socket_factory->AddSocketDataProvider(
          socket_data.back().get());
      AddSSLSocketData();
    }
  
    CreateNetworkSession();
  
    std::vector<base::WeakPtr<SpdySession>> sessions;
    std::vector<base::WeakPtr<SpdyStream>> streams;
    for (size_t i = 0; i < num_tests; i++) {
      SCOPED_TRACE(i);
      SpdySessionKey key(
          HostPortPair::FromURL(GURL(kSSLServerTests[i].url)),
          ProxyServer::FromPacString(kSSLServerTests[i].proxy_pac_string),
          PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
          SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */);
      sessions.push_back(
          CreateSpdySession(http_session_.get(), key, NetLogWithSource()));
      streams.push_back(CreateStreamSynchronously(
          SPDY_BIDIRECTIONAL_STREAM, sessions.back(),
          GURL(kSSLServerTests[i].url), MEDIUM, NetLogWithSource()));
      ASSERT_TRUE(streams.back());
    }
  
    // All sessions are active and available.
    for (size_t i = 0; i < num_tests; i++) {
      SCOPED_TRACE(i);
      EXPECT_TRUE(sessions[i]->is_active());
      EXPECT_TRUE(sessions[i]->IsAvailable());
    }
  
    spdy_session_pool_->OnSSLConfigForServerChanged(
        HostPortPair(kSSLServerTestHost, 443));
  
    // The sessions should continue to be active, but now some are unavailable.
    for (size_t i = 0; i < num_tests; i++) {
      SCOPED_TRACE(i);
      ASSERT_TRUE(sessions[i]);
      EXPECT_TRUE(sessions[i]->is_active());
      if (kSSLServerTests[i].expect_invalidated) {
        EXPECT_FALSE(sessions[i]->IsAvailable());
        EXPECT_TRUE(sessions[i]->IsGoingAway());
      } else {
        EXPECT_TRUE(sessions[i]->IsAvailable());
        EXPECT_FALSE(sessions[i]->IsGoingAway());
      }
    }
  
    // Each stream is still around. Close them.
    for (size_t i = 0; i < num_tests; i++) {
      SCOPED_TRACE(i);
      ASSERT_TRUE(streams[i]);
      streams[i]->Close();
    }
  
    // TODO(https://crbug.com/982499): The invalidated sessions should be closed
    // after a RunUntilIdle(), but they are not.
  }
  
  }  // namespace net
  